Compare commits

..
131 Commits
Author SHA1 Message Date
tarsin 6ba494a4f1 Fixup! Re-add warning
Releases / Build (push) Has been cancelled
2022-08-12 20:43:24 +08:00
tarsin b81572ffff Re-add warning 2022-08-12 20:40:13 +08:00
tarsin d2ae1567be import jetpack compose 2022-08-12 19:01:59 +08:00
tarsin d00055a5f8 Add back doh 2022-08-12 18:09:59 +08:00
tarsin 896a2684d9 Make release 2022-08-12 17:28:13 +08:00
tarsin b98ecb17d3 Checkout to nettle 3.8.1 2022-08-12 17:24:31 +08:00
tarsin cd9af7feee Silence a warning 2022-08-12 15:29:39 +08:00
tarsin 1c38f77b5c Refactor history with room paging 2022-08-12 15:29:04 +08:00
tarsin c5977e358e Rename .java to .kt 2022-08-12 15:29:04 +08:00
tarsin 78e58e6894 Import android paging 2022-08-12 12:02:54 +08:00
tarsin 30433c5cbf Drop history limit 2022-08-12 12:00:50 +08:00
renovate[bot] fe8e3766c0 Update dependency com.google.android.material:material to v1.7.0-beta01 2022-08-11 22:02:01 +00:00
txyyh 90c95d9788 Enable renovate automerge 2022-08-11 09:57:41 +00:00
tarsin 3930a79a47 Update AndroidManifest.xml 2022-08-11 14:17:32 +08:00
renovate[bot] fc852780f8 Update activity_version to v1.6.0-beta01 (1.8.x.x) (#177)
Update activity_version to v1.6.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 09:11:58 +08:00
renovate[bot] 35315c38cd Update dependency androidx.annotation:annotation to v1.5.0-alpha02 (1.8.x.x) (#179)
Update dependency androidx.annotation:annotation to v1.5.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 09:11:41 +08:00
renovate[bot] 0b40f37418 Update dependency androidx.webkit:webkit to v1.5.0-rc01 (1.8.x.x) (#183)
Update dependency androidx.webkit:webkit to v1.5.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 09:11:07 +08:00
renovate[bot] 5a5f0421d8 Update dependency androidx.annotation:annotation-experimental to v1.3.0-beta01 (1.8.x.x) (#180)
Update dependency androidx.annotation:annotation-experimental to v1.3.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 09:10:38 +08:00
renovate[bot] 86c5c5bc59 Update dependency androidx.appcompat:appcompat to v1.6.0-beta01 (1.8.x.x) (#181)
Update dependency androidx.appcompat:appcompat to v1.6.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 09:08:04 +08:00
renovate[bot] 776a088de9 Update core_version to v1.9.0-beta01 (1.8.x.x) (#178)
Update core_version to v1.9.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 09:07:45 +08:00
renovate[bot] ba54655877 Update dependency androidx.recyclerview:recyclerview to v1.3.0-beta02 (1.8.x.x) (#182)
Update dependency androidx.recyclerview:recyclerview to v1.3.0-beta02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-11 09:07:30 +08:00
txyyh 9f47ae438c Disable blank issue 2022-08-10 16:49:39 +00:00
txyyh 1717233f5a Add LICENSE header 2022-08-10 09:00:26 +00:00
txyyh cf71feb89d Use ic_launcher_foreground on scene_login (#174) 2022-08-10 16:23:09 +08:00
txyyh 21e393246a Use new icon (#173)
* Use new icon

* Add monochrome icon
2022-08-10 15:45:15 +08:00
txyyh c368f234f0 Fix wrong deploy username 2022-08-09 05:04:44 +00:00
tarsin c3d8610e2a Update AndroidManifest.xml 2022-08-09 08:43:24 +08:00
txyyh 1a10e330aa Only build CI on branches (#170) 2022-08-08 00:59:21 +08:00
txyyh d992897506 Auto deploy release to fdroid repo (#169) 2022-08-07 19:21:55 +08:00
tarsin a0fe6f0d7d Revert "archive: Use memfd instead"
* It sucks, seems simplify the conditions, but more syscalls are
  made(read/seek/write), more context switch, futhermore AImageDecoder itself does support
  read raw buffer, and we are not intend to support multi-process decoding

This reverts commit f4826ea727.
2022-08-07 19:14:25 +08:00
tarsin f4826ea727 archive: Use memfd instead 2022-08-07 17:13:48 +08:00
tarsin a17b254494 Call mmap/munmap in native code 2022-08-07 15:23:32 +08:00
tarsin 0190fc07bd Fix thumb card margin 2022-08-07 14:38:27 +08:00
tarsin c8dab6a88f Archive: ignore crc32 2022-08-07 11:43:27 +08:00
renovate[bot] fbafadfd98 Update dependency gradle to v7.5.1 (1.8.x.x) (#167)
Update dependency gradle to v7.5.1

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-06 07:42:25 +08:00
tarsin b5832fd706 Make release
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-08-05 22:13:38 +08:00
tarsin 93312a0714 Dependencies tweaks 2022-08-05 22:13:03 +08:00
tarsin 7e6e1f6f14 Make default label removable 2022-08-05 21:14:38 +08:00
tarsin 6cd498af6a Make downloadList qs drawer drag to rearrange position and swipe to remove 2022-08-05 20:27:43 +08:00
tarsin 2781aaca14 Make gallerylist qs drawer drag to rearrange position and swipe to remove 2022-08-05 20:03:51 +08:00
tarsin 91b9c9af40 Reimplement history swipe-to-remove 2022-08-05 19:44:12 +08:00
tarsin 5c0d10b41a Clean up readme 2022-08-05 18:14:15 +08:00
tarsin a3bdbd7a85 Drop android-advancedrecyclerview
* Actually this library is unmaintained for above a year, and drag feature we used is totally broken on Android 12 and above https://github.com/h6ah4i/android-advancedrecyclerview/issues/516 https://github.com/Ehviewer-Overhauled/Ehviewer/issues/54
2022-08-05 18:12:23 +08:00
tarsin f66151f880 Clean up proguards 2022-08-05 15:13:59 +08:00
txyyh 6c5f52995d Add mult-language README and markdown TOC (#165)
* Add mult-language README

* Add markdown TOC

* Add language selection to README

* Fix wrong branch name in README
2022-08-05 14:55:03 +08:00
tarsin d94b96a8eb Clean up 2022-08-04 13:05:47 +08:00
tarsin a5d90cc97d Make release
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-08-04 07:39:53 +08:00
tarsin 657d566e89 No need to merge old local db 2022-08-04 07:06:01 +08:00
tarsin 5e6d24f01e Clean up 2022-08-04 06:49:37 +08:00
tarsin e353bbe4c3 Refine database import export 2022-08-04 06:35:42 +08:00
renovate[bot] 4ee2cdf19c Update plugin com.android.application to v7.2.2 (1.8.x.x) (#162)
Update plugin com.android.application to v7.2.2

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-04 04:59:09 +08:00
txyyh b28831a8aa Delete useless string (#161) 2022-08-04 04:58:53 +08:00
EdwardWuandtarsin 800b64d5d3 fixup! Ehviewer: Add Chinese(Traditional) Translate (#160)
* Ehviewer: Add Chinese(Traditional) Translate

Signed-off-by: Edward Wu <bluehome.wu@gmail.com>

* fixup! Ehviewer: Add Chinese(Traditional) Translate

Signed-off-by: Edward Wu <bluehome.wu@gmail.com>

Co-authored-by: tarsin <43900799+asuka-mio@users.noreply.github.com>
2022-08-03 22:22:08 +08:00
tarsin cfa6164b5c Use ksp 2022-08-03 22:16:01 +08:00
tarsin 9cb22b6e17 Initial stub migrate to androidx room 2022-08-03 21:59:55 +08:00
tarsin 19891e6768 Fix location 2022-08-03 20:39:11 +08:00
tarsin c616410e0b Drop daogenerator first 2022-08-03 20:33:45 +08:00
EdwardWu 31e1e79ed2 Ehviewer: Add Chinese(Traditional) Translate (#158)
Signed-off-by: Edward Wu <bluehome.wu@gmail.com>
2022-08-03 19:48:22 +08:00
tarsin 6191a9827d Use Ofast 2022-08-03 19:14:11 +08:00
tarsin f373d8f293 Allow universal apk build 2022-08-03 19:07:08 +08:00
tarsin c233eb5a30 Avoid unnecessary native method calling java method 2022-08-03 18:49:00 +08:00
tarsin 360a3e9b5f Clean up 2022-08-03 18:10:30 +08:00
tarsin 01aff59d46 Re-checkout 2022-08-03 17:56:27 +08:00
tarsin f0f1dca7cb Fix build 2022-08-03 17:51:01 +08:00
tarsin 6f3bd92539 Merge native 2022-08-03 17:48:46 +08:00
tarsin a8023f7334 Minify proguard rules 2022-08-03 17:34:21 +08:00
txyyh 8c368972ec Add get-it-on-fdroid into README (#157)
* Fix wrong alt

* Add get-it-on-fdroid
2022-08-02 23:52:50 +08:00
tarsin 5aea90314a Update README.md 2022-08-02 14:02:53 +08:00
tarsin 0c8ba49d61 Update README.md 2022-08-02 14:00:40 +08:00
tarsin ea5b8b9f81 Update README.md 2022-08-02 13:52:28 +08:00
LaLucid 6b21f5082c update CI (#155)
* update CI

* do not need setup-android
2022-08-02 13:46:49 +08:00
tarsin 438d7f1b34 Use O3 optimization 2022-08-01 22:24:01 +08:00
tarsin 301797b477 Import androidx navigation 2022-08-01 10:18:36 +08:00
tarsin 2758be2462 Code clean 2022-08-01 10:14:33 +08:00
tarsin 8f03e4910d Code clean 2022-08-01 10:04:27 +08:00
tarsin a09efd3eea Fix #81 2022-08-01 09:48:56 +08:00
tarsin a66d460572 Remove warning 2022-08-01 09:35:17 +08:00
txyyh 162b88915e 1.8.0.4
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-31 23:10:42 +08:00
txyyh d014b6947c Rewrite download section in README (#150)
* Delete CI Download in README

* Add Chinese NOTE

* Add English NOTE

* Use icon for github download
2022-07-31 12:34:48 +08:00
tarsin a6188964c9 Add simple sanitize, Fix#148 2022-07-31 09:23:41 +08:00
tarsin 722fbec72c 1.8.0.3
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-29 18:08:31 +08:00
tarsin 22190fb686 Fix a double-free problem 2022-07-28 23:45:35 +08:00
txyyh 49ea70c839 Add monochrome icon (#144) 2022-07-28 12:06:56 +08:00
tarsin c655e7e1c9 Drop blank res 2022-07-28 11:28:45 +08:00
tarsin d13cd1e07f 1.8.0.2
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-28 10:37:07 +08:00
tarsin 7365fd0448 Fix a memleak 2022-07-28 10:33:47 +08:00
txyyh f1febca312 Let renovate bot detect two branch 2022-07-28 04:12:33 +08:00
renovate[bot] dbf67404c0 Update fragment_version to v1.5.1 (#138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 03:39:42 +08:00
renovate[bot] 8cfe8c4b80 Update collection_version to v1.3.0-alpha02 (#137)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 03:39:25 +08:00
tarsin 674f53315f Code clean 2022-07-27 19:46:39 +08:00
tarsin e51cd6b90d Drop taptargetview
* Not MD3
2022-07-25 20:44:59 +08:00
tarsin a44f12b2e0 Reformat Code 2022-07-25 20:07:16 +08:00
tarsin a2552ed5b9 Reformat 2022-07-25 20:05:38 +08:00
tarsin 763c007950 Log tweaks 2022-07-25 20:02:47 +08:00
tarsin b221bf8e52 Release 1.8.0.1
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-25 19:16:07 +08:00
tarsin ab9a89685c Code clean 2022-07-25 19:02:10 +08:00
tarsin 41ccb16227 Code clean 2022-07-25 18:42:18 +08:00
tarsin f6bf7b1ba6 No need to thanks image decs anymore 2022-07-25 13:06:24 +08:00
tarsin 09e7113df1 Fix a dirty dealloc 2022-07-25 12:23:52 +08:00
tarsin 62a7e283a4 Should be able to support all format now 2022-07-25 10:51:57 +08:00
tarsin a7f7bf1316 Release 1.8
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-25 10:20:16 +08:00
tarsin a72fca3dfb Fix a lag 2022-07-25 10:16:54 +08:00
tarsin c5e7a6e8f9 Code clean 2022-07-25 10:02:34 +08:00
tarsin 80a20f25b9 Fix animated 2022-07-25 09:28:40 +08:00
tarsin 500163fe1a Merge pull request #132 from Ehviewer-Overhauled/master
Pull master
2022-07-25 07:54:04 +08:00
txyyh 513886eff4 pick gallery_detail_header.xml changes to television (#131)
* Rewrite info TableLayout to ConstraintLayout for television

* Rewrite layout and minor design improve for television
2022-07-25 07:50:58 +08:00
txyyh 0093db9986 Make buttongroup horizontal (#125)
* Use another impl to create space

* Horizontal button
2022-07-25 07:50:41 +08:00
tarsin 50c6111773 Imagedecoder do not support anon pipe, drop it and use memarea 2022-07-24 23:32:03 +08:00
tarsin 3a05649c71 Code clean 2022-07-24 22:34:19 +08:00
tarsin 9d25376e1f Refactor 2022-07-24 21:41:53 +08:00
tarsin 89935b5639 Hard cast 2022-07-24 20:32:39 +08:00
tarsin e095627aee Refactor 2022-07-24 20:20:04 +08:00
tarsin b61e46e956 Clean up 2022-07-24 19:49:34 +08:00
tarsin 76459c5dd2 Initial stupid migrate to ImageDecoder 2022-07-24 19:18:54 +08:00
tarsin 7ad67a3dbd Minsdk 31 2022-07-24 18:24:24 +08:00
tarsin 577b49ac19 Drop image decode native libs 2022-07-24 18:17:37 +08:00
txyyh 182e3a0d40 Rewrite README description (#124) 2022-07-24 17:40:19 +08:00
txyyh 6d140aaa9d Delete FOSSA (#123) 2022-07-24 14:24:31 +08:00
tarsin 17248d3a82 Revert "Use SMAPSHOT version of mdc"
This reverts commit 7a0fab29bc.
2022-07-24 13:07:28 +08:00
txyyh ec80e7a189 Back to corner (#121) 2022-07-24 05:36:07 +08:00
txyyh 493eeadf1b Add note for CI download (#122) 2022-07-24 03:35:28 +08:00
tarsin 7a0fab29bc Use SMAPSHOT version of mdc 2022-07-23 20:41:02 +08:00
hathlife 5f7e40f258 Gallery detail head tv layout (#96)
* Make height of App logo ImageView match parent

* Add TV layout for gallery detail scene.

* Add credits

* Use GPLv3 for all TV layout code
2022-07-23 20:27:42 +08:00
txyyh 0bf6b8d827 Release 1.7.29.1
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-23 17:14:25 +08:00
tarsin 4c6ccab051 Native lib info refactor 2022-07-23 16:25:53 +08:00
tarsin 00377f15b9 Set our own libs 2022-07-23 16:07:23 +08:00
tarsin 96b50eb5aa Add nettle for encrypt 2022-07-23 15:51:43 +08:00
tarsin acdf681d40 Add gnutls libnettle 2022-07-23 14:26:17 +08:00
tarsin b92cf9746b Add .gz .xz .tar .cbz .cbr to supported mimeTypes 2022-07-23 13:28:41 +08:00
txyyh d0dfbaaae0 Rewrite layout and minor design improve (#116)
* Use ConstraintLayout instead

* Specify info Card Text to BodySmall

* Specify thumb shape to none (no corner)
2022-07-23 07:44:33 +08:00
txyyh 0c656e617b Add CI download link (#117)
* Add CI Download link

* Use head in right way
2022-07-23 00:49:03 +08:00
184 changed files with 5138 additions and 8673 deletions

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
blank_issues_enabled: false
contact_links:
- name: 所有其他问题 / All other questions
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
+10 -7
View File
@@ -1,6 +1,11 @@
name: CI
on: [push]
on:
push:
branches:
- '*'
tags-ignore:
- "*"
jobs:
linux:
@@ -9,6 +14,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -16,18 +23,14 @@ jobs:
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
with:
arguments: assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
+4 -6
View File
@@ -9,6 +9,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -16,18 +18,14 @@ jobs:
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
with:
arguments: assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
+26 -7
View File
@@ -12,6 +12,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -19,26 +21,43 @@ jobs:
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
with:
arguments: assembleRelease
- name: Releases
uses: softprops/action-gh-release@v0.1.14
uses: softprops/action-gh-release@v1
with:
body: Bump Version
discussion_category_name: Announcements
files: |
app/build/outputs/apk/release/app-universal-release.apk
app/build/outputs/apk/release/app-arm64-v8a-release.apk
app/build/outputs/apk/release/app-x86_64-release.apk
app/build/outputs/apk/release/app-x86-release.apk
app/build/outputs/apk/release/app-armeabi-v7a-release.apk
- name: Collect Apks
run: |
mkdir app/build/outputs/apk/release/fdroid
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME.apk
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME-armv7.apk
- name: Push Apks
uses: cpina/github-action-push-to-another-repository@v1.5
env:
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
with:
source-directory: app/build/outputs/apk/release/fdroid
destination-github-username: Ehviewer-Overhauled
destination-repository-name: fdroid-repo
user-name: 'github-actions[bot]'
user-email: 'github-actions[bot]@users.noreply.github.com'
target-branch: deploy
commit-message: Add apks from $GITHUB_REF
target-directory: fdroid/tmp-apks
+7 -13
View File
@@ -1,15 +1,9 @@
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
url = https://github.com/Ehviewer-Overhauled/libjpeg-turbo
[submodule "native/src/main/jni/image/giflib/giflib"]
path = native/src/main/jni/image/giflib/giflib
url = https://github.com/Ehviewer-Overhauled/giflib
[submodule "native/src/main/jni/image/libpng/libpng"]
path = native/src/main/jni/image/libpng/libpng
url = https://github.com/Ehviewer-Overhauled/libpng
[submodule "native/src/main/jni/libarchive"]
path = native/src/main/jni/libarchive
[submodule "app/src/main/cpp/libarchive"]
path = app/src/main/cpp/libarchive
url = https://github.com/Ehviewer-Overhauled/libarchive
[submodule "native/src/main/jni/liblzma"]
path = native/src/main/jni/liblzma
[submodule "app/src/main/cpp/liblzma"]
path = app/src/main/cpp/liblzma
url = https://github.com/Ehviewer-Overhauled/xz
[submodule "app/src/main/cpp/nettle/nettle"]
path = app/src/main/cpp/nettle/nettle
url = https://github.com/Ehviewer-Overhauled/nettle
+51 -30
View File
@@ -1,5 +1,17 @@
<p align="right">
<strong>English<strong>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-cn.md">
简体中文
</a>
<span> | <span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-tw.md">
正體中文
</a>
</p>
<h1 align="center">
<img src="art/launcher_icon-web.png" width="200">
<img src="art/launcher_icon-web.svg" width="200">
<br>EhViewer<br>
</h1>
@@ -7,7 +19,7 @@
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/master/LICENSE">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/LICENSE">
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
@@ -21,54 +33,65 @@
</a>
</p>
## Description
这是一个 E-Hentai Android 平台的浏览器
已针对性适配 Material Design 3 及其动态取色系统
动态配色仅供 Android 12 及以上平台使用!
<div align="center">
<h3>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#description">
Description
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#download">
Download
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#screenshot">
Screenshot
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#thanks">
Thanks
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#license">
License
</a>
</h3>
</div>
An E-Hentai Application for Android.
# Description
with Material Design 3 and Dynamic Color Support.
EhViewer fork dedicated to lightweight and high-performance
Dynamic Color only support Android S and above!
with Material Design 3 and Dynamic Color Support
## Download
# Download
[Github 下载](https://github.com/Ehviewer-Overhauled/Ehviewer/releases)
<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>
## Screenshot
App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 and above, also the develop mainline. Version 1.7 support Android 11 and below, drop down some spicetify functions for Android 12 to make app useable on lower Android System
# Screenshot
![screenshot-01](art/screenshot-01.png)
![screenshot-02](art/screenshot-02.png)
## Thanks
本项目受到了诸多开源项目的帮助
# Thanks
Here is the libraries
- [AOSP](http://source.android.com/)
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
- [AOSP&AndroidX](http://source.android.com/)
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
- [GnuTLS](https://gnutls.org/)
- [material-components-android](https://github.com/material-components/material-components-android)
- [material-design-icons](https://github.com/google/material-design-icons)
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
- [apng](http://apng.sourceforge.net/)
- [giflib](http://giflib.sourceforge.net)
- [greenDAO](https://github.com/greenrobot/greenDAO)
- [jsoup](https://github.com/jhy/jsoup)
- [libjpeg-turbo](https://libjpeg-turbo.org/)
- [libpng](http://www.libpng.org/pub/png/libpng.html)
- [okhttp](https://github.com/square/okhttp)
- [roaster](https://github.com/forge/roaster)
- [TapTargetView](https://github.com/KeepSafe/TapTargetView)
- [RikkaX](https://github.com/RikkaApps/RikkaX)
- [libarchive](http://www.libarchive.org/)
- [XZ Utils](https://tukaani.org/xz/)
## License
# License
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
@@ -79,5 +102,3 @@ Here is the libraries
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/>.
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fasuka-mio%2FEhviewer.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fasuka-mio%2FEhviewer?ref=badge_large)
+2 -1
View File
@@ -4,4 +4,5 @@ manifest-merger-release-report.txt
/src/main/libs
/src/main/obj
# Intellij
*.iml
*.iml
/.cxx
+61 -29
View File
@@ -1,12 +1,13 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.devtools.ksp'
}
android {
compileSdkVersion 33
buildToolsVersion "33.0.0"
ndkVersion "25.0.8775105"
Properties localProperties = new Properties()
if (project.rootProject.file('local.properties').exists()) {
@@ -18,6 +19,7 @@ android {
enable = true
reset()
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
universalApk = true
}
}
@@ -32,13 +34,19 @@ android {
defaultConfig {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 26
minSdkVersion 31
targetSdkVersion 33
versionCode 114532
versionName "1.7.29.0"
versionCode 180007
versionName "1.8.1.0"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
@@ -78,27 +86,25 @@ android {
}
}
sourceSets {
main {
java.srcDirs += 'src/main/java-gen'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.3.0"
}
namespace 'com.hippo.ehviewer'
}
tasks.withType(JavaCompile) {
task -> task.dependsOn ":daogenerator:executeDaoGenerator"
}
dependencies {
def activity_version = "1.6.0-alpha05"
def activity_version = "1.6.0-beta01"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.annotation:annotation:1.4.0"
implementation "androidx.annotation:annotation-experimental:1.2.0"
implementation "androidx.appcompat:appcompat:1.6.0-alpha05"
implementation "androidx.annotation:annotation:1.5.0-alpha02"
implementation "androidx.annotation:annotation-experimental:1.3.0-beta01"
implementation "androidx.appcompat:appcompat:1.6.0-beta01"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
@@ -107,27 +113,36 @@ dependencies {
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
implementation "androidx.browser:browser:1.4.0"
def collection_version = "1.3.0-alpha01"
def collection_version = "1.3.0-alpha02"
implementation "androidx.collection:collection:$collection_version"
implementation "androidx.collection:collection-ktx:$collection_version"
def compose_version = "1.3.0-alpha03"
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
implementation "androidx.compose.runtime:runtime:$compose_version"
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material3:material3:1.0.0-alpha16"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
def core_version = "1.9.0-alpha05"
def core_version = "1.9.0-beta01"
implementation "androidx.core:core:$core_version"
implementation "androidx.core:core-ktx:$core_version"
implementation "androidx.customview:customview:1.2.0-alpha01"
implementation "androidx.documentfile:documentfile:1.1.0-alpha01"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.5.0"
def fragment_version = "1.6.0-alpha01"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.6.0-alpha01"
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
@@ -135,8 +150,23 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
implementation "androidx.loader:loader:1.1.0"
def nav_version = "2.5.1"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.paging:paging-runtime:3.2.0-alpha02"
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta01'
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta02'
def room_version = "2.5.0-alpha02"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-paging:$room_version"
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation "androidx.transition:transition-ktx:1.4.1"
@@ -144,24 +174,26 @@ dependencies {
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.webkit:webkit:1.5.0-beta01'
implementation 'androidx.webkit:webkit:1.5.0-rc01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.7.0-alpha03'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
implementation 'com.google.android.material:material:1.7.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 'org.greenrobot:greendao:3.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jsoup:jsoup:1.15.2'
implementation projects.native
}
configurations.all {
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
exclude group: 'dev.rikka.rikkax.material', module: 'material'
exclude group: 'androidx.compose.material'
}
+8 -17
View File
@@ -7,28 +7,19 @@
public static void throw*(...);
}
-keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
-keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
-keepnames class com.hippo.ehviewer.ui.scene.* { }
-keep class com.hippo.ehviewer.dao.* { *; }
-keepattributes SourceFile,LineNumberTable
-renamesourcefileattribute
-repackageclasses
-allowaccessmodification
-mergeinterfacesaggressively
-overloadaggressively
-dontwarn net.sqlcipher.database.**
-dontwarn rx.**
-dontwarn org.conscrypt.ConscryptHostnameVerifier
-dontwarn org.conscrypt.Conscrypt$Version
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
-dontwarn org.bouncycastle.jsse.BCSSLSocket
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
-dontwarn net.sqlcipher.Cursor
-dontwarn org.bouncycastle.jsse.BCSSLParameters
-dontwarn org.conscrypt.Conscrypt
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE
+25 -32
View File
@@ -26,11 +26,12 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Required to maintain app compatibility. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
@@ -120,35 +121,21 @@
<data android:mimeType="application/x-7z-compressed" />
<data android:mimeType="application/x-rar-compressed" />
<data android:mimeType="application/x-zip-compressed" />
</intent-filter>
<data android:mimeType="application/x-tar" />
<data android:mimeType="application/x-xz" />
<data android:mimeType="application/gzip" />
<data android:mimeType="application/x-cbz" />
<data android:mimeType="application/x-cbr" />
<data android:mimeType="application/x-cbt" />
<data android:mimeType="application/x-cb7" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:pathPattern=".*\\.7z" />
<data android:pathPattern=".*\\.rar" />
<data android:pathPattern=".*\\.zip" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.7z" />
<data android:pathPattern=".*\\.rar" />
<data android:pathPattern=".*\\.zip" />
<!-- untested -->
<data android:mimeType="application/vnd.comicbook+zip" />
<data android:mimeType="application/vnd.comicbook-rar" />
<data android:mimeType="application/x-compressed-tar" />
<data android:mimeType="application/x-bzip-compressed-tar" />
<data android:mimeType="application/x-lzma-compressed-tar" />
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
</activity>
@@ -158,6 +145,12 @@
android:label="@string/settings"
android:theme="@style/AppTheme" />
<activity
android:name="com.hippo.ehviewer.ui.SecurityActivity"
android:configChanges="screenSize"
android:label="@string/settings"
android:theme="@style/AppTheme" />
<activity
android:name="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
android:excludeFromRecents="true"
+50
View File
@@ -0,0 +1,50 @@
project(ehviewer)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto \
-mllvm -polly \
-mllvm -polly-run-dce \
-mllvm -polly-run-inliner \
-mllvm -polly-isl-arg=--no-schedule-serialize-sccs \
-mllvm -polly-ast-use-context \
-mllvm -polly-detect-keep-going \
-mllvm -polly-position=before-vectorizer \
-mllvm -polly-vectorizer=stripmine \
-mllvm -polly-detect-profitability-min-per-loop-insts=40 \
-mllvm -polly-invariant-load-hoisting")
# Build GNUTLS libnettle
add_subdirectory(nettle)
# Configure libnettle support for libarchive
include_directories(nettle)
include_directories(.)
set(HAVE_LIBNETTLE 1)
set(HAVE_NETTLE_AES_H 1)
set(HAVE_NETTLE_HMAC_H 1)
set(HAVE_NETTLE_MD5_H 1)
set(HAVE_NETTLE_PBKDF2_H 1)
set(HAVE_NETTLE_RIPEMD160_H 1)
set(HAVE_NETTLE_SHA_H 1)
# Build liblzma
add_subdirectory(liblzma)
# Configure lzma support for libarchive
include_directories(liblzma/src/liblzma/api)
SET(HAVE_LIBLZMA 1)
SET(HAVE_LZMA_H 1)
SET(HAVE_LZMA_STREAM_ENCODER_MT 1)
SET(HAVE_LZMADEC_H 1)
SET(HAVE_LIBLZMADEC 1)
# Configure libarchive link's static lib
SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
# Build libarchive
add_subdirectory(libarchive)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED main.c archive.c image.c)
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
@@ -17,15 +17,17 @@
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
*/
#include <jni.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <jni.h>
#include <android/log.h>
#include <archive.h>
#include <archive_entry.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdbool.h>
#define LOG_TAG "libarchive_wrapper"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
@@ -43,21 +45,27 @@ static char *passwd = NULL;
static void *archiveAddr = NULL;
static jlong archiveSize = 0;
const char supportExt[9][6] = {
"jpeg",
"jpg",
"png",
"gif",
"webp",
"bmp",
"ico",
"wbmp",
"heif"
};
static int filename_is_playable_file(const char *name) {
const char *dotptr = strrchr(name, '.');
if (dotptr++) {
switch (*dotptr) {
case 'j':
return (strcmp(dotptr, "jpg") == 0) || (strcmp(dotptr, "jpeg") == 0);
case 'p':
return strcmp(dotptr, "png") == 0;
case 'g':
return strcmp(dotptr, "gif") == 0;
default:
return 0;
}
}
return 0;
if (!dotptr++)
return false;
int i;
for (i = 0; i < 9; i++)
if (strcmp(dotptr, supportExt[i]) == 0)
return true;
return false;
}
static long archive_list_all_entries() {
@@ -85,17 +93,21 @@ static int archive_alloc() {
arc = archive_read_new();
archive_read_support_format_all(arc);
archive_read_support_filter_all(arc);
archive_read_set_option(arc, "zip", "ignorecrc32", "1");
if (passwd)
archive_read_add_passphrase(arc, passwd);
return archive_read_open_memory(arc, archiveAddr, archiveSize);
}
JNIEXPORT jint JNICALL
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jlong addr, jlong size) {
archiveAddr = (void *) addr;
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jint fd, jlong size) {
archiveAddr = mmap64(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (archiveAddr == MAP_FAILED) {
LOGE("%s%d", "mmap64 failed with errno ", errno);
return 0;
}
archiveSize = size;
env = _;
madvise(archiveAddr, archiveSize, MADV_SEQUENTIAL | MADV_WILLNEED);
long r = archive_alloc();
if (r) {
r = 0;
@@ -111,51 +123,62 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jlong add
need_encrypt = true;
break;
case 0: // format supports but no encrypted entry found
case ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW: // Sometimes we might not read through whole file|vm, but it means so far we haven't meet any encrypted entry yet
default:
need_encrypt = false;
}
LOGI("%s%d", "archive_read_has_encrypted_entries returns ", encryptRet);
// madvise for readahead optimization
int format = archive_format(arc);
switch (format) {
case ARCHIVE_FORMAT_ZIP:
case ARCHIVE_FORMAT_RAR:
case ARCHIVE_FORMAT_RAR_V5:
madvise(archiveAddr, archiveSize, MADV_SEQUENTIAL | MADV_WILLNEED);
break;
default:
;
}
}
archive_release();
return r;
}
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *_, jobject thiz, jint index,
jint fd) {
typedef struct Memarea {
void *buffer;
long size;
} Memarea;
JNIEXPORT jlong JNICALL
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *_, jobject thiz, jint index) {
struct archive_entry *entry;
size_t size;
int ret;
la_int64_t offset;
const void *buff;
if (!arc || index < cur_index) {
archive_alloc();
cur_index = 0;
}
Memarea *memarea = malloc(sizeof(Memarea));
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname(entry)))
continue;
if (cur_index++ == index) {
for (;;) {
ret = archive_read_data_block(arc, &buff, &size, &offset);
if (ret == ARCHIVE_EOF)
break;
if (ret != ARCHIVE_OK) {
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
break;
}
write(fd, buff, size);
}
memarea->size = (long) archive_entry_size(entry);
memarea->buffer = malloc(memarea->size);
ret = archive_read_data(arc, memarea->buffer, memarea->size);
if (ret != memarea->size)
LOGE("%s", "No enough data read, WTF?");
if (ret < 0)
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
break;
}
}
return (jlong) memarea;
}
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *jniEnv, jobject thiz) {
archive_release();
free(passwd);
munmap(archiveAddr, archiveSize);
}
JNIEXPORT jboolean JNICALL
@@ -172,19 +195,17 @@ Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *_, jobject thiz, jstri
free(passwd);
passwd = calloc(len, sizeof(char));
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
LOGI("%s%s", "Passwd is ", passwd);
archive_alloc();
void *tmpBuf = alloca(4096);
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname(entry)))
continue;
if (!archive_entry_is_encrypted(entry))
continue;
void *tmpBuf = malloc(4096);
if (archive_read_data(arc, tmpBuf, 4096) < ARCHIVE_OK) {
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
ret = false;
}
free(tmpBuf);
break;
}
archive_release();
+380
View File
@@ -0,0 +1,380 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <android/data_space.h>
#include <android/imagedecoder.h>
#include <android/log.h>
#include <GLES2/gl2.h>
#include "image.h"
#define TAG "ImageDecoder_wrapper"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG ,__VA_ARGS__)
#define IMAGE_TILE_MAX_SIZE (512 * 512)
static void *tile_buffer = NULL;
bool image_onLoad() {
tile_buffer = malloc(IMAGE_TILE_MAX_SIZE * 4);
return tile_buffer != NULL;
}
void image_onUnload() {
free(tile_buffer);
tile_buffer = NULL;
}
typedef struct {
AImageDecoder *decoder;
void *srcBuffer;
size_t stride;
int fd;
} SOURCE;
typedef struct {
int32_t width;
int32_t height;
void *buffer;
int bufferLen;
bool isAnimated;
bool alpha;
int delay;
SOURCE *src;
} IMAGE;
static void recycleSource(SOURCE *src) {
if (!src)
return;
AImageDecoder_delete(src->decoder);
free(src->srcBuffer);
free(src);
}
static void recycle(IMAGE *image) {
if (!image)
return;
recycleSource(image->src);
free(image->buffer);
free(image);
}
typedef struct Memarea {
void *buffer;
long size;
} Memarea;
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
int width, int height) {
int left;
int line;
size_t line_stride;
int src_stride;
int src_pos;
int dst_pos;
size_t dst_blank_length;
// Sanitize
if (src_x < 0) {
width -= src_x;
dst_x -= src_x;
src_x = 0;
}
if (dst_x < 0) {
width -= dst_x;
src_x -= dst_x;
dst_x = 0;
}
if (width <= 0) {
return false;
}
if (src_y < 0) {
height -= src_y;
dst_y -= src_y;
src_y = 0;
}
if (dst_y < 0) {
height -= dst_y;
src_y -= dst_y;
dst_y = 0;
}
if (height <= 0) {
return false;
}
left = src_x + width - src_w;
if (left > 0) {
width -= left;
}
left = dst_x + width - dst_w;
if (left > 0) {
width -= left;
}
if (width <= 0) {
return false;
}
left = src_y + height - src_h;
if (left > 0) {
height -= left;
}
left = dst_y + height - dst_h;
if (left > 0) {
height -= left;
}
if (height <= 0) {
return false;
}
// Init
line_stride = (size_t) (width * 4);
src_stride = src_w * 4;
src_pos = src_y * src_stride + src_x * 4;
dst_pos = 0;
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
// First line
dst_pos += (int)dst_blank_length;
memcpy(dst + dst_pos, src + src_pos, line_stride);
dst_pos += (int)line_stride;
src_pos += src_stride;
// Other lines
dst_blank_length = (size_t) ((dst_w - width) * 4);
for (line = 1; line < height; line++) {
dst_pos += (int)dst_blank_length;
memcpy(dst + dst_pos, src + src_pos, line_stride);
dst_pos += (int)line_stride;
src_pos += src_stride;
}
return true;
}
void decodeCommon(IMAGE *image, SOURCE *src) {
image->src = src;
AImageDecoder *decoder = src->decoder;
image->isAnimated = AImageDecoder_isAnimated(decoder);
AImageDecoder_setDataSpace(decoder, ADATASPACE_SRGB);
AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888);
const AImageDecoderHeaderInfo *headerInfo = AImageDecoder_getHeaderInfo(decoder);
image->height = AImageDecoderHeaderInfo_getHeight(headerInfo);
image->width = AImageDecoderHeaderInfo_getWidth(headerInfo);
image->alpha =
AImageDecoderHeaderInfo_getAlphaFlags(headerInfo) == ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
src->stride = AImageDecoder_getMinimumStride(decoder);
image->bufferLen = (int) (image->height * src->stride);
image->buffer = malloc(image->bufferLen);
if (image->isAnimated) {
AImageDecoder_setInternallyHandleDisposePrevious(decoder, false);
AImageDecoderFrameInfo *frameInfo = AImageDecoderFrameInfo_create();
AImageDecoder_getFrameInfo(decoder, frameInfo);
image->delay = (int) AImageDecoderFrameInfo_getDuration(frameInfo) / 1000000;
AImageDecoderFrameInfo_delete(frameInfo);
}
AImageDecoder_decodeImage(decoder, image->buffer, src->stride, image->bufferLen);
if (!image->isAnimated) {
recycleSource(image->src);
image->src = NULL;
}
}
IMAGE *createFromFd(int fd) {
AImageDecoder *decoder = NULL;
if (AImageDecoder_createFromFd(fd, &decoder) != ANDROID_IMAGE_DECODER_SUCCESS)
return NULL;
IMAGE *image = calloc(1, sizeof(IMAGE));
SOURCE *src = calloc(1, sizeof(SOURCE));
src->decoder = decoder;
src->fd = fd;
decodeCommon(image, src);
return image;
}
IMAGE *createFromAddr(void *addr, long size) {
AImageDecoder *decoder;
AImageDecoder_createFromBuffer((const void *) addr, size, &decoder);
IMAGE *image = calloc(1, sizeof(IMAGE));
SOURCE *src = calloc(1, sizeof(SOURCE));
src->decoder = decoder;
src->srcBuffer = addr;
decodeCommon(image, src);
return image;
}
IMAGE *create(int32_t width, int32_t height, const void *data) {
IMAGE *plain = calloc(1, sizeof(IMAGE));
void *buffer = NULL;
size_t length = width * height * 4;
buffer = malloc(length);
memcpy(buffer, data, length);
// plain->isAnimated = false; this is unnecessary since we use calloc
plain->width = width;
plain->height = height;
plain->buffer = buffer;
plain->bufferLen = (int) length;
return plain;
}
void render(IMAGE *image, int src_x, int src_y,
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
int width, int height) {
copy_pixels(image->buffer, image->width, image->height, src_x, src_y, dst, dst_w, dst_h, dst_x,
dst_y, width, height);
}
void advance(IMAGE *image) {
if (image->isAnimated) {
AImageDecoder_advanceFrame(image->src->decoder);
if (AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
image->bufferLen) == ANDROID_IMAGE_DECODER_FINISHED) {
AImageDecoder_rewind(image->src->decoder);
AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
image->bufferLen);
}
}
}
static int jniGetFDFromFileDescriptor(JNIEnv *env, jobject fileDescriptor) {
jint fd = -1;
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
if (fdClass != NULL) {
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
fd = (*env)->GetIntField(env, fileDescriptor, fdClassDescriptorFieldID);
}
}
return fd;
}
JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeDecodeFdInt(JNIEnv *env, jclass clazz, jint fd) {
IMAGE *image = createFromFd(fd);
return (jlong) image;
}
JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jobject fd) {
return Java_com_hippo_image_Image_nativeDecodeFdInt(env, clazz,
jniGetFDFromFileDescriptor(env, fd));
}
JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeCreate(JNIEnv *env, jclass clazz, jobject bitmap) {
AndroidBitmapInfo info;
void *pixels = NULL;
void *image = NULL;
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, &pixels);
image = create((int)info.width, (int)info.height, pixels);
AndroidBitmap_unlockPixels(env, bitmap);
return (jlong) image;
}
JNIEXPORT void JNICALL
Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jlong ptr, jint src_x,
jint src_y, jobject dst, jint dst_x, jint dst_y, jint width,
jint height) {
AndroidBitmapInfo info;
void *pixels = NULL;
AndroidBitmap_getInfo(env, dst, &info);
AndroidBitmap_lockPixels(env, dst, &pixels);
render((IMAGE *) ptr, src_x, src_y, pixels, (int)info.width, (int)info.height, dst_x, dst_y, width,
height);
AndroidBitmap_unlockPixels(env, dst);
}
JNIEXPORT void JNICALL
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jlong ptr, jboolean init,
jint src_x, jint src_y, jint width, jint height) {
if (width * height > IMAGE_TILE_MAX_SIZE)
return;
render((IMAGE *) ptr, src_x, src_y, tile_buffer, width, height, 0, 0, width, height);
if (init) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
tile_buffer);
} else {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
tile_buffer);
}
}
JNIEXPORT void JNICALL
Java_com_hippo_image_Image_nativeAdvance(JNIEnv *env, jclass clazz, jlong ptr) {
advance((IMAGE *) ptr);
}
JNIEXPORT jint JNICALL
Java_com_hippo_image_Image_nativeGetDelay(JNIEnv *env, jclass clazz, jlong ptr) {
return ((IMAGE *) ptr)->delay;
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_image_Image_nativeIsOpaque(JNIEnv *env, jclass clazz, jlong ptr) {
return ((IMAGE *) ptr)->alpha;
}
JNIEXPORT void JNICALL
Java_com_hippo_image_Image_nativeRecycle(JNIEnv *env, jclass clazz, jlong ptr) {
recycle((IMAGE *) ptr);
}
JNIEXPORT jlong JNICALL
Java_com_hippo_image_Image_nativeDecodeAddr(JNIEnv *env, jclass clazz, jlong addr) {
Memarea *memarea = (Memarea *) addr;
IMAGE *image = createFromAddr(memarea->buffer, memarea->size);
free(memarea);
return (jlong) image;
}
JNIEXPORT jint JNICALL
Java_com_hippo_image_Image_nativeGetFormat(JNIEnv *env, jclass clazz, jlong native_ptr) {
return ((IMAGE *) native_ptr)->isAnimated;
}
JNIEXPORT jint JNICALL
Java_com_hippo_image_Image_nativeGetWidth(JNIEnv *env, jclass clazz, jlong native_ptr) {
return ((IMAGE *) native_ptr)->width;
}
JNIEXPORT jint JNICALL
Java_com_hippo_image_Image_nativeGetHeight(JNIEnv *env, jclass clazz, jlong native_ptr) {
return ((IMAGE *) native_ptr)->height;
}
@@ -17,22 +17,13 @@
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by Hippo on 12/28/2015.
//
#ifndef IMAGE_IMAGE_H
#define IMAGE_IMAGE_H
#ifndef IMAGE_JAVA_WRAPPER_H
#define IMAGE_JAVA_WRAPPER_H
#include <jni.h>
#include <stdbool.h>
#define IMAGE_TILE_MAX_SIZE (512 * 512)
bool image_onLoad();
bool image_onLoad(JavaVM *vm);
void image_onUnload();
JNIEnv *obtain_env(bool *attach);
void release_env();
#endif // IMAGE_JAVA_WRAPPER_H
#endif /* IMAGE_IMAGE_H */
File renamed without changes.
@@ -21,9 +21,9 @@
#include <archive.h>
#include <lzma.h>
#include <zlib.h>
#include "image/libjpeg-turbo/jconfigint.h"
#include <nettle/config.h>
#include "java_wrapper.h"
#include "image.h"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
@@ -39,6 +39,11 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved) {
image_onUnload();
}
JNIEXPORT jstring JNICALL
Java_com_hippo_Native_getlibarchiveVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, ARCHIVE_VERSION_STRING);
@@ -49,12 +54,12 @@ Java_com_hippo_Native_getliblzmaVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, lzma_version_string());
}
JNIEXPORT jstring JNICALL
Java_com_hippo_Native_getlibjpeg_1turboVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, VERSION);
}
JNIEXPORT jstring JNICALL
Java_com_hippo_Native_getzlibVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, ZLIB_VERSION);
}
JNIEXPORT jstring JNICALL
Java_com_hippo_Native_getnettleVersion(JNIEnv *env, jclass clazz) {
return (*env)->NewStringUTF(env, PACKAGE_STRING);
}
+239
View File
@@ -0,0 +1,239 @@
cmake_minimum_required(VERSION 3.4.1)
project(nettle C)
set(LIBNETTLE_DEFINITIONS
-DHAVE_CONFIG_H)
set(LIBNETTLE_SOURCES
nettle/aes-decrypt-internal.c
nettle/aes-decrypt.c
nettle/aes-decrypt-table.c
nettle/aes128-decrypt.c
nettle/aes192-decrypt.c
nettle/aes256-decrypt.c
nettle/aes-encrypt-internal.c
nettle/aes-encrypt.c
nettle/aes-encrypt-table.c
nettle/aes128-encrypt.c
nettle/aes192-encrypt.c
nettle/aes256-encrypt.c
nettle/aes-invert-internal.c
nettle/aes-set-key-internal.c
nettle/aes-set-encrypt-key.c
nettle/aes-set-decrypt-key.c
nettle/aes128-set-encrypt-key.c
nettle/aes128-set-decrypt-key.c
nettle/aes128-meta.c
nettle/aes192-set-encrypt-key.c
nettle/aes192-set-decrypt-key.c
nettle/aes192-meta.c
nettle/aes256-set-encrypt-key.c
nettle/aes256-set-decrypt-key.c
nettle/aes256-meta.c
nettle/nist-keywrap.c
nettle/arcfour.c
nettle/arcfour-crypt.c
nettle/arctwo.c
nettle/arctwo-meta.c
nettle/blowfish.c
nettle/blowfish-bcrypt.c
nettle/base16-encode.c
nettle/base16-decode.c
nettle/base16-meta.c
nettle/base64-encode.c
nettle/base64-decode.c
nettle/base64-meta.c
nettle/base64url-encode.c
nettle/base64url-decode.c
nettle/base64url-meta.c
nettle/buffer.c
nettle/buffer-init.c
nettle/camellia-crypt-internal.c
nettle/camellia-table.c
nettle/camellia-absorb.c
nettle/camellia-invert-key.c
nettle/camellia128-set-encrypt-key.c
nettle/camellia128-crypt.c
nettle/camellia128-set-decrypt-key.c
nettle/camellia128-meta.c
nettle/camellia192-meta.c
nettle/camellia256-set-encrypt-key.c
nettle/camellia256-crypt.c
nettle/camellia256-set-decrypt-key.c
nettle/camellia256-meta.c
nettle/cast128.c
nettle/cast128-meta.c
nettle/cbc.c
nettle/cbc-aes128-encrypt.c
nettle/cbc-aes192-encrypt.c
nettle/cbc-aes256-encrypt.c
nettle/ccm.c
nettle/ccm-aes128.c
nettle/ccm-aes192.c
nettle/ccm-aes256.c
nettle/cfb.c
nettle/siv-cmac.c
nettle/siv-cmac-aes128.c
nettle/siv-cmac-aes256.c
nettle/cnd-memcpy.c
nettle/chacha-crypt.c
nettle/chacha-core-internal.c
nettle/chacha-poly1305.c
nettle/chacha-poly1305-meta.c
nettle/chacha-set-key.c
nettle/chacha-set-nonce.c
nettle/ctr.c
nettle/ctr16.c
nettle/des.c
nettle/des3.c
nettle/eax.c
nettle/eax-aes128.c
nettle/eax-aes128-meta.c
nettle/ghash-set-key.c
nettle/ghash-update.c
nettle/gcm.c
nettle/gcm-aes.c
nettle/gcm-aes128.c
nettle/gcm-aes128-meta.c
nettle/gcm-aes192.c
nettle/gcm-aes192-meta.c
nettle/gcm-aes256.c
nettle/gcm-aes256-meta.c
nettle/gcm-camellia128.c
nettle/gcm-camellia128-meta.c
nettle/gcm-camellia256.c
nettle/gcm-camellia256-meta.c
nettle/cmac.c
nettle/cmac64.c
nettle/cmac-aes128.c
nettle/cmac-aes256.c
nettle/cmac-des3.c
nettle/cmac-aes128-meta.c
nettle/cmac-aes256-meta.c
nettle/cmac-des3-meta.c
nettle/gost28147.c
nettle/gosthash94.c
nettle/gosthash94-meta.c
nettle/hmac.c
nettle/hmac-gosthash94.c
nettle/hmac-md5.c
nettle/hmac-ripemd160.c
nettle/hmac-sha1.c
nettle/hmac-sha224.c
nettle/hmac-sha256.c
nettle/hmac-sha384.c
nettle/hmac-sha512.c
nettle/hmac-streebog.c
nettle/hmac-sm3.c
nettle/hmac-md5-meta.c
nettle/hmac-ripemd160-meta.c
nettle/hmac-sha1-meta.c
nettle/hmac-sha224-meta.c
nettle/hmac-sha256-meta.c
nettle/hmac-sha384-meta.c
nettle/hmac-sha512-meta.c
nettle/hmac-streebog-meta.c
nettle/hmac-sm3-meta.c
nettle/knuth-lfib.c
nettle/hkdf.c
nettle/md2.c
nettle/md2-meta.c
nettle/md4.c
nettle/md4-meta.c
nettle/md5.c
nettle/md5-compress.c
nettle/md5-compat.c
nettle/md5-meta.c
nettle/memeql-sec.c
nettle/memxor.c
nettle/memxor3.c
nettle/nettle-lookup-hash.c
nettle/nettle-meta-aeads.c
nettle/nettle-meta-armors.c
nettle/nettle-meta-ciphers.c
nettle/nettle-meta-hashes.c
nettle/nettle-meta-macs.c
nettle/pbkdf2.c
nettle/pbkdf2-hmac-gosthash94.c
nettle/pbkdf2-hmac-sha1.c
nettle/pbkdf2-hmac-sha256.c
nettle/pbkdf2-hmac-sha384.c
nettle/pbkdf2-hmac-sha512.c
nettle/poly1305-aes.c
nettle/poly1305-internal.c
nettle/realloc.c
nettle/ripemd160.c
nettle/ripemd160-compress.c
nettle/ripemd160-meta.c
nettle/salsa20-core-internal.c
nettle/salsa20-crypt-internal.c
nettle/salsa20-crypt.c
nettle/salsa20r12-crypt.c
nettle/salsa20-set-key.c
nettle/salsa20-set-nonce.c
nettle/salsa20-128-set-key.c
nettle/salsa20-256-set-key.c
nettle/sha1.c
nettle/sha1-compress.c
nettle/sha1-meta.c
nettle/sha256.c
nettle/sha256-compress.c
nettle/sha224-meta.c
nettle/sha256-meta.c
nettle/sha512.c
nettle/sha512-compress.c
nettle/sha384-meta.c
nettle/sha512-meta.c
nettle/sha512-224-meta.c
nettle/sha512-256-meta.c
nettle/sha3.c
nettle/sha3-permute.c
nettle/sha3-224.c
nettle/sha3-224-meta.c
nettle/sha3-256.c
nettle/sha3-256-meta.c
nettle/sha3-384.c
nettle/sha3-384-meta.c
nettle/sha3-512.c
nettle/sha3-512-meta.c
nettle/shake256.c
nettle/sm3.c
nettle/sm3-meta.c
nettle/serpent-set-key.c
nettle/serpent-encrypt.c
nettle/serpent-decrypt.c
nettle/serpent-meta.c
nettle/streebog.c
nettle/streebog-meta.c
nettle/twofish.c
nettle/twofish-meta.c
nettle/umac-nh.c
nettle/umac-nh-n.c
nettle/umac-l2.c
nettle/umac-l3.c
nettle/umac-poly64.c
nettle/umac-poly128.c
nettle/umac-set-key.c
nettle/umac32.c
nettle/umac64.c
nettle/umac96.c
nettle/umac128.c
nettle/version.c
nettle/write-be32.c
nettle/write-le32.c
nettle/write-le64.c
nettle/yarrow256.c
nettle/yarrow_key_event.c
nettle/xts.c
nettle/xts-aes128.c
nettle/xts-aes256.c
)
set(LIBNETTLE_INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/nettle
)
add_library(nettle STATIC ${LIBNETTLE_SOURCES})
target_include_directories(nettle PUBLIC ${LIBNETTLE_INCLUDES})
target_compile_definitions(nettle PRIVATE ${LIBNETTLE_DEFINITIONS})
+275
View File
@@ -0,0 +1,275 @@
/* config.h. Generated from config.h.in by configure. */
/* config.h.in. Generated from configure.ac by autoheader. */
/* Define if building universal (internal helper macro) */
/* #undef AC_APPLE_UNIVERSAL_BUILD */
/* Define to 1 if using 'alloca.c'. */
/* #undef C_ALLOCA */
/* Define to 1 if you have 'alloca', as a function or macro. */
#define HAVE_ALLOCA 1
/* Define to 1 if <alloca.h> works. */
#define HAVE_ALLOCA_H 1
/* Define if __builtin_bswap64 is available */
#define HAVE_BUILTIN_BSWAP64 1
/* Define if clock_gettime is available */
#define HAVE_CLOCK_GETTIME 1
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1
/* Define if fcntl file locking is available */
#define HAVE_FCNTL_LOCKING 1
/* Define if the compiler understands __attribute__ */
#define HAVE_GCC_ATTRIBUTE 1
/* Define to 1 if you have the `getline' function. */
#define HAVE_GETLINE 1
/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1
/* Define to 1 if you have dlopen (with -ldl). */
#define HAVE_LIBDL 1
/* Define to 1 if you have the `gmp' library (-lgmp). */
/* #undef HAVE_LIBGMP */
/* Define if compiler and linker supports __attribute__ ifunc */
#define HAVE_LINK_IFUNC 1
/* Define to 1 if you have the <malloc.h> header file. */
#define HAVE_MALLOC_H 1
/* Define to 1 each of the following for which a native (ie. CPU specific)
implementation of the corresponding routine exists. */
/* #undef HAVE_NATIVE_memxor3 */
/* #undef HAVE_NATIVE_aes_decrypt */
/* #undef HAVE_NATIVE_aes_encrypt */
/* #undef HAVE_NATIVE_aes128_decrypt */
/* #undef HAVE_NATIVE_aes128_encrypt */
/* #undef HAVE_NATIVE_aes128_invert_key */
/* #undef HAVE_NATIVE_aes128_set_decrypt_key */
/* #undef HAVE_NATIVE_aes128_set_encrypt_key */
/* #undef HAVE_NATIVE_aes192_decrypt */
/* #undef HAVE_NATIVE_aes192_encrypt */
/* #undef HAVE_NATIVE_aes192_invert_key */
/* #undef HAVE_NATIVE_aes192_set_decrypt_key */
/* #undef HAVE_NATIVE_aes192_set_encrypt_key */
/* #undef HAVE_NATIVE_aes256_decrypt */
/* #undef HAVE_NATIVE_aes256_encrypt */
/* #undef HAVE_NATIVE_aes256_invert_key */
/* #undef HAVE_NATIVE_aes256_set_decrypt_key */
/* #undef HAVE_NATIVE_aes256_set_encrypt_key */
/* #undef HAVE_NATIVE_cbc_aes128_encrypt */
/* #undef HAVE_NATIVE_cbc_aes192_encrypt */
/* #undef HAVE_NATIVE_cbc_aes256_encrypt */
/* #undef HAVE_NATIVE_chacha_core */
/* #undef HAVE_NATIVE_chacha_2core */
/* #undef HAVE_NATIVE_chacha_3core */
/* #undef HAVE_NATIVE_chacha_4core */
/* #undef HAVE_NATIVE_fat_chacha_2core */
/* #undef HAVE_NATIVE_fat_chacha_3core */
/* #undef HAVE_NATIVE_fat_chacha_4core */
/* #undef HAVE_NATIVE_ecc_curve25519_modp */
/* #undef HAVE_NATIVE_ecc_curve448_modp */
/* #undef HAVE_NATIVE_ecc_secp192r1_modp */
/* #undef HAVE_NATIVE_ecc_secp192r1_redc */
/* #undef HAVE_NATIVE_ecc_secp224r1_modp */
/* #undef HAVE_NATIVE_ecc_secp224r1_redc */
/* #undef HAVE_NATIVE_ecc_secp256r1_modp */
/* #undef HAVE_NATIVE_ecc_secp256r1_redc */
/* #undef HAVE_NATIVE_ecc_secp384r1_modp */
/* #undef HAVE_NATIVE_ecc_secp384r1_redc */
/* #undef HAVE_NATIVE_ecc_secp521r1_modp */
/* #undef HAVE_NATIVE_ecc_secp521r1_redc */
/* #undef HAVE_NATIVE_ghash_set_key */
/* #undef HAVE_NATIVE_ghash_update */
/* #undef HAVE_NATIVE_salsa20_core */
/* #undef HAVE_NATIVE_salsa20_2core */
/* #undef HAVE_NATIVE_fat_salsa20_2core */
/* #undef HAVE_NATIVE_sha1_compress */
/* #undef HAVE_NATIVE_sha256_compress */
/* #undef HAVE_NATIVE_sha512_compress */
/* #undef HAVE_NATIVE_sha3_permute */
/* #undef HAVE_NATIVE_umac_nh */
/* #undef HAVE_NATIVE_umac_nh_n */
/* Define to 1 if you have the <openssl/ecdsa.h> header file. */
/* #undef HAVE_OPENSSL_ECDSA_H */
/* Define to 1 if you have the <openssl/evp.h> header file. */
/* #undef HAVE_OPENSSL_EVP_H */
/* Define to 1 if you have the `secure_getenv' function. */
/* #undef HAVE_SECURE_GETENV */
/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1
/* Define to 1 if you have the <stdio.h> header file. */
#define HAVE_STDIO_H 1
/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the `strerror' function. */
#define HAVE_STRERROR 1
/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1
/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1
/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1
/* Define to 1 if you have the <sys/time.h> header file. */
#define HAVE_SYS_TIME_H 1
/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1
/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1
/* Define to 1 if you have the <valgrind/memcheck.h> header file. */
/* #undef HAVE_VALGRIND_MEMCHECK_H */
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "nettle-bugs@lists.lysator.liu.se"
/* Define to the full name of this package. */
#define PACKAGE_NAME "nettle"
/* Define to the full name and version of this package. */
#define PACKAGE_STRING "nettle 3.8.1"
/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "nettle"
/* Define to the home page for this package. */
#define PACKAGE_URL ""
/* Define to the version of this package. */
#define PACKAGE_VERSION "3.8.1"
/* The size of `long', as computed by sizeof. */
#define SIZEOF_LONG __SIZEOF_LONG__
/* The size of `size_t', as computed by sizeof. */
#define SIZEOF_SIZE_T __SIZEOF_SIZE_T__
/* If using the C implementation of alloca, define if you know the
direction of stack growth for your system; otherwise it will be
automatically deduced at runtime.
STACK_DIRECTION > 0 => grows toward higher addresses
STACK_DIRECTION < 0 => grows toward lower addresses
STACK_DIRECTION = 0 => direction of growth unknown */
/* #undef STACK_DIRECTION */
/* Define to 1 if all of the C90 standard headers exist (not just the ones
required in a freestanding environment). This macro is provided for
backward compatibility; new code need not use it. */
#define STDC_HEADERS 1
/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. This
macro is obsolete. */
#define TIME_WITH_SYS_TIME 1
/* Defined if public key features are enabled */
/* #undef WITH_HOGWEED */
/* Define if you have openssl's libcrypto (used for benchmarking) */
/* #undef WITH_OPENSSL */
/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
significant byte first (like Motorola and SPARC, unlike Intel). */
#if defined AC_APPLE_UNIVERSAL_BUILD
# if defined __BIG_ENDIAN__
# define WORDS_BIGENDIAN 1
# endif
#else
# ifndef WORDS_BIGENDIAN
/* # undef WORDS_BIGENDIAN */
# endif
#endif
/* Define to empty if `const' does not conform to ANSI C. */
/* #undef const */
/* Define to `int' if <sys/types.h> doesn't define. */
/* #undef gid_t */
/* Define to `__inline__' or `__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */
#ifndef __cplusplus
/* #undef inline */
#endif
/* Define to `unsigned int' if <sys/types.h> does not define. */
/* #undef size_t */
/* Define to `int' if <sys/types.h> doesn't define. */
/* #undef uid_t */
/* AIX requires this to be the first thing in the file. */
#ifndef __GNUC__
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
# ifdef _AIX
#pragma alloca
# else
# ifndef alloca /* predefined by HP cc +Olibcalls */
char *alloca ();
# endif
# endif
/* Needed for alloca on windows */
# if HAVE_MALLOC_H
# include <malloc.h>
# endif
# endif
#else /* defined __GNUC__ */
# if HAVE_ALLOCA_H
# include <alloca.h>
# else
/* Needed for alloca on windows, also with gcc */
# if HAVE_MALLOC_H
# include <malloc.h>
# endif
# endif
#endif
#if HAVE_STRERROR
#define STRERROR strerror
#else
#define STRERROR(x) (sys_errlist[x])
#endif
#if __GNUC__ && HAVE_GCC_ATTRIBUTE
# define NORETURN __attribute__ ((__noreturn__))
# define PRINTF_STYLE(f, a) __attribute__ ((__format__ (__printf__, f, a)))
# define UNUSED __attribute__ ((__unused__))
#else
# define NORETURN
# define PRINTF_STYLE(f, a)
# define UNUSED
#endif
#if defined(__x86_64__) || defined(__arch64__)
# define HAVE_NATIVE_64_BIT 1
#else
/* Needs include of <limits.h> before use. */
# define HAVE_NATIVE_64_BIT (SIZEOF_LONG * CHAR_BIT >= 64)
#endif
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+64
View File
@@ -0,0 +1,64 @@
/* version.h
Information about library version.
Copyright (C) 2015 Red Hat, Inc.
Copyright (C) 2015 Niels Möller
This file is part of GNU Nettle.
GNU Nettle is free software: you can redistribute it and/or
modify it under the terms of either:
* the GNU Lesser General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your
option) any later version.
or
* the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your
option) any later version.
or both in parallel, as here.
GNU Nettle 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 copies of the GNU General Public License and
the GNU Lesser General Public License along with this program. If
not, see http://www.gnu.org/licenses/.
*/
#ifndef NETTLE_VERSION_H_INCLUDED
#define NETTLE_VERSION_H_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif
/* Individual version numbers in decimal */
#define NETTLE_VERSION_MAJOR 3
#define NETTLE_VERSION_MINOR 8
#define NETTLE_USE_MINI_GMP 0
/* We need a preprocessor constant for GMP_NUMB_BITS, simply using
sizeof(mp_limb_t) * CHAR_BIT is not good enough. */
#if NETTLE_USE_MINI_GMP
# define GMP_NUMB_BITS n/a
#endif
int
nettle_version_major (void);
int
nettle_version_minor (void);
#ifdef __cplusplus
}
#endif
#endif /* NETTLE_VERSION_H_INCLUDED */
@@ -28,7 +28,7 @@ public class Native {
public static native String getliblzmaVersion();
public static native String getlibjpeg_turboVersion();
public static native String getzlibVersion();
public static native String getnettleVersion();
}
@@ -28,8 +28,6 @@ import java.io.FileDescriptor;
public class UriArchiveAccessor {
ParcelFileDescriptor pfd;
FileDescriptor fd;
long archiveAddr;
Context ctx;
Uri uri;
@@ -40,14 +38,12 @@ public class UriArchiveAccessor {
public int open() throws Exception {
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
fd = pfd.getFileDescriptor();
archiveAddr = Os.mmap(0, pfd.getStatSize(), OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, 0);
return openArchive(archiveAddr, pfd.getStatSize());
return openArchive(pfd.getFd(), pfd.getStatSize());
}
private native int openArchive(long addr, long size);
private native int openArchive(int fd, long size);
public native void extracttoOutputStream(int index, int fd);
public native long extracttoOutputStream(int index);
public native boolean needPassword();
@@ -57,8 +53,6 @@ public class UriArchiveAccessor {
public void close() throws Exception {
closeArchive();
Os.munmap(archiveAddr, pfd.getStatSize());
Os.close(fd);
pfd.close();
}
}
@@ -167,6 +167,7 @@ public final class DiskLruCache implements Closeable {
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0;
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
@@ -175,21 +176,7 @@ public final class DiskLruCache implements Closeable {
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
@@ -244,7 +231,21 @@ public final class DiskLruCache implements Closeable {
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
@@ -107,7 +107,7 @@ public class EhApplication extends SceneApplication {
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
private static final long DEBUG_PRINT_INTERVAL = 3000L;
public static boolean locked = false;
public static boolean locked = true;
private static EhApplication instance;
@@ -337,10 +337,6 @@ public class EhApplication extends SceneApplication {
EhEngine.initialize();
BitmapUtils.initialize(this);
if (EhDB.needMerge()) {
EhDB.mergeOldDB(this);
}
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
+98 -384
View File
@@ -17,27 +17,24 @@
package com.hippo.ehviewer;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.paging.PagingSource;
import androidx.room.Room;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.dao.DaoMaster;
import com.hippo.ehviewer.dao.DaoSession;
import com.hippo.ehviewer.dao.BasicDao;
import com.hippo.ehviewer.dao.DownloadDirname;
import com.hippo.ehviewer.dao.DownloadDirnameDao;
import com.hippo.ehviewer.dao.DownloadInfo;
import com.hippo.ehviewer.dao.DownloadLabel;
import com.hippo.ehviewer.dao.DownloadLabelDao;
import com.hippo.ehviewer.dao.DownloadsDao;
import com.hippo.ehviewer.dao.EhDatabase;
import com.hippo.ehviewer.dao.Filter;
import com.hippo.ehviewer.dao.FilterDao;
import com.hippo.ehviewer.dao.HistoryDao;
import com.hippo.ehviewer.dao.HistoryInfo;
import com.hippo.ehviewer.dao.LocalFavoriteInfo;
@@ -46,14 +43,8 @@ import com.hippo.ehviewer.dao.QuickSearch;
import com.hippo.ehviewer.dao.QuickSearchDao;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.SqlUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.ObjectUtils;
import com.hippo.yorozuya.collect.SparseJLArray;
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.query.CloseableListIterator;
import org.greenrobot.greendao.query.LazyList;
import java.io.File;
import java.io.FileInputStream;
@@ -65,15 +56,9 @@ import java.util.ArrayList;
import java.util.List;
public class EhDB {
private static final int CUR_DB_VER = 4;
private static final String TAG = EhDB.class.getSimpleName();
private static final int MAX_HISTORY_COUNT = 200;
private static DaoSession sDaoSession;
private static boolean sHasOldDB;
private static boolean sNewDB;
private static EhDatabase db;
private static void upgradeDB(SQLiteDatabase db, int oldVersion) {
switch (oldVersion) {
@@ -115,219 +100,12 @@ public class EhDB {
}
public static void initialize(Context context) {
sHasOldDB = context.getDatabasePath("data").exists();
DBOpenHelper helper = new DBOpenHelper(
context.getApplicationContext(), "eh.db", null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
sDaoSession = daoMaster.newSession();
}
public static boolean needMerge() {
return sNewDB && sHasOldDB;
}
public static void mergeOldDB(Context context) {
sNewDB = false;
OldDBHelper oldDBHelper = new OldDBHelper(context);
SQLiteDatabase oldDB;
try {
oldDB = oldDBHelper.getReadableDatabase();
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
return;
}
// Get GalleryInfo list
SparseJLArray<GalleryInfo> map = new SparseJLArray<>();
try {
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_GALLERY, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
GalleryInfo gi = new GalleryInfo();
gi.gid = cursor.getInt(0);
gi.token = cursor.getString(1);
gi.title = cursor.getString(2);
gi.posted = cursor.getString(3);
gi.category = cursor.getInt(4);
gi.thumb = cursor.getString(5);
gi.uploader = cursor.getString(6);
try {
// In 0.6.x version, NaN is stored
gi.rating = cursor.getFloat(7);
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
gi.rating = -1.0f;
}
map.put(gi.gid, gi);
cursor.moveToNext();
}
}
cursor.close();
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
// Merge local favorites
try {
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_LOCAL_FAVOURITE, null);
if (cursor != null) {
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
if (cursor.moveToFirst()) {
long i = 0L;
while (!cursor.isAfterLast()) {
// Get GalleryInfo first
long gid = cursor.getInt(0);
GalleryInfo gi = map.get(gid);
if (gi == null) {
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
cursor.moveToNext();
continue;
}
LocalFavoriteInfo info = new LocalFavoriteInfo(gi);
info.setTime(i);
dao.insert(info);
cursor.moveToNext();
i++;
}
}
cursor.close();
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
// Merge quick search
try {
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_TAG, null);
if (cursor != null) {
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
QuickSearch quickSearch = new QuickSearch();
int mode = cursor.getInt(2);
String search = cursor.getString(4);
String tag = cursor.getString(7);
if (mode == ListUrlBuilder.MODE_UPLOADER && search != null &&
search.startsWith("uploader:")) {
search = search.substring("uploader:".length());
}
quickSearch.setTime(cursor.getInt(0));
quickSearch.setName(cursor.getString(1));
quickSearch.setMode(mode);
quickSearch.setCategory(cursor.getInt(3));
quickSearch.setKeyword(mode == ListUrlBuilder.MODE_TAG ? tag : search);
quickSearch.setAdvanceSearch(cursor.getInt(5));
quickSearch.setMinRating(cursor.getInt(6));
dao.insert(quickSearch);
cursor.moveToNext();
}
}
cursor.close();
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
// Merge download info
try {
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_DOWNLOAD, null);
if (cursor != null) {
DownloadsDao dao = sDaoSession.getDownloadsDao();
if (cursor.moveToFirst()) {
long i = 0L;
while (!cursor.isAfterLast()) {
// Get GalleryInfo first
long gid = cursor.getInt(0);
GalleryInfo gi = map.get(gid);
if (gi == null) {
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
cursor.moveToNext();
continue;
}
DownloadInfo info = new DownloadInfo(gi);
int state = cursor.getInt(2);
int legacy = cursor.getInt(3);
if (state == DownloadInfo.STATE_FINISH && legacy > 0) {
state = DownloadInfo.STATE_FAILED;
}
info.setState(state);
info.setLegacy(legacy);
if (cursor.getColumnCount() == 5) {
info.setTime(cursor.getLong(4));
} else {
info.setTime(i);
}
dao.insert(info);
cursor.moveToNext();
i++;
}
}
cursor.close();
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
try {
// Merge history info
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_HISTORY, null);
if (cursor != null) {
HistoryDao dao = sDaoSession.getHistoryDao();
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
// Get GalleryInfo first
long gid = cursor.getInt(0);
GalleryInfo gi = map.get(gid);
if (gi == null) {
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
cursor.moveToNext();
continue;
}
HistoryInfo info = new HistoryInfo(gi);
info.setMode(cursor.getInt(1));
info.setTime(cursor.getLong(2));
dao.insert(info);
cursor.moveToNext();
}
}
cursor.close();
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
try {
oldDBHelper.close();
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
db = Room.databaseBuilder(context, EhDatabase.class, "eh.db").allowMainThreadQueries().build();
}
public static synchronized List<DownloadInfo> getAllDownloadInfo() {
DownloadsDao dao = sDaoSession.getDownloadsDao();
List<DownloadInfo> list = dao.queryBuilder().orderDesc(DownloadsDao.Properties.Time).list();
DownloadsDao dao = db.downloadsDao();
List<DownloadInfo> list = dao.list();
// Fix state
for (DownloadInfo info : list) {
if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) {
@@ -339,7 +117,7 @@ public class EhDB {
// Insert or update
public static synchronized void putDownloadInfo(DownloadInfo downloadInfo) {
DownloadsDao dao = sDaoSession.getDownloadsDao();
DownloadsDao dao = db.downloadsDao();
if (null != dao.load(downloadInfo.gid)) {
// Update
dao.update(downloadInfo);
@@ -349,13 +127,13 @@ public class EhDB {
}
}
public static synchronized void removeDownloadInfo(long gid) {
sDaoSession.getDownloadsDao().deleteByKey(gid);
public static synchronized void removeDownloadInfo(DownloadInfo downloadInfo) {
db.downloadsDao().delete(downloadInfo);
}
@Nullable
public static synchronized String getDownloadDirname(long gid) {
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
DownloadDirnameDao dao = db.downloadDirnameDao();
DownloadDirname raw = dao.load(gid);
if (raw != null) {
return raw.getDirname();
@@ -368,7 +146,7 @@ public class EhDB {
* Insert or update
*/
public static synchronized void putDownloadDirname(long gid, String dirname) {
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
DownloadDirnameDao dao = db.downloadDirnameDao();
DownloadDirname raw = dao.load(gid);
if (raw != null) { // Update
raw.setDirname(dirname);
@@ -382,23 +160,23 @@ public class EhDB {
}
public static synchronized void removeDownloadDirname(long gid) {
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
DownloadDirnameDao dao = db.downloadDirnameDao();
dao.deleteByKey(gid);
}
public static synchronized void clearDownloadDirname() {
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
DownloadDirnameDao dao = db.downloadDirnameDao();
dao.deleteAll();
}
@NonNull
public static synchronized List<DownloadLabel> getAllDownloadLabelList() {
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
return dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time).list();
DownloadLabelDao dao = db.downloadLabelDao();
return dao.list();
}
public static synchronized DownloadLabel addDownloadLabel(String label) {
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
DownloadLabelDao dao = db.downloadLabelDao();
DownloadLabel raw = new DownloadLabel();
raw.setLabel(label);
raw.setTime(System.currentTimeMillis());
@@ -409,13 +187,13 @@ public class EhDB {
public static synchronized DownloadLabel addDownloadLabel(DownloadLabel raw) {
// Reset id
raw.setId(null);
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
DownloadLabelDao dao = db.downloadLabelDao();
raw.setId(dao.insert(raw));
return raw;
}
public static synchronized void updateDownloadLabel(DownloadLabel raw) {
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
DownloadLabelDao dao = db.downloadLabelDao();
dao.update(raw);
}
@@ -428,9 +206,8 @@ public class EhDB {
int offset = reverse ? toPosition : fromPosition;
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
List<DownloadLabel> list = dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time)
.offset(offset).limit(limit).list();
DownloadLabelDao dao = db.downloadLabelDao();
List<DownloadLabel> list = dao.list(offset, limit);
int step = reverse ? 1 : -1;
int start = reverse ? limit - 1 : 0;
@@ -441,50 +218,44 @@ public class EhDB {
}
list.get(start).setTime(toTime);
dao.updateInTx(list);
dao.update(list);
}
public static synchronized void removeDownloadLabel(DownloadLabel raw) {
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
DownloadLabelDao dao = db.downloadLabelDao();
dao.delete(raw);
}
public static synchronized List<GalleryInfo> getAllLocalFavorites() {
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time).list();
List<GalleryInfo> result = new ArrayList<>();
result.addAll(list);
return result;
LocalFavoritesDao dao = db.localFavoritesDao();
List<LocalFavoriteInfo> list = dao.list();
return new ArrayList<>(list);
}
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
query = SqlUtils.sqlEscapeString("%" + query + "%");
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time)
.where(LocalFavoritesDao.Properties.Title.like(query)).list();
List<GalleryInfo> result = new ArrayList<>();
result.addAll(list);
return result;
LocalFavoritesDao dao = db.localFavoritesDao();
List<LocalFavoriteInfo> list = dao.list();
return new ArrayList<>(list);
}
public static synchronized void removeLocalFavorites(long gid) {
sDaoSession.getLocalFavoritesDao().deleteByKey(gid);
db.localFavoritesDao().deleteByKey(gid);
}
public static synchronized void removeLocalFavorites(long[] gidArray) {
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
LocalFavoritesDao dao = db.localFavoritesDao();
for (long gid : gidArray) {
dao.deleteByKey(gid);
}
}
public static synchronized boolean containLocalFavorites(long gid) {
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
LocalFavoritesDao dao = db.localFavoritesDao();
return null != dao.load(gid);
}
public static synchronized void putLocalFavorites(GalleryInfo galleryInfo) {
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
LocalFavoritesDao dao = db.localFavoritesDao();
if (null == dao.load(galleryInfo.gid)) {
LocalFavoriteInfo info;
if (galleryInfo instanceof LocalFavoriteInfo) {
@@ -504,31 +275,31 @@ public class EhDB {
}
public static synchronized List<QuickSearch> getAllQuickSearch() {
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
return dao.queryBuilder().orderAsc(QuickSearchDao.Properties.Time).list();
QuickSearchDao dao = db.quickSearchDao();
return dao.list();
}
public static synchronized void insertQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
QuickSearchDao dao = db.quickSearchDao();
quickSearch.id = null;
quickSearch.time = System.currentTimeMillis();
quickSearch.id = dao.insert(quickSearch);
}
public static synchronized void importQuickSearch(List<QuickSearch> quickSearchList) {
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
QuickSearchDao dao = db.quickSearchDao();
for (QuickSearch quickSearch : quickSearchList) {
dao.insert(quickSearch);
}
}
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
QuickSearchDao dao = db.quickSearchDao();
dao.update(quickSearch);
}
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
QuickSearchDao dao = db.quickSearchDao();
dao.delete(quickSearch);
}
@@ -541,9 +312,8 @@ public class EhDB {
int offset = reverse ? toPosition : fromPosition;
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
List<QuickSearch> list = dao.queryBuilder().orderAsc(QuickSearchDao.Properties.Time)
.offset(offset).limit(limit).list();
QuickSearchDao dao = db.quickSearchDao();
List<QuickSearch> list = dao.list(offset, limit);
int step = reverse ? 1 : -1;
int start = reverse ? limit - 1 : 0;
@@ -554,15 +324,19 @@ public class EhDB {
}
list.get(start).setTime(toTime);
dao.updateInTx(list);
dao.update(list);
}
public static synchronized LazyList<HistoryInfo> getHistoryLazyList() {
return sDaoSession.getHistoryDao().queryBuilder().orderDesc(HistoryDao.Properties.Time).listLazy();
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 = sDaoSession.getHistoryDao();
HistoryDao dao = db.historyDao();
HistoryInfo info = dao.load(galleryInfo.gid);
if (null != info) {
// Update time
@@ -573,49 +347,42 @@ public class EhDB {
info = new HistoryInfo(galleryInfo);
info.time = System.currentTimeMillis();
dao.insert(info);
List<HistoryInfo> list = dao.queryBuilder().orderDesc(HistoryDao.Properties.Time)
.limit(-1).offset(MAX_HISTORY_COUNT).list();
dao.deleteInTx(list);
}
}
public static synchronized void putHistoryInfo(List<HistoryInfo> historyInfoList) {
HistoryDao dao = sDaoSession.getHistoryDao();
HistoryDao dao = db.historyDao();
for (HistoryInfo info : historyInfoList) {
if (null == dao.load(info.gid)) {
dao.insert(info);
}
}
List<HistoryInfo> list = dao.queryBuilder().orderDesc(HistoryDao.Properties.Time)
.limit(-1).offset(MAX_HISTORY_COUNT).list();
dao.deleteInTx(list);
}
public static synchronized void deleteHistoryInfo(HistoryInfo info) {
HistoryDao dao = sDaoSession.getHistoryDao();
HistoryDao dao = db.historyDao();
dao.delete(info);
}
public static synchronized void clearHistoryInfo() {
HistoryDao dao = sDaoSession.getHistoryDao();
HistoryDao dao = db.historyDao();
dao.deleteAll();
}
public static synchronized List<Filter> getAllFilter() {
return sDaoSession.getFilterDao().queryBuilder().list();
return db.filterDao().list();
}
public static synchronized boolean addFilter(Filter filter) {
Filter existFilter;
try {
existFilter = sDaoSession.getFilterDao().queryBuilder().where(FilterDao.Properties.Text.eq(filter.text), FilterDao.Properties.Mode.eq(filter.mode)).unique();
existFilter = db.filterDao().load(filter.text, filter.mode);
} catch (Exception e) {
existFilter = null;
}
if (existFilter == null) {
filter.setId(null);
filter.setId(sDaoSession.getFilterDao().insert(filter));
filter.setId(db.filterDao().insert(filter));
return true;
} else {
return false;
@@ -623,23 +390,19 @@ public class EhDB {
}
public static synchronized void deleteFilter(Filter filter) {
sDaoSession.getFilterDao().delete(filter);
db.filterDao().delete(filter);
}
public static synchronized void triggerFilter(Filter filter) {
filter.setEnable(!filter.enable);
sDaoSession.getFilterDao().update(filter);
db.filterDao().update(filter);
}
private static <T> boolean copyDao(AbstractDao<T, ?> from, AbstractDao<T, ?> to) {
try (CloseableListIterator<T> iterator = from.queryBuilder().listIterator()) {
while (iterator.hasNext()) {
to.insert(iterator.next());
}
} catch (IOException e) {
return false;
}
return true;
private static <T> boolean copyDao(BasicDao<T> from, BasicDao<T> to) {
List<T> list = from.list();
for (T item : list)
to.insert(item);
return false;
}
public static synchronized boolean exportDB(Context context, Uri uri) {
@@ -647,31 +410,24 @@ public class EhDB {
// Delete old export db
context.deleteDatabase(ehExportName);
DBOpenHelper helper = new DBOpenHelper(context.getApplicationContext(), ehExportName, null);
EhDatabase newDb = Room.databaseBuilder(context, EhDatabase.class, ehExportName).allowMainThreadQueries().build();
try {
// Copy data to a export db
try (SQLiteDatabase db = helper.getWritableDatabase()) {
DaoMaster daoMaster = new DaoMaster(db);
DaoSession exportSession = daoMaster.newSession();
if (!copyDao(sDaoSession.getDownloadsDao(), exportSession.getDownloadsDao()))
return false;
if (!copyDao(sDaoSession.getDownloadLabelDao(), exportSession.getDownloadLabelDao()))
return false;
if (!copyDao(sDaoSession.getDownloadDirnameDao(), exportSession.getDownloadDirnameDao()))
return false;
if (!copyDao(sDaoSession.getHistoryDao(), exportSession.getHistoryDao()))
return false;
if (!copyDao(sDaoSession.getQuickSearchDao(), exportSession.getQuickSearchDao()))
return false;
if (!copyDao(sDaoSession.getLocalFavoritesDao(), exportSession.getLocalFavoritesDao()))
return false;
if (!copyDao(sDaoSession.getBookmarksDao(), exportSession.getBookmarksDao()))
return false;
if (!copyDao(sDaoSession.getFilterDao(), exportSession.getFilterDao()))
return false;
}
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;
// Copy export db to data dir
File dbFile = context.getDatabasePath(ehExportName);
@@ -718,42 +474,45 @@ public class EhDB {
inputStream.close();
outputStream.close();
SQLiteDatabase db = SQLiteDatabase.openDatabase(
SQLiteDatabase oldDB = SQLiteDatabase.openDatabase(
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
int newVersion = DaoMaster.SCHEMA_VERSION;
int oldVersion = db.getVersion();
int newVersion = CUR_DB_VER;
int oldVersion = oldDB.getVersion();
if (oldVersion < newVersion) {
upgradeDB(db, oldVersion);
db.setVersion(newVersion);
upgradeDB(oldDB, oldVersion);
oldDB.setVersion(newVersion);
} else if (oldVersion > newVersion) {
return context.getString(R.string.cant_read_the_file);
}
DaoMaster daoMaster = new DaoMaster(db);
DaoSession session = daoMaster.newSession();
String tmpDBName = "tmp.db";
context.deleteDatabase(tmpDBName);
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
.createFromFile(file).allowMainThreadQueries().build();
// Downloads
DownloadManager manager = EhApplication.getDownloadManager(context);
List<DownloadInfo> downloadInfoList = session.getDownloadsDao().queryBuilder().list();
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
manager.addDownload(downloadInfoList, false);
// Download label
List<DownloadLabel> downloadLabelList = session.getDownloadLabelDao().queryBuilder().list();
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
manager.addDownloadLabel(downloadLabelList);
// Download dirname
List<DownloadDirname> downloadDirnameList = session.getDownloadDirnameDao().queryBuilder().list();
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
for (DownloadDirname dirname : downloadDirnameList) {
putDownloadDirname(dirname.getGid(), dirname.getDirname());
}
// History
List<HistoryInfo> historyInfoList = session.getHistoryDao().queryBuilder().list();
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
putHistoryInfo(historyInfoList);
// QuickSearch
List<QuickSearch> quickSearchList = session.getQuickSearchDao().queryBuilder().list();
List<QuickSearch> currentQuickSearchList = sDaoSession.getQuickSearchDao().queryBuilder().list();
List<QuickSearch> quickSearchList = oldRoomDatabase.quickSearchDao().list();
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
List<QuickSearch> importList = new ArrayList<>();
for (QuickSearch quickSearch : quickSearchList) {
String name = quickSearch.name;
@@ -772,17 +531,14 @@ public class EhDB {
importQuickSearch(importList);
// LocalFavorites
List<LocalFavoriteInfo> localFavoriteInfoList = session.getLocalFavoritesDao().queryBuilder().list();
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
for (LocalFavoriteInfo info : localFavoriteInfoList) {
putLocalFavorites(info);
}
// Bookmarks
// TODO
// Filter
List<Filter> filterList = session.getFilterDao().queryBuilder().list();
List<Filter> currentFilterList = sDaoSession.getFilterDao().queryBuilder().list();
List<Filter> filterList = oldRoomDatabase.filterDao().list();
List<Filter> currentFilterList = db.filterDao().list();
for (Filter filter : filterList) {
if (!currentFilterList.contains(filter)) {
addFilter(filter);
@@ -796,46 +552,4 @@ public class EhDB {
return context.getString(R.string.cant_read_the_file);
}
}
private static class DBOpenHelper extends DaoMaster.OpenHelper {
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onCreate(SQLiteDatabase db) {
super.onCreate(db);
sNewDB = true;
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
upgradeDB(db, oldVersion);
}
}
private static class OldDBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "data";
private static final int VERSION = 4;
private static final String TABLE_GALLERY = "gallery";
private static final String TABLE_LOCAL_FAVOURITE = "local_favourite";
private static final String TABLE_TAG = "tag";
private static final String TABLE_DOWNLOAD = "download";
private static final String TABLE_HISTORY = "history";
public OldDBHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}
@@ -23,6 +23,7 @@ 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> {
@@ -34,7 +35,8 @@ public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
try {
isPipe.obtain();
return ImageBitmap.decode(isPipe.open());
FileInputStream is = (FileInputStream) isPipe.open();
return ImageBitmap.decode(is.getFD());
} catch (OutOfMemoryError | IOException e) {
return null;
} finally {
@@ -20,6 +20,9 @@ import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
import java.util.regex.Pattern;
@@ -108,30 +111,52 @@ public class GalleryInfo implements Parcelable {
return new GalleryInfo[size];
}
};
@PrimaryKey
@ColumnInfo(name = "GID")
public long gid;
@ColumnInfo(name = "TOKEN")
public String token;
@ColumnInfo(name = "TITLE")
public String title;
@ColumnInfo(name = "TITLE_JPN")
public String titleJpn;
@ColumnInfo(name = "THUMB")
public String thumb;
@ColumnInfo(name = "CATEGORY")
public int category;
@ColumnInfo(name = "POSTED")
public String posted;
@ColumnInfo(name = "UPLOADER")
public String uploader;
@Ignore
public boolean disowned;
@ColumnInfo(name = "RATING")
public float rating;
@Ignore
public boolean rated;
@Nullable
@Ignore
public String[] simpleTags;
@Ignore
public int pages;
@Ignore
public int thumbWidth;
@Ignore
public int thumbHeight;
@Ignore
public int spanSize;
@Ignore
public int spanIndex;
@Ignore
public int spanGroupIndex;
/**
* language from title
*/
@ColumnInfo(name = "SIMPLE_LANGUAGE")
public String simpleLanguage;
@Ignore
public int favoriteSlot = -2;
@Ignore
public String favoriteName;
public GalleryInfo() {
@@ -170,6 +195,7 @@ public class GalleryInfo implements Parcelable {
}
private void generateSLangFromTags() {
assert simpleTags != null;
for (String tag : simpleTags) {
for (int i = 0; i < S_LANGS.length; i++) {
if (S_LANG_TAGS[i].equals(tag)) {
@@ -0,0 +1,8 @@
package com.hippo.ehviewer.dao;
import java.util.List;
public interface BasicDao<T> {
List<T> list();
long insert(T t);
}
@@ -0,0 +1,160 @@
package com.hippo.ehviewer.dao;
import android.os.Parcel;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import com.hippo.ehviewer.client.data.GalleryInfo;
@Entity(tableName = "BOOKMARKS")
public class BookmarkInfo extends GalleryInfo {
public static final Creator<BookmarkInfo> CREATOR = new Creator<>() {
@Override
public BookmarkInfo createFromParcel(Parcel source) {
return new BookmarkInfo(source);
}
@Override
public BookmarkInfo[] newArray(int size) {
return new BookmarkInfo[size];
}
};
@ColumnInfo(name = "PAGE")
public int page;
@ColumnInfo(name = "TIME")
public long time;
protected BookmarkInfo(Parcel in) {
super(in);
this.page = in.readInt();
this.time = in.readLong();
}
public BookmarkInfo(GalleryInfo galleryInfo) {
this.gid = galleryInfo.gid;
this.token = galleryInfo.token;
this.title = galleryInfo.title;
this.titleJpn = galleryInfo.titleJpn;
this.thumb = galleryInfo.thumb;
this.category = galleryInfo.category;
this.posted = galleryInfo.posted;
this.uploader = galleryInfo.uploader;
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
}
public BookmarkInfo(int page, long time) {
this.page = page;
this.time = time;
}
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitleJpn() {
return titleJpn;
}
public void setTitleJpn(String titleJpn) {
this.titleJpn = titleJpn;
}
public String getThumb() {
return thumb;
}
public void setThumb(String thumb) {
this.thumb = thumb;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getPosted() {
return posted;
}
public void setPosted(String posted) {
this.posted = posted;
}
public String getUploader() {
return uploader;
}
public void setUploader(String uploader) {
this.uploader = uploader;
}
public float getRating() {
return rating;
}
public void setRating(float rating) {
this.rating = rating;
}
public String getSimpleLanguage() {
return simpleLanguage;
}
public void setSimpleLanguage(String simpleLanguage) {
this.simpleLanguage = simpleLanguage;
}
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.page);
dest.writeLong(this.time);
}
}
@@ -0,0 +1,20 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface BookmarksDao extends BasicDao<BookmarkInfo> {
@Insert
long insert(BookmarkInfo bookmark);
@Delete
void delete(BookmarkInfo bookmark);
@Query("SELECT * FROM BOOKMARKS ORDER BY TIME DESC")
List<BookmarkInfo> list();
}
@@ -0,0 +1,30 @@
package com.hippo.ehviewer.dao;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "DOWNLOAD_DIRNAME")
public class DownloadDirname {
@PrimaryKey
@ColumnInfo(name = "GID")
private long gid;
@ColumnInfo(name = "DIRNAME")
private String dirname;
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getDirname() {
return dirname;
}
public void setDirname(String dirname) {
this.dirname = dirname;
}
}
@@ -0,0 +1,29 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface DownloadDirnameDao extends BasicDao<DownloadDirname> {
@Query("SELECT * FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
DownloadDirname load(long gid);
@Update
void update(DownloadDirname downloadDirname);
@Insert
long insert(DownloadDirname downloadDirname);
@Query("DELETE FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
void deleteByKey(long gid);
@Query("DELETE FROM DOWNLOAD_DIRNAME")
void deleteAll();
@Query("SELECT * FROM DOWNLOAD_DIRNAME")
List<DownloadDirname> list();
}
@@ -0,0 +1,252 @@
package com.hippo.ehviewer.dao;
import android.os.Parcel;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import com.hippo.ehviewer.client.data.GalleryInfo;
@Entity(tableName = "DOWNLOADS")
public class DownloadInfo extends GalleryInfo {
public static final Creator<DownloadInfo> CREATOR = new Creator<>() {
@Override
public DownloadInfo createFromParcel(Parcel source) {
return new DownloadInfo(source);
}
@Override
public DownloadInfo[] newArray(int size) {
return new DownloadInfo[size];
}
};
public static final int STATE_INVALID = -1;
public static final int STATE_NONE = 0;
public static final int STATE_WAIT = 1;
public static final int STATE_DOWNLOAD = 2;
public static final int STATE_FINISH = 3;
public static final int STATE_FAILED = 4;
@ColumnInfo(name = "STATE")
public int state;
@ColumnInfo(name = "LEGACY")
public int legacy;
@ColumnInfo(name = "TIME")
public long time;
@ColumnInfo(name = "LABEL")
public String label;
@Ignore
public long speed;
@Ignore
public long remaining;
@Ignore
public int finished;
@Ignore
public int downloaded;
@Ignore
public int total;
DownloadInfo() {
}
protected DownloadInfo(Parcel in) {
super(in);
this.state = in.readInt();
this.legacy = in.readInt();
this.time = in.readLong();
this.label = in.readString();
}
public DownloadInfo(GalleryInfo galleryInfo) {
this.gid = galleryInfo.gid;
this.token = galleryInfo.token;
this.title = galleryInfo.title;
this.titleJpn = galleryInfo.titleJpn;
this.thumb = galleryInfo.thumb;
this.category = galleryInfo.category;
this.posted = galleryInfo.posted;
this.uploader = galleryInfo.uploader;
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
}
public DownloadInfo(int state, int legacy, long time, String label, long speed, long remaining,
int finished, int downloaded, int total) {
this.state = state;
this.legacy = legacy;
this.time = time;
this.label = label;
this.speed = speed;
this.remaining = remaining;
this.finished = finished;
this.downloaded = downloaded;
this.total = total;
}
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitleJpn() {
return titleJpn;
}
public void setTitleJpn(String titleJpn) {
this.titleJpn = titleJpn;
}
public String getThumb() {
return thumb;
}
public void setThumb(String thumb) {
this.thumb = thumb;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getPosted() {
return posted;
}
public void setPosted(String posted) {
this.posted = posted;
}
public String getUploader() {
return uploader;
}
public void setUploader(String uploader) {
this.uploader = uploader;
}
public float getRating() {
return rating;
}
public void setRating(float rating) {
this.rating = rating;
}
public String getSimpleLanguage() {
return simpleLanguage;
}
public void setSimpleLanguage(String simpleLanguage) {
this.simpleLanguage = simpleLanguage;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public int getLegacy() {
return legacy;
}
public void setLegacy(int legacy) {
this.legacy = legacy;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.state);
dest.writeInt(this.legacy);
dest.writeLong(this.time);
dest.writeString(this.label);
}
public long getSpeed() {
return this.speed;
}
public void setSpeed(long speed) {
this.speed = speed;
}
public long getRemaining() {
return this.remaining;
}
public void setRemaining(long remaining) {
this.remaining = remaining;
}
public int getFinished() {
return this.finished;
}
public void setFinished(int finished) {
this.finished = finished;
}
public int getDownloaded() {
return this.downloaded;
}
public void setDownloaded(int downloaded) {
this.downloaded = downloaded;
}
public int getTotal() {
return this.total;
}
public void setTotal(int total) {
this.total = total;
}
}
@@ -0,0 +1,40 @@
package com.hippo.ehviewer.dao;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "DOWNLOAD_LABELS")
public class DownloadLabel {
@PrimaryKey
@ColumnInfo(name = "_id")
private Long id;
@ColumnInfo(name = "LABEL")
private String label;
@ColumnInfo(name = "TIME")
private long time;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
@@ -0,0 +1,30 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface DownloadLabelDao extends BasicDao<DownloadLabel> {
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC")
List<DownloadLabel> list();
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
List<DownloadLabel> list(int offset, int limit);
@Update
void update(List<DownloadLabel> downloadLabels);
@Update
void update(DownloadLabel downloadLabel);
@Insert
long insert(DownloadLabel downloadLabel);
@Delete
void delete(DownloadLabel downloadLabel);
}
@@ -0,0 +1,27 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface DownloadsDao extends BasicDao<DownloadInfo> {
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC")
List<DownloadInfo> list();
@Query("SELECT * FROM DOWNLOADS WHERE GID = :gid")
DownloadInfo load(long gid);
@Update
void update(DownloadInfo downloadInfo);
@Insert
long insert(DownloadInfo downloadInfo);
@Delete
void delete(DownloadInfo downloadInfo);
}
@@ -0,0 +1,23 @@
package com.hippo.ehviewer.dao;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {BookmarkInfo.class, DownloadInfo.class, DownloadLabel.class, DownloadDirname.class, Filter.class, HistoryInfo.class, LocalFavoriteInfo.class, QuickSearch.class}, version = 4, exportSchema = false)
public abstract class EhDatabase extends RoomDatabase {
public abstract BookmarksDao bookmarksBao();
public abstract DownloadDirnameDao downloadDirnameDao();
public abstract DownloadLabelDao downloadLabelDao();
public abstract DownloadsDao downloadsDao();
public abstract FilterDao filterDao();
public abstract HistoryDao historyDao();
public abstract LocalFavoritesDao localFavoritesDao();
public abstract QuickSearchDao quickSearchDao();
}
@@ -0,0 +1,67 @@
package com.hippo.ehviewer.dao;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.hippo.util.HashCodeUtils;
import com.hippo.yorozuya.ObjectUtils;
@Entity(tableName = "FILTER")
public class Filter {
@ColumnInfo(name = "MODE")
public int mode;
@ColumnInfo(name = "TEXT")
public String text;
@ColumnInfo(name = "ENABLE")
public Boolean enable;
@PrimaryKey
@ColumnInfo(name = "_id")
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public int getMode() {
return mode;
}
public void setMode(int mode) {
this.mode = mode;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Boolean getEnable() {
return enable;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
@Override
public int hashCode() {
return HashCodeUtils.hashCode(mode, text);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Filter)) {
return false;
}
Filter filter = (Filter) o;
return filter.mode == mode && ObjectUtils.equal(filter.text, text);
}
}
@@ -0,0 +1,27 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface FilterDao extends BasicDao<Filter> {
@Query("SELECT * FROM FILTER")
List<Filter> list();
@Update
void update(Filter filter);
@Insert
long insert(Filter filter);
@Delete
void delete(Filter filter);
@Query("SELECT * FROM FILTER WHERE TEXT = :text AND MODE = :mode")
Filter load(String text, int mode);
}
@@ -0,0 +1,40 @@
package com.hippo.ehviewer.dao;
import androidx.paging.PagingSource;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface HistoryDao extends BasicDao<HistoryInfo> {
@Query("SELECT * FROM HISTORY WHERE GID = :gid")
HistoryInfo load(long gid);
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC")
List<HistoryInfo> list();
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC LIMIT :limit OFFSET :offset")
List<HistoryInfo> list(int offset, int limit);
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC")
PagingSource<Integer, HistoryInfo> listLazy();
@Update
void update(HistoryInfo historyInfo);
@Insert
long insert(HistoryInfo historyInfo);
@Delete
void delete(HistoryInfo historyInfos);
@Delete
void delete(List<HistoryInfo> historyInfos);
@Query("DELETE FROM HISTORY")
void deleteAll();
}
@@ -0,0 +1,160 @@
package com.hippo.ehviewer.dao;
import android.os.Parcel;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import com.hippo.ehviewer.client.data.GalleryInfo;
@Entity(tableName = "HISTORY")
public class HistoryInfo extends GalleryInfo {
public static final Creator<HistoryInfo> CREATOR = new Creator<>() {
@Override
public HistoryInfo createFromParcel(Parcel source) {
return new HistoryInfo(source);
}
@Override
public HistoryInfo[] newArray(int size) {
return new HistoryInfo[size];
}
};
@ColumnInfo(name = "MODE")
public int mode;
@ColumnInfo(name = "TIME")
public long time;
protected HistoryInfo(Parcel in) {
super(in);
this.mode = in.readInt();
this.time = in.readLong();
}
public HistoryInfo(GalleryInfo galleryInfo) {
this.gid = galleryInfo.gid;
this.token = galleryInfo.token;
this.title = galleryInfo.title;
this.titleJpn = galleryInfo.titleJpn;
this.thumb = galleryInfo.thumb;
this.category = galleryInfo.category;
this.posted = galleryInfo.posted;
this.uploader = galleryInfo.uploader;
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
}
public HistoryInfo(int mode, long time) {
this.mode = mode;
this.time = time;
}
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitleJpn() {
return titleJpn;
}
public void setTitleJpn(String titleJpn) {
this.titleJpn = titleJpn;
}
public String getThumb() {
return thumb;
}
public void setThumb(String thumb) {
this.thumb = thumb;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getPosted() {
return posted;
}
public void setPosted(String posted) {
this.posted = posted;
}
public String getUploader() {
return uploader;
}
public void setUploader(String uploader) {
this.uploader = uploader;
}
public float getRating() {
return rating;
}
public void setRating(float rating) {
this.rating = rating;
}
public String getSimpleLanguage() {
return simpleLanguage;
}
public void setSimpleLanguage(String simpleLanguage) {
this.simpleLanguage = simpleLanguage;
}
public int getMode() {
return mode;
}
public void setMode(int mode) {
this.mode = mode;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.mode);
dest.writeLong(this.time);
}
}
@@ -0,0 +1,147 @@
package com.hippo.ehviewer.dao;
import android.os.Parcel;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import com.hippo.ehviewer.client.data.GalleryInfo;
@Entity(tableName = "LOCAL_FAVORITES")
public class LocalFavoriteInfo extends GalleryInfo {
public static final Creator<LocalFavoriteInfo> CREATOR = new Creator<>() {
@Override
public LocalFavoriteInfo createFromParcel(Parcel source) {
return new LocalFavoriteInfo(source);
}
@Override
public LocalFavoriteInfo[] newArray(int size) {
return new LocalFavoriteInfo[size];
}
};
@ColumnInfo(name = "TIME")
public long time;
protected LocalFavoriteInfo(Parcel in) {
super(in);
this.time = in.readLong();
}
public LocalFavoriteInfo(GalleryInfo galleryInfo) {
this.gid = galleryInfo.gid;
this.token = galleryInfo.token;
this.title = galleryInfo.title;
this.titleJpn = galleryInfo.titleJpn;
this.thumb = galleryInfo.thumb;
this.category = galleryInfo.category;
this.posted = galleryInfo.posted;
this.uploader = galleryInfo.uploader;
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
}
public LocalFavoriteInfo() {
}
public long getGid() {
return gid;
}
public void setGid(long gid) {
this.gid = gid;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitleJpn() {
return titleJpn;
}
public void setTitleJpn(String titleJpn) {
this.titleJpn = titleJpn;
}
public String getThumb() {
return thumb;
}
public void setThumb(String thumb) {
this.thumb = thumb;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getPosted() {
return posted;
}
public void setPosted(String posted) {
this.posted = posted;
}
public String getUploader() {
return uploader;
}
public void setUploader(String uploader) {
this.uploader = uploader;
}
public float getRating() {
return rating;
}
public void setRating(float rating) {
this.rating = rating;
}
public String getSimpleLanguage() {
return simpleLanguage;
}
public void setSimpleLanguage(String simpleLanguage) {
this.simpleLanguage = simpleLanguage;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(this.time);
}
}
@@ -0,0 +1,29 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface LocalFavoritesDao extends BasicDao<LocalFavoriteInfo> {
@Query("SELECT * FROM LOCAL_FAVORITES ORDER BY TIME DESC")
List<LocalFavoriteInfo> list();
@Query("SELECT * FROM LOCAL_FAVORITES WHERE TITLE LIKE :title ORDER BY TIME DESC")
List<LocalFavoriteInfo> list(String title);
@Query("SELECT * FROM LOCAL_FAVORITES WHERE GID = :gid")
LocalFavoriteInfo load(long gid);
@Insert
long insert(LocalFavoriteInfo localFavoriteInfo);
@Delete
void delete(LocalFavoriteInfo localFavoriteInfo);
@Query("DELETE FROM LOCAL_FAVORITES WHERE GID = :gid")
void deleteByKey(long gid);
}
@@ -0,0 +1,117 @@
package com.hippo.ehviewer.dao;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "QUICK_SEARCH")
public class QuickSearch {
@PrimaryKey
@ColumnInfo(name = "_id")
public Long id;
@ColumnInfo(name = "NAME")
public String name;
@ColumnInfo(name = "MODE")
public int mode;
@ColumnInfo(name = "CATEGORY")
public int category;
@ColumnInfo(name = "KEYWORD")
public String keyword;
@ColumnInfo(name = "ADVANCE_SEARCH")
public int advanceSearch;
@ColumnInfo(name = "MIN_RATING")
public int minRating;
@ColumnInfo(name = "PAGE_FROM")
public int pageFrom;
@ColumnInfo(name = "PAGE_TO")
public int pageTo;
@ColumnInfo(name = "TIME")
public long time;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMode() {
return mode;
}
public void setMode(int mode) {
this.mode = mode;
}
public int getCategory() {
return category;
}
public void setCategory(int category) {
this.category = category;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
public int getAdvanceSearch() {
return advanceSearch;
}
public void setAdvanceSearch(int advanceSearch) {
this.advanceSearch = advanceSearch;
}
public int getMinRating() {
return minRating;
}
public void setMinRating(int minRating) {
this.minRating = minRating;
}
public int getPageFrom() {
return pageFrom;
}
public void setPageFrom(int pageFrom) {
this.pageFrom = pageFrom;
}
public int getPageTo() {
return pageTo;
}
public void setPageTo(int pageTo) {
this.pageTo = pageTo;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@NonNull
@Override
public String toString() {
return name;
}
}
@@ -0,0 +1,30 @@
package com.hippo.ehviewer.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface QuickSearchDao extends BasicDao<QuickSearch> {
@Query("SELECT * FROM QUICK_SEARCH ORDER BY TIME ASC")
List<QuickSearch> list();
@Query("SELECT * FROM QUICK_SEARCH ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
List<QuickSearch> list(int offset, int limit);
@Update
void update(List<QuickSearch> downloadLabels);
@Update
void update(QuickSearch quickSearch);
@Insert
long insert(QuickSearch quickSearch);
@Delete
void delete(QuickSearch quickSearch);
}
@@ -516,7 +516,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
DownloadInfo info = mAllInfoMap.get(gid);
if (info != null) {
// Remove from DB
EhDB.removeDownloadInfo(info.gid);
EhDB.removeDownloadInfo(info);
// Remove all list and map
mAllInfoList.remove(info);
@@ -552,7 +552,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Remove from DB
EhDB.removeDownloadInfo(info.gid);
EhDB.removeDownloadInfo(info);
// Remove from all info map
mAllInfoList.remove(info);
@@ -34,7 +34,6 @@ import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.thread.PVLock;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -50,7 +49,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
private final UriArchiveAccessor archiveAccessor;
private final Stack<Integer> requests = new Stack<>();
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private final LinkedHashMap<Integer, InputStream> streams = new LinkedHashMap<>();
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private Thread archiveThread;
private Thread decodeThread;
@@ -250,18 +249,18 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
Pipe pipe = new Pipe();
synchronized (streams) {
if (streams.get(index) != null) {
continue;
}
streams.put(index, pipe.getInputStream());
streams.notify();
}
archiveAccessor.extracttoOutputStream(index, pipe.getOutputFd());
pipe.closeOutputFd();
long addr = archiveAccessor.extracttoOutputStream(index);
synchronized (streams) {
streams.put(index, addr);
streams.notify();
}
}
}
}
@@ -271,7 +270,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
int index;
InputStream stream;
Long addr;
synchronized (streams) {
if (streams.isEmpty()) {
try {
@@ -283,16 +282,16 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
Iterator<Map.Entry<Integer, InputStream>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, InputStream> entry = iterator.next();
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, Long> entry = iterator.next();
iterator.remove();
index = entry.getKey();
stream = entry.getValue();
addr = entry.getValue();
decodingIndex.lazySet(index);
}
try {
Image image = Image.decode(stream, true);
Image image = Image.decodeAddr(addr, true);
if (image != null) {
notifyPageSucceed(index, image);
} else {
@@ -34,6 +34,7 @@ import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.StringUtils;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -258,7 +259,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
InputStream is = null;
try {
is = files[index].openInputStream();
Image image = Image.decode(is, true);
Image image = Image.decode(((FileInputStream) is).getFD(), true);
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
if (image != null) {
notifyPageSucceed(index, image);
@@ -1,52 +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.gallery;
import android.os.ParcelFileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
class Pipe {
private ParcelFileDescriptor[] fd;
{
try {
fd = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
e.printStackTrace();
}
}
private final InputStream inputStream = new FileInputStream(fd[0].getFileDescriptor());
InputStream getInputStream() {
return inputStream;
}
int getOutputFd() {
return fd[1].getFd();
}
void closeOutputFd() {
try {
fd[1].close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -41,6 +41,27 @@ class VersionPreference @JvmOverloads constructor(
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setTitle(R.string.show_library_version)
builder.setMessage(Native.getlibarchiveVersion() + "\nlzma(xz-utils) " + Native.getliblzmaVersion() + "\nzlib " + Native.getzlibVersion() + "\nlibjpeg-turbo " + Native.getlibjpeg_turboVersion())
val info = NativeLibInfo().addLib(Native.getlibarchiveVersion())
.addLib("lzma(xz-utils)", Native.getliblzmaVersion())
.addLib("zlib", Native.getzlibVersion())
.addLib(Native.getnettleVersion())
builder.setMessage(info.getMessage())
}
inner class NativeLibInfo {
private var message: String = ""
fun addLib(libname: String, libversion: String): NativeLibInfo {
message = "$message$libname $libversion\n"
return this
}
fun getMessage(): String {
return message;
}
fun addLib(msg: String): NativeLibInfo {
message = "$message$msg\n"
return this
}
}
}
@@ -66,6 +66,7 @@ import com.hippo.yorozuya.thread.PriorityThread;
import com.hippo.yorozuya.thread.PriorityThreadFactory;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -1096,33 +1097,6 @@ public final class SpiderQueen implements Runnable {
void onGetImageFailure(int index, String error);
}
private static class AutoCloseInputStream extends InputStream {
private final InputStreamPipe mPipe;
private final InputStream mIs;
public AutoCloseInputStream(InputStreamPipe pipe, InputStream is) {
mPipe = pipe;
mIs = is;
}
@Override
public int read() throws IOException {
return mIs.read();
}
@Override
public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
return mIs.read(buffer, byteOffset, byteCount);
}
@Override
public void close() throws IOException {
mPipe.close();
mPipe.release();
}
}
private class SpiderWorker implements Runnable {
private final long mGid;
@@ -1693,7 +1667,7 @@ public final class SpiderQueen implements Runnable {
pipe.obtain();
try {
is = new AutoCloseInputStream(pipe, pipe.open());
is = pipe.open();
} catch (IOException e) {
// Can't open pipe
error = GetText.getString(R.string.error_reading_failed);
@@ -1703,7 +1677,17 @@ public final class SpiderQueen implements Runnable {
}
if (is != null) {
image = Image.decode(is, true);
try {
image = Image.decode(((FileInputStream) is).getFD(), true);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (image == null) {
error = GetText.getString(R.string.error_decoding_failed);
}
@@ -17,6 +17,7 @@
package com.hippo.ehviewer.ui;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
@@ -123,6 +124,9 @@ public abstract class EhActivity extends AppCompatActivity {
@Override
protected void onResume() {
if (Settings.getSecurity() && SecurityActivity.Companion.isAuthenticationSupported(this) && EhApplication.locked) {
startActivity(new Intent(this, SecurityActivity.class));
}
super.onResume();
if (Settings.getEnabledSecurity()) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
@@ -226,6 +226,9 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private int mSize;
private int mCurrentIndex;
private int mSavingPage = -1;
private EditTextDialogBuilder builder;
private boolean dialogShown = false;
private AlertDialog dialog;
private void buildProvider() {
if (mGalleryProvider != null) {
@@ -287,7 +290,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
outState.putInt(KEY_PAGE, mPage);
outState.putInt(KEY_CURRENT_INDEX, mCurrentIndex);
}
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
@Override
protected void attachBaseContext(@NonNull Context newBase) {
@@ -477,16 +489,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
FrameLayout mainLayout = (FrameLayout) ViewUtils.$$(this, R.id.main);
mainLayout.addView(new GalleryGuideView(this));
}
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
}
@Override
protected void onDestroy() {
@@ -1046,6 +1049,66 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
private void showPasswdDialog() {
if (!dialogShown) {
dialogShown = true;
dialog.show();
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
GalleryActivity.this.onProvidePasswd();
});
}
dialog.setOnCancelListener(v -> finish());
}
}
private void onProvidePasswd() {
String passwd = builder.getText();
if (passwd.isEmpty())
builder.setError(getString(R.string.passwd_cannot_be_empty));
else {
ArchiveGalleryProvider.passwd = passwd;
ArchiveGalleryProvider.pv.v();
}
}
private void onPasswdWrong() {
builder.setError(getString(R.string.passwd_wrong));
}
private void onPasswdCorrect() {
dialog.dismiss();
}
private static class ShowPasswdDialogHandler extends Handler {
//弱引用持有HandlerActivity , GC 回收时会被回收掉
private final WeakReference<GalleryActivity> weakReference;
public ShowPasswdDialogHandler(GalleryActivity activity) {
this.weakReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
GalleryActivity activity = weakReference.get();
super.handleMessage(msg);
if (null != activity) {
switch (msg.what) {
case 0:
activity.showPasswdDialog();
break;
case 1:
activity.onPasswdWrong();
break;
case 2:
activity.onPasswdCorrect();
break;
}
}
}
}
private class GalleryMenuHelper implements DialogInterface.OnClickListener {
private final View mView;
@@ -1306,69 +1369,5 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
private EditTextDialogBuilder builder;
private boolean dialogShown = false;
private AlertDialog dialog;
private void showPasswdDialog() {
if (!dialogShown) {
dialogShown = true;
dialog.show();
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
GalleryActivity.this.onProvidePasswd();
});
}
dialog.setOnCancelListener(v -> finish());
}
}
private void onProvidePasswd() {
String passwd = builder.getText();
if (passwd.isEmpty())
builder.setError(getString(R.string.passwd_cannot_be_empty));
else {
ArchiveGalleryProvider.passwd = passwd;
ArchiveGalleryProvider.pv.v();
}
}
private void onPasswdWrong() {
builder.setError(getString(R.string.passwd_wrong));
}
private void onPasswdCorrect() {
dialog.dismiss();
}
private static class ShowPasswdDialogHandler extends Handler {
//弱引用持有HandlerActivity , GC 回收时会被回收掉
private final WeakReference<GalleryActivity> weakReference;
public ShowPasswdDialogHandler(GalleryActivity activity) {
this.weakReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
GalleryActivity activity = weakReference.get();
super.handleMessage(msg);
if (null != activity) {
switch (msg.what) {
case 0:
activity.showPasswdDialog();
break;
case 1:
activity.onPasswdWrong();
break;
case 2:
activity.onPasswdCorrect();
break;
}
}
}
}
}
@@ -47,7 +47,6 @@ import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhTagDatabase;
@@ -67,11 +66,9 @@ import com.hippo.ehviewer.ui.scene.GalleryListScene;
import com.hippo.ehviewer.ui.scene.GalleryPreviewsScene;
import com.hippo.ehviewer.ui.scene.HistoryScene;
import com.hippo.ehviewer.ui.scene.ProgressScene;
import com.hippo.ehviewer.ui.scene.SecurityScene;
import com.hippo.ehviewer.ui.scene.SelectSiteScene;
import com.hippo.ehviewer.ui.scene.SignInScene;
import com.hippo.ehviewer.ui.scene.SolidScene;
import com.hippo.ehviewer.ui.scene.WarningScene;
import com.hippo.ehviewer.ui.scene.WebViewSignInScene;
import com.hippo.ehviewer.widget.EhStageLayout;
import com.hippo.io.UniFileInputStreamPipe;
@@ -98,8 +95,6 @@ public final class MainActivity extends StageActivity
private static final String KEY_NAV_CHECKED_ITEM = "nav_checked_item";
static {
registerLaunchMode(SecurityScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(WarningScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(SignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(WebViewSignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(CookieSignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
@@ -143,11 +138,7 @@ public final class MainActivity extends StageActivity
@NonNull
@Override
protected Announcer getLaunchAnnouncer() {
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this)) {
return new Announcer(SecurityScene.class);
} else if (Settings.getShowWarning()) {
return new Announcer(WarningScene.class);
} else if (EhUtils.needSignedIn(this)) {
if (EhUtils.needSignedIn(this)) {
return new Announcer(SignInScene.class);
} else if (Settings.getSelectSite()) {
return new Announcer(SelectSiteScene.class);
@@ -161,17 +152,7 @@ public final class MainActivity extends StageActivity
// Sometimes scene can't show directly
private Announcer processAnnouncer(Announcer announcer) {
if (0 == getSceneCount()) {
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this)) {
Bundle newArgs = new Bundle();
newArgs.putString(SecurityScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
newArgs.putBundle(SecurityScene.KEY_TARGET_ARGS, announcer.getArgs());
return new Announcer(SecurityScene.class).setArgs(newArgs);
} else if (Settings.getShowWarning()) {
Bundle newArgs = new Bundle();
newArgs.putString(WarningScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
newArgs.putBundle(WarningScene.KEY_TARGET_ARGS, announcer.getArgs());
return new Announcer(WarningScene.class).setArgs(newArgs);
} else if (EhUtils.needSignedIn(this)) {
if (EhUtils.needSignedIn(this)) {
Bundle newArgs = new Bundle();
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.getArgs());
@@ -427,9 +408,6 @@ public final class MainActivity extends StageActivity
@Override
protected void onResume() {
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this) && EhApplication.locked) {
startScene(new Announcer(SecurityScene.class), true);
}
super.onResume();
setNavCheckedItem(mNavCheckedItem);
checkClipboardUrl();
@@ -16,10 +16,11 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.ui.scene
package com.hippo.ehviewer.ui
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
@@ -30,14 +31,10 @@ import androidx.fragment.app.FragmentActivity
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
class SecurityScene : SolidScene() {
override fun needShowLeftDrawer(): Boolean {
return false
}
class SecurityActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (isAuthenticationSupported(requireContext()))
if (isAuthenticationSupported(this))
startAuthentication(getString(R.string.settings_privacy_require_unlock))
else
onSuccess()
@@ -52,7 +49,7 @@ class SecurityScene : SolidScene() {
title = title,
subtitle = subtitle,
confirmationRequired = confirmationRequired,
executor = ContextCompat.getMainExecutor(requireContext()),
executor = ContextCompat.getMainExecutor(this),
callback = AuthenticationCallback()
)
}
@@ -70,7 +67,7 @@ class SecurityScene : SolidScene() {
errorCode: Int,
errString: CharSequence
) {
requireActivity().finish()
moveTaskToBack(true)
}
}
@@ -84,8 +81,6 @@ class SecurityScene : SolidScene() {
private fun onSuccess() {
EhApplication.locked = false
if (stackIndex == 0)
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
finish()
}
}
@@ -22,7 +22,6 @@ import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@@ -22,7 +22,7 @@ import android.os.Bundle
import androidx.preference.SwitchPreferenceCompat
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.ui.scene.SecurityScene.Companion.isAuthenticationSupported
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
/**
* Created by Mo10 on 2018/2/10.
@@ -22,21 +22,17 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
@@ -45,26 +41,18 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
import com.hippo.app.CheckBoxDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.conaco.DataContainer;
@@ -85,7 +73,6 @@ 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.ui.MainActivity;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.io.UniFileInputStreamPipe;
import com.hippo.scene.Announcer;
@@ -99,7 +86,6 @@ import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ObjectUtils;
import com.hippo.yorozuya.ViewUtils;
import com.hippo.yorozuya.collect.LongList;
@@ -390,90 +376,9 @@ public class DownloadsScene extends ToolbarScene
updateView();
guide();
return view;
}
private void guide() {
if (Settings.getGuideDownloadThumb() && null != mRecyclerView) {
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Settings.getGuideDownloadThumb()) {
guideDownloadThumb();
}
if (null != mRecyclerView) {
ViewUtils.removeOnGlobalLayoutListener(mRecyclerView.getViewTreeObserver(), this);
}
}
});
} else {
guideDownloadLabels();
}
}
private void guideDownloadThumb() {
MainActivity activity = getMainActivity();
if (null == activity || !Settings.getGuideDownloadThumb() || null == mLayoutManager || null == mRecyclerView) {
guideDownloadLabels();
return;
}
int position = mLayoutManager.findFirstCompletelyVisibleItemPositions(null)[0];
if (position < 0) {
guideDownloadLabels();
return;
}
RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(position);
if (null == holder) {
guideDownloadLabels();
return;
}
TapTargetView.showFor(requireActivity(),
TapTarget.forView(((DownloadHolder) holder).thumb,
getString(R.string.guide_download_thumb_title),
getString(R.string.guide_download_thumb_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetDismissed(view, userInitiated);
Settings.putGuideDownloadThumb(false);
guideDownloadLabels();
}
});
}
private void guideDownloadLabels() {
MainActivity activity = getMainActivity();
if (null == activity || !Settings.getGuideDownloadLabels()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_download_labels_title),
getString(R.string.guide_download_labels_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.puttGuideDownloadLabels(false);
openDrawer(Gravity.RIGHT);
}
});
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -639,20 +544,13 @@ public class DownloadsScene extends ToolbarScene
initLabels();
RecyclerViewDragDropManager dragDropManager = new RecyclerViewDragDropManager();
dragDropManager.setInitiateOnLongPress(true);
dragDropManager.setInitiateOnTouch(false);
dragDropManager.setDraggingItemAlpha(0.8f);
mLabelAdapter = new DownloadLabelAdapter(inflater);
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
mLabelAdapter.setHasStableIds(true);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(mLabelAdapter));
dragDropManager.attachRecyclerView(recyclerView);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new DownloadLabelItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setAdapter(mLabelAdapter);
return view;
}
@@ -975,7 +873,7 @@ public class DownloadsScene extends ToolbarScene
holder.speed.setText(FileUtils.humanReadableByteCount(speed, false) + "/S");
}
private static class DownloadLabelHolder extends AbstractDraggableItemViewHolder {
private static class DownloadLabelHolder extends RecyclerView.ViewHolder {
private final TextView label;
private final ImageView option;
@@ -1054,7 +952,7 @@ public class DownloadsScene extends ToolbarScene
}
}
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> implements DraggableItemAdapter<DownloadLabelHolder> {
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> {
private final LayoutInflater mInflater;
@@ -1108,36 +1006,15 @@ public class DownloadsScene extends ToolbarScene
});
if (position > 0) {
holder.option.setVisibility(View.VISIBLE);
holder.itemView.setOnLongClickListener(v -> {
holder.option.setOnClickListener(v -> {
if (context != null) {
PopupMenu popupMenu = new PopupMenu(context, holder.option);
popupMenu.inflate(R.menu.download_label_option);
popupMenu.show();
popupMenu.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.menu_label_rename) {
EditTextDialogBuilder builder = new EditTextDialogBuilder(
context, label, getString(R.string.download_labels));
builder.setTitle(R.string.rename_label_title);
builder.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.show();
new RenameLabelDialogHelper(builder, dialog, label);
} else if (itemId == R.id.menu_label_remove) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_label_title))
.setMessage(getString(R.string.delete_label_message, label))
.setPositiveButton(R.string.delete, (dialog, which) -> {
mDownloadManager.deleteLabel(label);
mLabels.remove(position);
notifyDataSetChanged();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
return false;
});
EditTextDialogBuilder builder = new EditTextDialogBuilder(
context, label, getString(R.string.download_labels));
builder.setTitle(R.string.rename_label_title);
builder.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.show();
new RenameLabelDialogHelper(builder, dialog, label);
}
return true;
});
} else {
holder.option.setVisibility(View.GONE);
@@ -1154,43 +1031,6 @@ public class DownloadsScene extends ToolbarScene
public int getItemCount() {
return mLabels != null ? mLabels.size() : 0;
}
@Override
public boolean onCheckCanStartDrag(@NonNull DownloadLabelHolder holder, int position, int x, int y) {
return position != 0 && x > holder.option.getX() && y > holder.option.getY();
}
@Override
public ItemDraggableRange onGetItemDraggableRange(@NonNull DownloadLabelHolder holder, int position) {
return new ItemDraggableRange(1, getItemCount() - 1);
}
@Override
public void onMoveItem(int fromPosition, int toPosition) {
Context context = getContext();
if (null == context || fromPosition == toPosition || toPosition == 0) {
return;
}
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
final String item = mLabels.remove(fromPosition);
mLabels.add(toPosition, item);
}
@Override
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
return dropPosition != 0;
}
@Override
public void onItemDragStarted(int position) {
notifyDataSetChanged();
}
@Override
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
notifyDataSetChanged();
}
}
private class DeleteRangeDialogHelper implements DialogInterface.OnClickListener {
@@ -1528,4 +1368,43 @@ public class DownloadsScene extends ToolbarScene
}
}
}
private class DownloadLabelItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
}
@SuppressLint("NotifyDataSetChanged")
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getBindingAdapterPosition();
int toPosition = target.getBindingAdapterPosition();
Context context = getContext();
if (null == context || fromPosition == toPosition || toPosition == 0 || fromPosition == 0) {
return false;
}
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
final String item = mLabels.remove(fromPosition);
mLabels.add(toPosition, item);
mLabelAdapter.notifyDataSetChanged();
return true;
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getBindingAdapterPosition();
if (mDownloadManager == null)
return;
if (position != 0) {
String label = mLabels.get(position);
mDownloadManager.deleteLabel(label);
}
mLabels.remove(position);
mLabelAdapter.notifyDataSetChanged();
}
}
}
@@ -45,8 +45,6 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.annotation.Implemented;
@@ -311,40 +309,9 @@ public class FavoritesScene extends BaseScene implements
mHelper.firstRefresh();
}
guideCollections();
return view;
}
private void guideCollections() {
Activity activity = getMainActivity();
if (null == activity || !Settings.getGuideCollections()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_collections_title),
getString(R.string.guide_collections_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.putGuideCollections(false);
openDrawer(Gravity.RIGHT);
}
});
}
// keyword of mUrlBuilder, fav cat of mUrlBuilder, mFavCatArray.
// They changed, call it
private void updateSearchBar() {
@@ -18,13 +18,10 @@ package com.hippo.ehviewer.ui.scene;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -33,10 +30,8 @@ import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
@@ -50,25 +45,17 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu;
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;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
@@ -111,7 +98,6 @@ import com.hippo.widget.FabLayout;
import com.hippo.widget.SearchBarMover;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.StringUtils;
@@ -634,40 +620,9 @@ public final class GalleryListScene extends BaseScene
mHelper.firstRefresh();
}
guideQuickSearch();
return view;
}
private void guideQuickSearch() {
Activity activity = getMainActivity();
if (null == activity || !Settings.getGuideQuickSearch()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_quick_search_title),
getString(R.string.guide_quick_search_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.putGuideQuickSearch(false);
openDrawer(Gravity.RIGHT);
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -786,19 +741,13 @@ public final class GalleryListScene extends BaseScene
Context context = getContext();
AssertUtils.assertNotNull(context);
RecyclerViewDragDropManager dragDropManager = new RecyclerViewDragDropManager();
dragDropManager.setInitiateOnLongPress(true);
dragDropManager.setInitiateOnTouch(false);
dragDropManager.setDraggingItemAlpha(0.8f);
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
final QsDrawerAdapter qsDrawerAdapter = new QsDrawerAdapter(inflater);
qsDrawerAdapter.setHasStableIds(true);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(qsDrawerAdapter));
dragDropManager.attachRecyclerView(recyclerView);
recyclerView.setAdapter(qsDrawerAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new GalleryListQSItemTouchHelperCallback(qsDrawerAdapter));
itemTouchHelper.attachToRecyclerView(recyclerView);
mQuickSearchList = EhDB.getAllQuickSearch();
tip.setText(R.string.quick_search_tip);
if (mIsTopList) {
@@ -1525,7 +1474,7 @@ public final class GalleryListScene extends BaseScene
}
}
private static class QsDrawerHolder extends AbstractDraggableItemViewHolder {
private static class QsDrawerHolder extends RecyclerView.ViewHolder {
private final TextView key;
private final ImageView option;
@@ -1570,7 +1519,7 @@ public final class GalleryListScene extends BaseScene
}
}
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> implements DraggableItemAdapter<QsDrawerHolder> {
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> {
private final LayoutInflater mInflater;
@@ -1601,34 +1550,6 @@ public final class GalleryListScene extends BaseScene
setState(STATE_NORMAL);
closeDrawer(Gravity.RIGHT);
});
holder.itemView.setOnLongClickListener(v -> {
PopupMenu popupMenu = new PopupMenu(requireContext(), holder.option);
popupMenu.inflate(R.menu.quicksearch_option);
popupMenu.show();
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
final QuickSearch quickSearch = mQuickSearchList.get(position);
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.menu_qs_remove) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_quick_search_title))
.setMessage(getString(R.string.delete_quick_search_message, quickSearch.name))
.setPositiveButton(R.string.delete, (dialog, which) -> {
EhDB.deleteQuickSearch(quickSearch);
mQuickSearchList.remove(position);
notifyDataSetChanged();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
}
return false;
}
});
return true;
});
} else {
int[] keywords = {11, 12, 13, 15};
int[] toplists = {R.string.toplist_alltime, R.string.toplist_pastyear, R.string.toplist_pastmonth, R.string.toplist_yesterday};
@@ -1664,44 +1585,6 @@ public final class GalleryListScene extends BaseScene
public int getItemCount() {
return !mIsTopList ? mQuickSearchList != null ? mQuickSearchList.size() : 0 : 4;
}
@Override
public boolean onCheckCanStartDrag(@NonNull QsDrawerHolder holder, int position, int x, int y) {
return !mIsTopList && x > holder.option.getX() && y > holder.option.getY();
}
@Override
public ItemDraggableRange onGetItemDraggableRange(@NonNull QsDrawerHolder holder, int position) {
return null;
}
@Override
public void onMoveItem(int fromPosition, int toPosition) {
if (fromPosition == toPosition) {
return;
}
if (null == mQuickSearchList) {
return;
}
EhDB.moveQuickSearch(fromPosition, toPosition);
final QuickSearch item = mQuickSearchList.remove(fromPosition);
mQuickSearchList.add(toPosition, item);
}
@Override
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
return true;
}
@Override
public void onItemDragStarted(int position) {
notifyDataSetChanged();
}
@Override
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
notifyDataSetChanged();
}
}
private abstract class UrlSuggestion extends SearchBar.Suggestion {
@@ -1886,4 +1769,47 @@ public final class GalleryListScene extends BaseScene
}
}
}
private class GalleryListQSItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final QsDrawerAdapter mAdapter;
public GalleryListQSItemTouchHelperCallback(QsDrawerAdapter adapter) {
mAdapter = adapter;
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
}
@SuppressLint("NotifyDataSetChanged")
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getBindingAdapterPosition();
int toPosition = target.getBindingAdapterPosition();
if (fromPosition == toPosition) {
return false;
}
if (null == mQuickSearchList) {
return false;
}
EhDB.moveQuickSearch(fromPosition, toPosition);
final QuickSearch item = mQuickSearchList.remove(fromPosition);
mQuickSearchList.add(toPosition, item);
mAdapter.notifyDataSetChanged();
return true;
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (mQuickSearchList == null)
return;
int position = viewHolder.getBindingAdapterPosition();
final QuickSearch quickSearch = mQuickSearchList.get(position);
EhDB.deleteQuickSearch(quickSearch);
mQuickSearchList.remove(position);
mAdapter.notifyDataSetChanged();
}
}
}
@@ -1,704 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem;
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.HandlerDrawable;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.FavouriteStatusRouter;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.dao.DownloadInfo;
import com.hippo.ehviewer.dao.DownloadLabel;
import com.hippo.ehviewer.dao.HistoryInfo;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.view.ViewTransition;
import com.hippo.widget.LoadImageView;
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.ViewUtils;
import org.greenrobot.greendao.query.LazyList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import rikka.core.res.ResourcesKt;
public class HistoryScene extends ToolbarScene {
/*---------------
View life cycle
---------------*/
@Nullable
private TextView mTip;
@Nullable
private FastScroller mFastScroller;
@Nullable
private EasyRecyclerView mRecyclerView;
@Nullable
private ViewTransition mViewTransition;
@Nullable
private RecyclerView.Adapter<?> mAdapter;
@Nullable
private LazyList<HistoryInfo> mLazyList;
private DownloadManager mDownloadManager;
private DownloadManager.DownloadInfoListener mDownloadInfoListener;
private FavouriteStatusRouter mFavouriteStatusRouter;
private FavouriteStatusRouter.Listener mFavouriteStatusRouterListener;
@Override
public void onDestroy() {
super.onDestroy();
mDownloadManager.removeDownloadInfoListener(mDownloadInfoListener);
mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener);
}
@Override
public int getNavCheckedItem() {
return R.id.nav_history;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Context context = getContext();
assert context != null;
mDownloadManager = EhApplication.getDownloadManager(context);
mDownloadManager = EhApplication.getDownloadManager(context);
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context);
mDownloadInfoListener = new DownloadManager.DownloadInfoListener() {
@Override
public void onAdd(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position) {
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onUpdate(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list) {
}
@Override
public void onUpdateAll() {
}
@Override
public void onReload() {
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onChange() {
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onRenameLabel(String from, String to) {
}
@Override
public void onRemove(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position) {
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onUpdateLabels() {
}
};
mDownloadManager.addDownloadInfoListener(mDownloadInfoListener);
mFavouriteStatusRouterListener = (gid, slot) -> {
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
};
mFavouriteStatusRouter.addListener(mFavouriteStatusRouterListener);
}
@Nullable
@Override
public View onCreateViewWithToolbar(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_history, container, false);
View content = ViewUtils.$$(view, R.id.content);
mRecyclerView = (EasyRecyclerView) ViewUtils.$$(content, R.id.recycler_view);
mFastScroller = (FastScroller) ViewUtils.$$(content, R.id.fast_scroller);
mTip = (TextView) ViewUtils.$$(view, R.id.tip);
mViewTransition = new ViewTransition(content, mTip);
Context context = getContext();
AssertUtils.assertNotNull(context);
Resources resources = context.getResources();
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.big_history);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
mTip.setCompoundDrawables(null, drawable, null, null);
RecyclerViewTouchActionGuardManager guardManager = new RecyclerViewTouchActionGuardManager();
guardManager.setInterceptVerticalScrollingWhileAnimationRunning(true);
guardManager.setEnabled(true);
RecyclerViewSwipeManager swipeManager = new RecyclerViewSwipeManager();
mAdapter = new HistoryAdapter();
mAdapter.setHasStableIds(true);
mAdapter = swipeManager.createWrappedAdapter(mAdapter);
mRecyclerView.setAdapter(mAdapter);
final GeneralItemAnimator animator = new SwipeDismissItemAnimator();
animator.setSupportsChangeAnimations(false);
mRecyclerView.setItemAnimator(animator);
AutoStaggeredGridLayoutManager layoutManager = new AutoStaggeredGridLayoutManager(
0, StaggeredGridLayoutManager.VERTICAL);
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()));
layoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setClipToPadding(false);
mRecyclerView.setClipChildren(false);
int interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval);
int paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h);
int paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v);
MarginItemDecoration decoration = new MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV);
mRecyclerView.addItemDecoration(decoration);
guardManager.attachRecyclerView(mRecyclerView);
swipeManager.attachRecyclerView(mRecyclerView);
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable handlerDrawable = new HandlerDrawable();
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorPrimary));
mFastScroller.setHandlerDrawable(handlerDrawable);
updateLazyList();
updateView(false);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setTitle(R.string.history);
setNavigationIcon(R.drawable.ic_baseline_menu_24);
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (null != mLazyList) {
mLazyList.close();
mLazyList = null;
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
if (null != mRecyclerView) {
mRecyclerView.stopScroll();
mRecyclerView = null;
}
mViewTransition = null;
mAdapter = null;
}
// Remember to notify
private void updateLazyList() {
LazyList<HistoryInfo> lazyList = EhDB.getHistoryLazyList();
if (mLazyList != null) {
mLazyList.close();
}
mLazyList = lazyList;
}
private void updateView(boolean animation) {
if (null == mAdapter || null == mViewTransition) {
return;
}
if (mAdapter.getItemCount() == 0) {
mViewTransition.showView(1, animation);
} else {
mViewTransition.showView(0, animation);
}
}
@SuppressLint("RtlHardcoded")
@Override
public void onNavigationClick() {
toggleDrawer(Gravity.LEFT);
}
@Override
public int getMenuResId() {
return R.menu.scene_history;
}
private void showClearAllDialog() {
new MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.clear_all_history)
.setPositiveButton(R.string.clear_all, (dialog, which) -> {
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
return;
}
EhDB.clearHistoryInfo();
updateLazyList();
mAdapter.notifyDataSetChanged();
updateView(true);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
@Override
public boolean onMenuItemClick(MenuItem item) {
// Skip when in choice mode
Context context = getContext();
if (null == context) {
return false;
}
int id = item.getItemId();
if (id == R.id.action_clear_all) {
showClearAllDialog();
return true;
}
return false;
}
public boolean onItemClick(View view, int position) {
if (null == mLazyList) {
return false;
}
Bundle args = new Bundle();
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO);
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, mLazyList.get(position));
Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args);
startScene(announcer);
return true;
}
public boolean onItemLongClick(int position) {
final Context context = getContext();
final MainActivity activity = getMainActivity();
if (null == context || null == activity || null == mLazyList) {
return false;
}
final GalleryInfo gi = mLazyList.get(position);
if (gi == null) {
return true;
}
boolean downloaded = mDownloadManager.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID;
boolean favourited = gi.favoriteSlot != -2;
CharSequence[] items = downloaded ? new CharSequence[]{
context.getString(R.string.read),
context.getString(R.string.delete_downloads),
context.getString(favourited ? R.string.remove_from_favourites : R.string.add_to_favourites),
context.getString(R.string.delete),
context.getString(R.string.download_move_dialog_title),
} : new CharSequence[]{
context.getString(R.string.read),
context.getString(R.string.download),
context.getString(favourited ? R.string.remove_from_favourites : R.string.add_to_favourites),
context.getString(R.string.delete),
};
int[] icons = downloaded ? new int[]{
R.drawable.v_book_open_x24,
R.drawable.v_delete_x24,
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
R.drawable.v_delete_x24,
R.drawable.v_folder_move_x24,
} : new int[]{
R.drawable.v_book_open_x24,
R.drawable.v_download_x24,
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
R.drawable.v_delete_x24,
};
new MaterialAlertDialogBuilder(context)
.setTitle(EhUtils.getSuitableTitle(gi))
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
switch (which) {
case 0: // Read
Intent intent = new Intent(activity, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi);
startActivity(intent);
break;
case 1: // Download
if (downloaded) {
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_remove_dialog_title)
.setMessage(getString(R.string.download_remove_dialog_message, gi.title))
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> mDownloadManager.deleteDownload(gi.gid))
.show();
} else {
CommonOperations.startDownload(activity, gi, false);
}
break;
case 2: // Favorites
if (favourited) {
CommonOperations.removeFromFavorites(activity, gi, new RemoveFromFavoriteListener(context, activity.getStageId(), getTag()));
} else {
CommonOperations.addToFavorites(activity, gi, new AddToFavoriteListener(context, activity.getStageId(), getTag()));
}
break;
case 3: // Delete
if (null == mLazyList || null == mAdapter) {
return;
}
HistoryInfo info = mLazyList.get(position);
EhDB.deleteHistoryInfo(info);
updateLazyList();
mAdapter.notifyDataSetChanged();
updateView(true);
break;
case 4: // Move
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager(context).getLabelList();
List<String> labelList = new ArrayList<>(labelRawList.size() + 1);
labelList.add(getString(R.string.default_download_label_name));
for (int i = 0, n = labelRawList.size(); i < n; i++) {
labelList.add(labelRawList.get(i).getLabel());
}
String[] labels = labelList.toArray(new String[0]);
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_move_dialog_title)
.setItems(labels, helper)
.show();
break;
}
}).show();
return true;
}
private static class AddToFavoriteListener extends EhCallback<GalleryListScene, Void> {
public AddToFavoriteListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
}
@Override
public void onSuccess(Void result) {
showTip(R.string.add_to_favorite_success, LENGTH_SHORT);
}
@Override
public void onFailure(Exception e) {
showTip(R.string.add_to_favorite_failure, LENGTH_LONG);
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryListScene;
}
}
private static class RemoveFromFavoriteListener extends EhCallback<GalleryListScene, Void> {
public RemoveFromFavoriteListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
}
@Override
public void onSuccess(Void result) {
showTip(R.string.remove_from_favorite_success, LENGTH_SHORT);
}
@Override
public void onFailure(Exception e) {
showTip(R.string.remove_from_favorite_failure, LENGTH_LONG);
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryListScene;
}
}
private static class HistoryHolder extends AbstractSwipeableItemViewHolder {
public final View card;
public final LoadImageView thumb;
public final TextView title;
public final TextView uploader;
public final SimpleRatingView rating;
public final TextView category;
public final TextView posted;
public final TextView simpleLanguage;
public final TextView pages;
public final ImageView downloaded;
public HistoryHolder(View itemView) {
super(itemView);
card = itemView.findViewById(R.id.card);
thumb = itemView.findViewById(R.id.thumb);
title = itemView.findViewById(R.id.title);
uploader = itemView.findViewById(R.id.uploader);
rating = itemView.findViewById(R.id.rating);
category = itemView.findViewById(R.id.category);
posted = itemView.findViewById(R.id.posted);
simpleLanguage = itemView.findViewById(R.id.simple_language);
pages = itemView.findViewById(R.id.pages);
downloaded = itemView.findViewById(R.id.downloaded);
}
@NonNull
@Override
public View getSwipeableContainerView() {
return card;
}
}
private class MoveDialogHelper implements DialogInterface.OnClickListener {
private final String[] mLabels;
private final GalleryInfo mGi;
public MoveDialogHelper(String[] labels, GalleryInfo gi) {
mLabels = labels;
mGi = gi;
}
@Override
public void onClick(DialogInterface dialog, int which) {
// Cancel check mode
Context context = getContext();
if (null == context) {
return;
}
if (null != mRecyclerView) {
mRecyclerView.outOfCustomChoiceMode();
}
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
if (downloadInfo == null) {
return;
}
String label = which == 0 ? null : mLabels[which];
downloadManager.changeLabel(Collections.singletonList(downloadInfo), label);
}
}
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder>
implements SwipeableItemAdapter<HistoryHolder> {
private final LayoutInflater mInflater;
private final int mListThumbWidth;
private final int mListThumbHeight;
public HistoryAdapter() {
mInflater = getLayoutInflater();
@SuppressLint("InflateParams") View calculator = mInflater.inflate(R.layout.item_gallery_list_thumb_height, null);
ViewUtils.measureView(calculator, 1024, ViewGroup.LayoutParams.WRAP_CONTENT);
mListThumbHeight = calculator.getMeasuredHeight();
mListThumbWidth = mListThumbHeight * 2 / 3;
}
@Override
public long getItemId(int position) {
if (null == mLazyList) {
return super.getItemId(position);
} else {
return mLazyList.get(position).gid;
}
}
@NonNull
@Override
public HistoryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
HistoryHolder holder = new HistoryHolder(mInflater.inflate(R.layout.item_history, parent, false));
ViewGroup.LayoutParams lp = holder.thumb.getLayoutParams();
lp.width = mListThumbWidth;
lp.height = mListThumbHeight;
holder.thumb.setLayoutParams(lp);
return holder;
}
@Override
public void onBindViewHolder(@NonNull HistoryHolder holder, int position) {
if (null == mLazyList) {
return;
}
GalleryInfo gi = mLazyList.get(position);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
holder.title.setText(EhUtils.getSuitableTitle(gi));
holder.uploader.setText(gi.uploader);
holder.rating.setRating(gi.rating);
TextView category = holder.category;
String newCategoryText = EhUtils.getCategory(gi.category);
if (!newCategoryText.contentEquals(category.getText())) {
category.setText(newCategoryText);
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category));
}
holder.posted.setText(gi.posted);
holder.pages.setText(null);
holder.pages.setVisibility(View.GONE);
if (TextUtils.isEmpty(gi.simpleLanguage)) {
holder.simpleLanguage.setText(null);
holder.simpleLanguage.setVisibility(View.GONE);
} else {
holder.simpleLanguage.setText(gi.simpleLanguage);
holder.simpleLanguage.setVisibility(View.VISIBLE);
}
holder.downloaded.setVisibility(mDownloadManager.containDownloadInfo(gi.gid) ? View.VISIBLE : View.GONE);
// Update transition name
long gid = gi.gid;
ViewCompat.setTransitionName(holder.thumb, TransitionNameFactory.getThumbTransitionName(gid));
holder.card.setOnClickListener(v -> onItemClick(holder.itemView, position));
holder.card.setOnLongClickListener(v -> onItemLongClick(position));
}
@Override
public int getItemCount() {
return null != mLazyList ? mLazyList.size() : 0;
}
@Override
public int onGetSwipeReactionType(@NonNull HistoryHolder holder, int position, int x, int y) {
return SwipeableItemConstants.REACTION_CAN_SWIPE_LEFT;
}
@Override
public void onSwipeItemStarted(@NonNull HistoryHolder holder, int position) {
}
@Override
public void onSetSwipeBackground(@NonNull HistoryHolder holder, int position, int type) {
}
@Override
public SwipeResultAction onSwipeItem(@NonNull HistoryHolder holder, int position, int result) {
switch (result) {
case SwipeableItemConstants.RESULT_SWIPED_LEFT:
return new SwipeResultActionClear(position);
case SwipeableItemConstants.RESULT_SWIPED_RIGHT:
case SwipeableItemConstants.RESULT_CANCELED:
case SwipeableItemConstants.RESULT_NONE:
case SwipeableItemConstants.RESULT_SWIPED_DOWN:
case SwipeableItemConstants.RESULT_SWIPED_UP:
default:
return new SwipeResultActionDefault();
}
}
}
private class SwipeResultActionClear extends SwipeResultActionRemoveItem {
private final int mPosition;
protected SwipeResultActionClear(int position) {
mPosition = position;
}
@Override
protected void onPerformAction() {
super.onPerformAction();
if (null == mLazyList || null == mAdapter) {
return;
}
HistoryInfo info = mLazyList.get(mPosition);
EhDB.deleteHistoryInfo(info);
updateLazyList();
mAdapter.notifyDataSetChanged();
updateView(true);
}
}
}
@@ -0,0 +1,489 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.view.*
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.lifecycleScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingDataAdapter
import androidx.paging.cachedIn
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.easyrecyclerview.EasyRecyclerView
import com.hippo.easyrecyclerview.FastScroller
import com.hippo.easyrecyclerview.HandlerDrawable
import com.hippo.easyrecyclerview.MarginItemDecoration
import com.hippo.ehviewer.*
import com.hippo.ehviewer.client.EhCacheKeyFactory
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.dao.HistoryInfo
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.GalleryActivity
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
import com.hippo.ehviewer.widget.SimpleRatingView
import com.hippo.scene.Announcer
import com.hippo.scene.SceneFragment
import com.hippo.view.ViewTransition
import com.hippo.widget.LoadImageView
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager
import com.hippo.yorozuya.ViewUtils
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import rikka.core.res.resolveColor
class HistoryScene : ToolbarScene() {
private var mRecyclerView: EasyRecyclerView? = null
private var mViewTransition: ViewTransition? = null
private var mAdapter: HistoryAdapter? = null
private var mDownloadManager: DownloadManager? = null
private var mDownloadInfoListener: DownloadInfoListener? = null
private var mFavouriteStatusRouter: FavouriteStatusRouter? = null
private var mFavouriteStatusRouterListener: FavouriteStatusRouter.Listener? = null
override fun onDestroy() {
super.onDestroy()
mDownloadManager!!.removeDownloadInfoListener(mDownloadInfoListener)
mFavouriteStatusRouter!!.removeListener(mFavouriteStatusRouterListener)
}
override fun getNavCheckedItem(): Int {
return R.id.nav_history
}
@SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val context = requireContext()
mDownloadManager = EhApplication.getDownloadManager(context)
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context)
mDownloadInfoListener = object : DownloadInfoListener {
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
mAdapter?.notifyDataSetChanged()
}
override fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>) {}
override fun onUpdateAll() {}
override fun onReload() {
mAdapter?.notifyDataSetChanged()
}
override fun onChange() {
mAdapter?.notifyDataSetChanged()
}
override fun onRenameLabel(from: String, to: String) {}
override fun onRemove(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
mAdapter?.notifyDataSetChanged()
}
override fun onUpdateLabels() {}
}
mDownloadManager!!.addDownloadInfoListener(mDownloadInfoListener)
mFavouriteStatusRouterListener = FavouriteStatusRouter.Listener { _: Long, _: Int ->
mAdapter?.notifyDataSetChanged()
}
mFavouriteStatusRouter!!.addListener(mFavouriteStatusRouterListener)
}
override fun onCreateViewWithToolbar(
inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.scene_history, container, false)
val content = ViewUtils.`$$`(view, R.id.content)
val recyclerView = ViewUtils.`$$`(content, R.id.recycler_view) as EasyRecyclerView
val mFastScroller = ViewUtils.`$$`(content, R.id.fast_scroller) as FastScroller
val mTip = ViewUtils.`$$`(view, R.id.tip) as TextView
mViewTransition = ViewTransition(content, mTip)
val resources = requireContext().resources
val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.big_history)
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
mTip.setCompoundDrawables(null, drawable, null, null)
val historyData = Pager(
PagingConfig(20)
) {
EhDB.getHistoryLazyList()
}.flow.cachedIn(viewLifecycleOwner.lifecycleScope)
mAdapter = HistoryAdapter(object : DiffUtil.ItemCallback<HistoryInfo>() {
override fun areItemsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
return oldItem.gid == newItem.gid
}
override fun areContentsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
return oldItem.gid == newItem.gid
}
})
recyclerView.adapter = mAdapter
val layoutManager = AutoStaggeredGridLayoutManager(
0, StaggeredGridLayoutManager.VERTICAL
)
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()))
layoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE)
recyclerView.layoutManager = layoutManager
recyclerView.clipToPadding = false
recyclerView.clipChildren = false
val interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval)
val paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h)
val paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v)
val decoration = MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV)
recyclerView.addItemDecoration(decoration)
val itemTouchHelper = ItemTouchHelper(HistoryItemTouchHelperCallback())
itemTouchHelper.attachToRecyclerView(recyclerView)
mFastScroller.attachToRecyclerView(recyclerView)
mRecyclerView = recyclerView
val handlerDrawable = HandlerDrawable()
handlerDrawable.setColor(theme.resolveColor(com.google.android.material.R.attr.colorPrimary))
mFastScroller.setHandlerDrawable(handlerDrawable)
viewLifecycleOwner.lifecycleScope.launch {
historyData.collectLatest { value ->
mAdapter!!.submitData(
value
)
}
}
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setTitle(R.string.history)
setNavigationIcon(R.drawable.ic_baseline_menu_24)
}
override fun onDestroyView() {
super.onDestroyView()
mRecyclerView?.stopScroll()
mRecyclerView = null
mViewTransition = null
mAdapter = null
}
@SuppressLint("RtlHardcoded")
override fun onNavigationClick() {
toggleDrawer(Gravity.LEFT)
}
override fun getMenuResId(): Int {
return R.menu.scene_history
}
@SuppressLint("NotifyDataSetChanged")
private fun showClearAllDialog() {
MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.clear_all_history)
.setPositiveButton(R.string.clear_all) { _: DialogInterface?, which: Int ->
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
return@setPositiveButton
}
EhDB.clearHistoryInfo()
mAdapter!!.refresh()
mAdapter!!.notifyDataSetChanged()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
// Skip when in choice mode
context ?: return false
val id = item.itemId
if (id == R.id.action_clear_all) {
showClearAllDialog()
return true
}
return false
}
fun onItemClick(position: Int): Boolean {
val args = Bundle()
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO)
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, mAdapter?.peek(position))
val announcer = Announcer(GalleryDetailScene::class.java).setArgs(args)
startScene(announcer)
return true
}
fun onItemLongClick(position: Int): Boolean {
val context = requireContext()
val activity = mainActivity ?: return false
val gi: GalleryInfo = mAdapter?.peek(position) ?: return true
val downloaded = mDownloadManager!!.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID
val favourited = gi.favoriteSlot != -2
val items = if (downloaded) arrayOf<CharSequence>(
context.getString(R.string.read),
context.getString(R.string.delete_downloads),
context.getString(if (favourited) R.string.remove_from_favourites else R.string.add_to_favourites),
context.getString(R.string.download_move_dialog_title)
) else arrayOf<CharSequence>(
context.getString(R.string.read),
context.getString(R.string.download),
context.getString(if (favourited) R.string.remove_from_favourites else R.string.add_to_favourites),
)
val icons = if (downloaded) intArrayOf(
R.drawable.v_book_open_x24,
R.drawable.v_delete_x24,
if (favourited) R.drawable.v_heart_broken_x24 else R.drawable.v_heart_x24,
R.drawable.v_folder_move_x24
) else intArrayOf(
R.drawable.v_book_open_x24,
R.drawable.v_download_x24,
if (favourited) R.drawable.v_heart_broken_x24 else R.drawable.v_heart_x24,
)
MaterialAlertDialogBuilder(context)
.setTitle(EhUtils.getSuitableTitle(gi))
.setAdapter(
SelectItemWithIconAdapter(
context,
items,
icons
)
) { _: DialogInterface?, which: Int ->
when (which) {
0 -> {
val intent = Intent(activity, GalleryActivity::class.java)
intent.action = GalleryActivity.ACTION_EH
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
startActivity(intent)
}
1 -> if (downloaded) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_remove_dialog_title)
.setMessage(
getString(
R.string.download_remove_dialog_message,
gi.title
)
)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
mDownloadManager!!.deleteDownload(
gi.gid
)
}
.show()
} else {
CommonOperations.startDownload(activity, gi, false)
}
2 -> if (favourited) {
CommonOperations.removeFromFavorites(
activity,
gi,
RemoveFromFavoriteListener(context, activity.stageId, tag)
)
} else {
CommonOperations.addToFavorites(
activity,
gi,
AddToFavoriteListener(context, activity.stageId, tag)
)
}
3 -> {
val labelRawList = EhApplication.getDownloadManager(context).labelList
val labelList: MutableList<String> = ArrayList(labelRawList.size + 1)
labelList.add(getString(R.string.default_download_label_name))
var i = 0
val n = labelRawList.size
while (i < n) {
labelList.add(labelRawList[i].label)
i++
}
val labels = labelList.toTypedArray()
val helper = MoveDialogHelper(labels, gi)
MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_move_dialog_title)
.setItems(labels, helper)
.show()
}
}
}.show()
return true
}
private class AddToFavoriteListener(context: Context?, stageId: Int, sceneTag: String?) :
EhCallback<GalleryListScene?, Void?>(context, stageId, sceneTag) {
override fun onSuccess(result: Void?) {
showTip(R.string.add_to_favorite_success, LENGTH_SHORT)
}
override fun onFailure(e: Exception) {
showTip(R.string.add_to_favorite_failure, LENGTH_LONG)
}
override fun onCancel() {}
override fun isInstance(scene: SceneFragment): Boolean {
return scene is GalleryListScene
}
}
private class RemoveFromFavoriteListener(context: Context?, stageId: Int, sceneTag: String?) :
EhCallback<GalleryListScene?, Void?>(context, stageId, sceneTag) {
override fun onSuccess(result: Void?) {
showTip(R.string.remove_from_favorite_success, LENGTH_SHORT)
}
override fun onFailure(e: Exception) {
showTip(R.string.remove_from_favorite_failure, LENGTH_LONG)
}
override fun onCancel() {}
override fun isInstance(scene: SceneFragment): Boolean {
return scene is GalleryListScene
}
}
private class HistoryHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val card: View
val thumb: LoadImageView
val title: TextView
val uploader: TextView
val rating: SimpleRatingView
val category: TextView
val posted: TextView
val simpleLanguage: TextView
val pages: TextView
val downloaded: ImageView
init {
card = itemView.findViewById(R.id.card)
thumb = itemView.findViewById(R.id.thumb)
title = itemView.findViewById(R.id.title)
uploader = itemView.findViewById(R.id.uploader)
rating = itemView.findViewById(R.id.rating)
category = itemView.findViewById(R.id.category)
posted = itemView.findViewById(R.id.posted)
simpleLanguage = itemView.findViewById(R.id.simple_language)
pages = itemView.findViewById(R.id.pages)
downloaded = itemView.findViewById(R.id.downloaded)
}
}
private inner class MoveDialogHelper(
private val mLabels: Array<String>,
private val mGi: GalleryInfo
) : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
mRecyclerView?.outOfCustomChoiceMode()
val downloadManager = EhApplication.getDownloadManager(requireContext())
val downloadInfo = downloadManager.getDownloadInfo(mGi.gid) ?: return
val label = if (which == 0) null else mLabels[which]
downloadManager.changeLabel(listOf(downloadInfo), label)
}
}
private inner class HistoryAdapter(diffCallback: DiffUtil.ItemCallback<HistoryInfo>) :
PagingDataAdapter<HistoryInfo, HistoryHolder>(diffCallback) {
private val mInflater: LayoutInflater = layoutInflater
private val mListThumbWidth: Int
private val mListThumbHeight: Int
init {
@SuppressLint("InflateParams") val calculator =
mInflater.inflate(R.layout.item_gallery_list_thumb_height, null)
ViewUtils.measureView(calculator, 1024, ViewGroup.LayoutParams.WRAP_CONTENT)
mListThumbHeight = calculator.measuredHeight
mListThumbWidth = mListThumbHeight * 2 / 3
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryHolder {
val holder = HistoryHolder(mInflater.inflate(R.layout.item_history, parent, false))
val lp = holder.thumb.layoutParams
lp.width = mListThumbWidth
lp.height = mListThumbHeight
holder.thumb.layoutParams = lp
return holder
}
override fun onBindViewHolder(holder: HistoryHolder, position: Int) {
val gi: GalleryInfo? = getItem(position)
gi ?: return
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb)
holder.title.text = EhUtils.getSuitableTitle(gi)
holder.uploader.text = gi.uploader
holder.rating.rating = gi.rating
val category = holder.category
val newCategoryText = EhUtils.getCategory(gi.category)
if (!newCategoryText!!.contentEquals(category.text)) {
category.text = newCategoryText
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category))
}
holder.posted.text = gi.posted
holder.pages.text = null
holder.pages.visibility = View.GONE
if (TextUtils.isEmpty(gi.simpleLanguage)) {
holder.simpleLanguage.text = null
holder.simpleLanguage.visibility = View.GONE
} else {
holder.simpleLanguage.text = gi.simpleLanguage
holder.simpleLanguage.visibility = View.VISIBLE
}
holder.downloaded.visibility =
if (mDownloadManager!!.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
// Update transition name
val gid = gi.gid
ViewCompat.setTransitionName(
holder.thumb,
TransitionNameFactory.getThumbTransitionName(gid)
)
holder.card.setOnClickListener { onItemClick(position) }
holder.card.setOnLongClickListener { onItemLongClick(position) }
}
}
private inner class HistoryItemTouchHelperCallback : ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
return makeMovementFlags(0, ItemTouchHelper.LEFT)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val mPosition = viewHolder.bindingAdapterPosition
if (mAdapter == null) {
return
}
val info: HistoryInfo = mAdapter!!.peek(mPosition)!!
EhDB.deleteHistoryInfo(info)
mAdapter!!.refresh()
}
}
}
@@ -28,26 +28,14 @@ import com.hippo.scene.Announcer;
*/
public class SolidScene extends BaseScene {
public static final int CHECK_STEP_SECURITY = 0;
public static final int CHECK_STEP_WARNING = 1;
public static final int CHECK_STEP_SIGN_IN = 2;
public static final int CHECK_STEP_SELECT_SITE = 3;
public static final int CHECK_STEP_SIGN_IN = 1;
public static final int CHECK_STEP_SELECT_SITE = 2;
public static final String KEY_TARGET_SCENE = "target_scene";
public static final String KEY_TARGET_ARGS = "target_args";
private static final String TAG = SolidScene.class.getSimpleName();
public void startSceneForCheckStep(int checkStep, Bundle args) {
switch (checkStep) {
case CHECK_STEP_SECURITY:
if (Settings.getShowWarning()) {
startScene(new Announcer(WarningScene.class).setArgs(args), true);
break;
}
case CHECK_STEP_WARNING:
if (EhUtils.needSignedIn(getContext())) {
startScene(new Announcer(SignInScene.class).setArgs(args), true);
break;
}
case CHECK_STEP_SIGN_IN:
if (Settings.getSelectSite()) {
startScene(new Announcer(SelectSiteScene.class).setArgs(args), true);
@@ -1,85 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.ui.annotation.ViewLifeCircle;
import com.hippo.yorozuya.ViewUtils;
public final class WarningScene extends SolidScene implements View.OnClickListener {
@Nullable
private View mReject;
@Nullable
@ViewLifeCircle
private View mAccept;
@Override
public boolean needShowLeftDrawer() {
return false;
}
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_warning, container, false);
mReject = ViewUtils.$$(view, R.id.reject);
mAccept = ViewUtils.$$(view, R.id.accept);
mReject.setOnClickListener(this);
mAccept.setOnClickListener(this);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
mReject = null;
mAccept = null;
}
@Override
public void onClick(View v) {
if (mReject == v) {
finishStage();
} else if (mAccept == v) {
// Never show this warning anymore
Settings.putShowWarning(false);
// Start new scene and finish it self
MainActivity activity = getMainActivity();
if (null != activity) {
startSceneForCheckStep(CHECK_STEP_WARNING, getArguments());
}
finish();
}
}
}
@@ -53,11 +53,6 @@ public class FixedThumb extends LoadImageView {
a.recycle();
}
public void setFix(float minAspect, float maxAspect) {
this.minAspect = minAspect;
this.maxAspect = maxAspect;
}
@Override
public void onPreSetImageDrawable(Drawable drawable, boolean isTarget) {
if (alwaysCutAndScale) {
@@ -57,6 +57,7 @@ public abstract class GalleryProvider {
public void stop() {
OSUtils.checkMainLoop();
mImageCache.evictAll();
Image.lazyEvictAll();
}
public void setGLRoot(GLRoot glRoot) {
@@ -245,18 +246,14 @@ public abstract class GalleryProvider {
}
public void add(Integer key, ImageWrapper value) {
if (value.getFormat() != Image.FORMAT_GIF && value.obtain()) {
put(key, value);
}
if (value.getFormat() != Image.FORMAT_ANIMATED)
if (value.obtain())
put(key, value);
}
@Override
protected int sizeOf(Integer key, ImageWrapper value) {
int size = value.getWidth() * value.getHeight() * 4;
if (value.getFormat() == Image.FORMAT_GIF) {
size *= 5;
}
return size;
return value.getWidth() * value.getHeight() * 4;
}
@Override
@@ -153,12 +153,6 @@ public class ImageTexture implements Texture, Animatable {
}
mTiles = list.toArray(new Tile[list.size()]);
if (!mImage.isCompleted()) {
Runnable runnable = new AnimateRunnable();
mAnimateRunnable = runnable;
sThreadExecutor.execute(runnable);
}
}
private static Tile obtainSmallTile() {
@@ -239,7 +233,7 @@ public class ImageTexture implements Texture, Animatable {
}
boolean end = mReleased.get() || mImage.isImageRecycled() || mNeedRelease.get() ||
(mImage.isCompleted() && mImage.getFrameCount() <= 1) || mRunning.get();
(mImage.getFormat() == 0) || mRunning.get();
synchronized (mImage) {
mImageBusy = false;
@@ -597,25 +591,11 @@ public class ImageTexture implements Texture, Animatable {
mImageBusy = true;
}
if (!mImage.isCompleted()) {
try {
sPVLock.p();
} catch (InterruptedException e) {
// Ignore
}
if (!mNeedRelease.get()) {
mImage.complete();
}
sPVLock.v();
}
int frameCount = mImage.getFrameCount();
synchronized (mImage) {
// Release image
mImageBusy = false;
// Check need release, frameCount <= 1
if (mNeedRelease.get() || frameCount <= 1) {
if (mNeedRelease.get() || mImage.getFormat() == 0) {
mAnimateRunnable = null;
return;
}
@@ -122,22 +122,6 @@ public class ImageWrapper {
return mCut.height();
}
/**
* @see Image#complete()
*/
public boolean complete() {
synchronized (mImage) {
return mImage.complete();
}
}
/**
* @see Image#isCompleted()
*/
public boolean isCompleted() {
return mImage.isCompleted();
}
/**
* @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int)
*/
@@ -168,13 +152,6 @@ public class ImageWrapper {
return mImage.getDelay();
}
/**
* @see Image#getFrameCount()
*/
public int getFrameCount() {
return mImage.getFrameCount();
}
/**
* @see Image#isOpaque()
*/
@@ -0,0 +1,219 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.image;
import android.graphics.Bitmap;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
import java.util.ArrayList;
/**
* The {@code Image} is a image which stored pixel data in native heap
*/
public final class Image {
public static final int FORMAT_NORMAL = 0;
public static final int FORMAT_ANIMATED = 1;
private static final ArrayList<Image> imageList = new ArrayList<>();
private static boolean needEvictAll = false;
private long mNativePtr;
private Image(long nativePtr) {
if (needEvictAll)
evictAll();
mNativePtr = nativePtr;
imageList.add(this);
}
private static Image newFromAddr(long addr) {
if (addr == 0)
return null;
return new Image(addr);
}
public static void lazyEvictAll() {
needEvictAll = true;
}
private static void evictAll() {
(new ArrayList<>(imageList)).forEach(Image::recycle);
needEvictAll = false;
}
/**
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decode(FileDescriptor fd, boolean partially) {
return newFromAddr(nativeDecode(fd));
}
/**
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decode(Integer fd, boolean partially) {
return newFromAddr(nativeDecodeFdInt(fd));
}
/**
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decodeAddr(Long addr, boolean partially) {
return newFromAddr(nativeDecodeAddr(addr));
}
/**
* Create a plain image from Bitmap
*/
@Nullable
public static Image create(Bitmap bitmap) {
return newFromAddr(nativeCreate(bitmap));
}
/**
* Return all un-recycled {@code Image} instance count.
* It is useful for debug.
*/
public static int getImageCount() {
return imageList.size();
}
private static native long nativeDecode(FileDescriptor fd);
private static native long nativeDecodeFdInt(int fd);
private static native long nativeDecodeAddr(long addr);
private static native long nativeCreate(Bitmap bitmap);
private static native void nativeRender(long nativePtr,
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
int width, int height);
private static native void nativeTexImage(long nativePtr, boolean init, int offsetX, int offsetY, int width, int height);
private static native void nativeAdvance(long nativePtr);
private static native int nativeGetDelay(long nativePtr);
private static native boolean nativeIsOpaque(long nativePtr);
private static native void nativeRecycle(long nativePtr);
private static native int nativeGetFormat(long nativePtr);
private static native int nativeGetWidth(long nativePtr);
private static native int nativeGetHeight(long nativePtr);
/**
* Return the format of the image
*/
public int getFormat() {
checkRecycled();
return nativeGetFormat(mNativePtr);
}
/**
* Return the width of the image
*/
public int getWidth() {
checkRecycled();
return nativeGetWidth(mNativePtr);
}
/**
* Return the height of the image
*/
public int getHeight() {
checkRecycled();
return nativeGetHeight(mNativePtr);
}
private void checkRecycled() {
if (mNativePtr == 0) {
throw new IllegalStateException("The image is recycled.");
}
}
/**
* Render the image to {@code Bitmap}
*/
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
int width, int height, boolean fillBlank, int defaultColor) {
checkRecycled();
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height);
}
/**
* Call {@code glTexImage2D} for init is true and
* call {@code glTexSubImage2D} for init is false.
* width * height must <= 512 * 512 or do nothing
*/
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
checkRecycled();
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height);
}
/**
* Move to next frame. Do nothing for non-animation image
*/
public void advance() {
checkRecycled();
nativeAdvance(mNativePtr);
}
/**
* Return current frame delay. 0 for non-animation image
*/
public int getDelay() {
checkRecycled();
int delay = nativeGetDelay(mNativePtr);
return delay <= 10 ? 100 : delay;
}
/**
* Return is the image opaque
*/
public boolean isOpaque() {
checkRecycled();
return nativeIsOpaque(mNativePtr);
}
/**
* Free the native object associated with this image.
* It must be called when the image will not be used.
* The image can't be used after this method is called.
*/
public synchronized void recycle() {
if (mNativePtr != 0) {
nativeRecycle(mNativePtr);
mNativePtr = 0;
imageList.remove(this);
}
}
/**
* Returns true if this image has been recycled.
*/
public boolean isRecycled() {
return mNativePtr == 0;
}
}
@@ -28,6 +28,7 @@ import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.Iterator;
@@ -44,8 +45,6 @@ public final class ImageBitmap implements Animatable, Runnable {
private final Bitmap mBitmap;
private final int mFormat;
private final boolean mIsOpaque;
private final int mByteCount;
private final int mFrameCount;
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
@Nullable
private Image mImage;
@@ -60,10 +59,8 @@ public final class ImageBitmap implements Animatable, Runnable {
image.render(0, 0, mBitmap, 0, 0, width, height, false, 0);
mFormat = image.getFormat();
mIsOpaque = image.isOpaque();
mByteCount = image.getByteCount();
mFrameCount = image.getFrameCount();
if (mFrameCount > 1) {
if (mFormat == 1) {
// For animated image, save image object
mImage = image;
} else {
@@ -73,19 +70,17 @@ public final class ImageBitmap implements Animatable, Runnable {
}
private ImageBitmap(@NonNull Bitmap bitmap) {
mFormat = Image.FORMAT_PLAIN;
mFormat = Image.FORMAT_NORMAL;
mBitmap = bitmap;
mIsOpaque = !bitmap.hasAlpha();
mByteCount = bitmap.getRowBytes() * bitmap.getHeight();
mFrameCount = 1;
}
/**
* Decode {@code InputStream}, then create image.
*/
@Nullable
public static ImageBitmap decode(@NonNull InputStream is) {
Image image = Image.decode(is, false);
public static ImageBitmap decode(@NonNull FileDescriptor fd) {
Image image = Image.decode(fd, false);
if (image != null) {
return create(image);
} else {
@@ -223,13 +218,6 @@ public final class ImageBitmap implements Animatable, Runnable {
return mIsOpaque;
}
/**
* Return byte count of image
*/
public int getByteCount() {
return mByteCount;
}
/**
* Return image is animated
*/
@@ -237,13 +225,6 @@ public final class ImageBitmap implements Animatable, Runnable {
return mImage != null;
}
/**
* Return image frame count
*/
public int getFrameCount() {
return mFrameCount;
}
/**
* Draw image to canvas
*/
@@ -33,6 +33,7 @@ final class DocumentsContractApi21 {
private static final String TAG = DocumentsContractApi21.class.getSimpleName();
private static final String PATH_DOCUMENT = "document";
private static final String PATH_TREE = "tree";
private DocumentsContractApi21() {
}
@@ -41,6 +41,7 @@ class FileDescriptorRandomAccessFile implements UniRandomAccessFile {
private final int flushAfterWrite;
private final Object closeLock = new Object();
private volatile boolean closed = false;
private FileDescriptorRandomAccessFile(ParcelFileDescriptor pfd, FileDescriptor fd, String mode) {
this.pfd = pfd;
this.fd = fd;
@@ -82,6 +82,13 @@ public class BatteryView extends AppCompatTextView {
}
mCurrentColor = color;
super.setTextColor(color);
}
private void startCharger() {
if (!mIsChargerWorking) {
getHandler().post(mCharger);
mIsChargerWorking = true;
}
} private final Runnable mCharger = new Runnable() {
private int level = 0;
@@ -97,13 +104,6 @@ public class BatteryView extends AppCompatTextView {
}
};
private void startCharger() {
if (!mIsChargerWorking) {
getHandler().post(mCharger);
mIsChargerWorking = true;
}
}
private void stopCharger() {
if (mIsChargerWorking) {
getHandler().removeCallbacks(mCharger);
@@ -145,6 +145,7 @@ public class BatteryView extends AppCompatTextView {
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -97,6 +97,10 @@ public class TextClock extends AppCompatTextView {
} else {
mTime = Calendar.getInstance();
}
}
public CharSequence getFormat12Hour() {
return mFormat12;
} private final Runnable mTicker = new Runnable() {
@Override
public void run() {
@@ -109,10 +113,6 @@ public class TextClock extends AppCompatTextView {
}
};
public CharSequence getFormat12Hour() {
return mFormat12;
}
public void setFormat12Hour(CharSequence format) {
mFormat12 = format;
@@ -144,19 +144,7 @@ public class TextClock extends AppCompatTextView {
createTime(timeZone);
onTimeChanged();
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
chooseFormat();
onTimeChanged();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
chooseFormat();
onTimeChanged();
}
};
}
/**
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
@@ -170,7 +158,19 @@ public class TextClock extends AppCompatTextView {
public CharSequence getFormat() {
return mFormat;
}
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
chooseFormat();
onTimeChanged();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
chooseFormat();
onTimeChanged();
}
};
/**
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
@@ -1,38 +1,51 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 GeneralPublic 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group
android:scaleX="0.37947997"
android:scaleY="0.37947997"
android:translateX="35.1"
android:translateY="39.584126">
<path
android:fillColor="@color/ic_launcher_foreground"
android:fillType="evenOdd"
android:pathData="M39.893,68.066L39.893,75.977L0,75.977L0,4.59L39.893,4.59L39.893,12.5L8.984,12.5L8.984,34.766L38.086,34.766L38.086,42.578L8.984,42.578L8.984,68.066L39.893,68.066Z" />
<path
android:fillColor="@color/ic_launcher_foreground"
android:fillType="evenOdd"
android:pathData="M99.609,75.977L90.918,75.977L90.918,41.699A23.436,23.436 0,0 0,90.662 38.109Q90.055,34.207 88.013,31.958Q85.629,29.334 81.028,28.863A20.84,20.84 0,0 0,78.906 28.76A23.134,23.134 0,0 0,74.45 29.16Q71.926,29.656 69.994,30.767A11.122,11.122 0,0 0,66.968 33.301A13.32,13.32 0,0 0,64.756 37.276Q63.399,40.971 63.213,46.495A53.517,53.517 0,0 0,63.184 48.291L63.184,75.977L54.395,75.977L54.395,0L63.184,0L63.184,22.266A66.935,66.935 0,0 1,63.115 25.383Q63.043,26.914 62.897,28.251A33.547,33.547 0,0 1,62.695 29.785L63.281,29.785A16.11,16.11 0,0 1,69.394 24.035A19.448,19.448 0,0 1,70.068 23.682A20.88,20.88 0,0 1,77.292 21.622A25.763,25.763 0,0 1,79.98 21.484A32.643,32.643 0,0 1,85.483 21.917Q91.249,22.905 94.678,26.147Q98.472,29.735 99.347,36.631A35.274,35.274 0,0 1,99.609 41.064L99.609,75.977Z" />
</group>
<group android:scaleX="0.7425"
android:scaleY="0.7425"
android:translateX="30.68"
android:translateY="30.68">
<path
android:pathData="M11,9H34"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#FBBD06"
android:strokeLineCap="round"/>
<path
android:pathData="M11,54H23"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#FBBD06"
android:strokeLineCap="round"/>
<path
android:pathData="M11,9L11,54"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#35AA53"
android:strokeLineCap="round"/>
<path
android:pathData="M11,31L52,31"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#FBBD06"
android:strokeLineCap="round"/>
<path
android:pathData="M52,31L52,54"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#4586F3"
android:strokeLineCap="round"/>
<path
android:pathData="M34,21L34,54"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#EB4334"
android:strokeLineCap="round"/>
<path
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z"
android:fillColor="#FBBD06"
android:fillType="evenOdd"/>
</group>
</vector>
@@ -0,0 +1,55 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.7425"
android:scaleY="0.7425"
android:translateX="30.68"
android:translateY="30.68">
<path
android:pathData="M11,9H34"
android:strokeAlpha="0.8"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M11,54H23"
android:strokeAlpha="0.8"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M11,9L11,54"
android:strokeAlpha="0.8"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M11,31L52,31"
android:strokeAlpha="0.8"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M52,31L52,54"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M34,21L34,54"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
<path
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z"
android:fillColor="#ffffff"
android:fillType="evenOdd"/>
</group>
</vector>
@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ 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/>.
~
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/actions"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="@dimen/keyline_margin">
<TextView
android:id="@+id/newerVersion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center"
android:padding="8dp"
android:text="@string/newer_version_avaliable"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/keyline_margin"
android:paddingRight="@dimen/keyline_margin">
<LinearLayout
android:id="@+id/rate"
android:layout_width="484dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<androidx.appcompat.widget.AppCompatRatingBar
android:id="@+id/rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:isIndicator="true"
android:theme="@style/RatingBarTheme" />
<TextView
android:id="@+id/rating_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="0dp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<HorizontalScrollView
android:id="@+id/actions_scroll_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clipToPadding="false"
android:paddingLeft="36dp"
android:paddingRight="@dimen/keyline_margin"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:divider="@drawable/spacer_keyline"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:showDividers="middle">
<FrameLayout
android:id="@+id/heart_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:nextFocusUp="@+id/download">
<TextView
android:id="@+id/heart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="4dp"
android:gravity="center_horizontal"
android:text="@string/favorited"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/heart_outline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="4dp"
android:gravity="center_horizontal"
android:text="@string/not_favorited"
android:textColor="?android:attr/textColorPrimary" />
</FrameLayout>
<TextView
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusUp="@+id/download"
android:text="@string/share"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/torrent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusUp="@+id/download"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/archive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusUp="@+id/download"
android:text="@string/archive"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/similar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusUp="@+id/download"
android:text="@string/similar_gallery"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/search_cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusRight="@+id/search_cover"
android:nextFocusUp="@+id/download"
android:text="@string/search_cover"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,263 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ 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/>.
~
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/color_bg"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:elevation="4dp"
app:fitsSystemWindowsInsets="top" />
<Space
android:layout_width="match_parent"
android:layout_height="28dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/title"
style="@style/TextAppearance.Material3.HeadlineSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
android:ellipsize="end"
android:maxLines="5" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="540dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginTop="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
android:layout_marginBottom="@dimen/keyline_margin"
app:fitsSystemWindowsInsets="top">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/header_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.hippo.widget.LoadImageView
android:id="@+id/thumb"
android:layout_width="256dp"
android:layout_height="@dimen/gallery_detail_thumb_height" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/info"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/keyline_margin"
android:clickable="true"
android:focusable="true"
android:nextFocusRight="@+id/read"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="@+id/language"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/size"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="right"
android:singleLine="true"
app:layout_constraintBaseline_toBaselineOf="@id/language"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/language" />
<TextView
android:id="@+id/favoredTimes"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="left"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/language" />
<TextView
android:id="@+id/pages"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="right"
android:singleLine="true"
app:layout_constraintBaseline_toBaselineOf="@id/favoredTimes"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/favoredTimes" />
<TextView
android:id="@+id/posted"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="left"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/favoredTimes" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.chip.Chip
android:id="@+id/uploader"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/keyline_margin"
android:ellipsize="end"
android:nextFocusRight="@+id/read"
android:nextFocusDown="@+id/rate"
android:singleLine="true"
android:textAllCaps="true"
app:chipIcon="@drawable/file_upload_black_24dp"
app:ensureMinTouchTargetSize="true"
app:layout_constraintBottom_toBottomOf="@id/thumb_card"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/category"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/keyline_margin"
android:ellipsize="end"
android:nextFocusRight="@+id/read"
android:singleLine="true"
android:textAllCaps="true"
app:chipIcon="@drawable/ic_baseline_label_24"
app:ensureMinTouchTargetSize="true"
app:layout_constraintBottom_toTopOf="@id/uploader"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/action_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
android:orientation="vertical">
<Button
android:id="@+id/read"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:insetLeft="40dp"
android:insetTop="16dp"
android:insetRight="40dp"
android:text="@string/read">
<requestFocus />
</Button>
<Button
android:id="@+id/download"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:insetLeft="40dp"
android:insetTop="16dp"
android:insetRight="40dp"
android:nextFocusDown="@+id/torrent" />
<android.widget.Space
android:layout_width="match_parent"
android:layout_height="80dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
@@ -1,17 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ 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
~ This file is part of EhViewer
~
~ http://www.apache.org/licenses/LICENSE-2.0
~ 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/>.
~
~ 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -1,17 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ 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
~ This file is part of EhViewer
~
~ http://www.apache.org/licenses/LICENSE-2.0
~ 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/>.
~
~ 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -36,10 +39,10 @@
<ImageView
android:layout_width="257dp"
android:layout_height="518dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@mipmap/ic_launcher" />
app:srcCompat="@drawable/ic_launcher_foreground" />
</LinearLayout>
@@ -99,6 +102,15 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
style="@style/TextAppearance.Material3.LabelLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingTop="24dp"
android:maxWidth="@dimen/single_max_width"
android:text="@string/app_waring"/>
<Space
android:layout_width="match_parent"
android:layout_height="40dp" />
@@ -1,17 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ 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
~ This file is part of EhViewer
~
~ http://www.apache.org/licenses/LICENSE-2.0
~ 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/>.
~
~ 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -1,98 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:orientation="vertical"
app:fitsSystemWindowsInsets="top|bottom">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="480dp"
android:layout_height="match_parent"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="108dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingStart="128dp"
android:paddingTop="16dp"
android:paddingEnd="12dp"
android:text="@string/app_waring"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"
app:drawableTopCompat="@drawable/v_alert_red_x144" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="224dp"
android:layout_weight="1" />
<Button
android:id="@+id/reject"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:insetLeft="80dp"
android:insetRight="80dp"
android:insetBottom="16dp"
android:text="@string/reject" />
<Button
android:id="@+id/accept"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:insetLeft="80dp"
android:insetRight="80dp"
android:insetBottom="16dp"
android:text="@string/accept" />
<Space
android:layout_width="match_parent"
android:layout_height="176dp"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
@@ -64,21 +64,24 @@
android:layout_marginBottom="@dimen/keyline_margin"
app:fitsSystemWindowsInsets="top">
<RelativeLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/header_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Elevated"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.hippo.widget.LoadImageView
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="@dimen/gallery_detail_thumb_width"
android:layout_height="@dimen/gallery_detail_thumb_height" />
android:layout_height="@dimen/gallery_detail_thumb_height"
app:alwaysCutAndScale="true"/>
</com.google.android.material.card.MaterialCardView>
@@ -87,12 +90,13 @@
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/keyline_margin"
android:clickable="true"
android:focusable="true"
android:orientation="vertical">
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
@@ -101,6 +105,7 @@
<TextView
android:id="@+id/language"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
@@ -110,6 +115,7 @@
<TextView
android:id="@+id/size"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@@ -123,6 +129,7 @@
<TextView
android:id="@+id/favoredTimes"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -133,6 +140,7 @@
<TextView
android:id="@+id/pages"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@@ -146,6 +154,7 @@
<TextView
android:id="@+id/posted"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -165,30 +174,31 @@
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/thumb_card"
android:layout_alignParentEnd="true"
android:layout_marginEnd="@dimen/keyline_margin"
android:ellipsize="end"
android:singleLine="true"
android:textAllCaps="true"
app:chipIcon="@drawable/file_upload_black_24dp"
app:ensureMinTouchTargetSize="true" />
app:ensureMinTouchTargetSize="true"
app:layout_constraintBottom_toBottomOf="@id/thumb_card"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/category"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/uploader"
android:layout_alignParentEnd="true"
android:layout_marginEnd="@dimen/keyline_margin"
android:ellipsize="end"
android:singleLine="true"
android:textAllCaps="true"
app:chipIcon="@drawable/ic_baseline_label_24"
app:ensureMinTouchTargetSize="true" />
app:chipSpacingHorizontal="8dp"
app:ensureMinTouchTargetSize="true"
app:layout_constraintBottom_toTopOf="@id/uploader"
app:layout_constraintEnd_toEndOf="parent" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
@@ -205,19 +215,17 @@
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:clickable="true"
android:focusable="true" />
<android.widget.Space
android:layout_width="8dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/read"
style="@style/Widget.Material3.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
@@ -15,6 +15,7 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
@@ -31,15 +32,15 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height">
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height"
app:alwaysCutAndScale="true"
app:retryType="click" />
app:retryType="click"/>
</com.google.android.material.card.MaterialCardView>
+22 -10
View File
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@mipmap/ic_launcher" />
app:srcCompat="@drawable/ic_launcher_foreground" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_layout"
@@ -83,6 +83,15 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
style="@style/TextAppearance.Material3.LabelLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingTop="24dp"
android:maxWidth="@dimen/single_max_width"
android:text="@string/app_waring"/>
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
@@ -95,27 +104,30 @@
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="vertical"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="4dp">
<Button
android:id="@+id/sign_in"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sign_in" />
<Button
android:id="@+id/register"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:text="@string/register" />
<Button
android:id="@+id/sign_in"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:text="@string/sign_in" />
</LinearLayout>
<LinearLayout
-82
View File
@@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingHorizontal="@dimen/keyline_margin"
android:paddingTop="@dimen/keyline_margin"
app:fitsSystemWindowsInsets="top|bottom">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@drawable/v_alert_red_x48" />
<ScrollView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:clipToPadding="false"
android:paddingVertical="@dimen/keyline_margin">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="32dp"
android:text="@string/app_waring"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_little_small" />
</ScrollView>
<LinearLayout
android:id="@+id/buttonPanel"
style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="20dp">
<Button
android:id="@+id/reject"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:text="@string/reject" />
<Button
android:id="@+id/accept"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:text="@string/accept" />
</LinearLayout>
</LinearLayout>
@@ -15,7 +15,9 @@
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@android:color/white"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>
@@ -15,7 +15,9 @@
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@android:color/white"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 B

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Loaded 100 of 184 files, more files were not shown because too many files have changed in this diff. Show more