Compare commits

...
124 Commits
Author SHA1 Message Date
tarsin 7af1e168a4 Bump Version
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-05-23 20:46:46 +08:00
tarsin c8bf34e0d7 Switch to md3 switch 2022-05-23 20:46:09 +08:00
tarsin 86d2282cd6 native: Reformat code 2022-05-23 20:04:04 +08:00
tarsin cfdb23d6dc app: Reformat code 2022-05-23 19:59:40 +08:00
tarsin 1874ddd15b Add nativelib version check 2022-05-23 19:58:16 +08:00
tarsin a004c308cc Rename .java to .kt 2022-05-23 19:58:16 +08:00
tarsin 863337c789 Support fskip for better decompress perf
* Actually the whole impl can't be more sucks, will import algorithm to manage buffer and read strategy further
2022-05-23 17:34:59 +08:00
tarsin 64d91eed33 Upgrade libjpeg-turbo to 2.1.3 2022-05-23 14:55:15 +08:00
tarsin d0c3a3e14d Refactor cmake 2022-05-23 11:36:51 +08:00
tarsin 464636d87d Add zstd support 2022-05-23 11:28:28 +08:00
tarsin 0b0a1f456f Reduce supported ext
* hippo's legacy glgallery only support these four extensions, further support need we migrate to coil and drop glgallery, in progress
2022-05-23 08:40:15 +08:00
tarsin 302030ca65 Update README.md 2022-05-23 08:28:38 +08:00
tarsin 3e63738542 Make a release
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-05-23 08:21:14 +08:00
tarsin d6823c9c8e Checkout to our newly patched libarchive 2022-05-23 08:04:59 +08:00
tarsin 6bc3866660 Build lzma
FIXME: Don't hack!!
https://github.com/asuka-mio/libarchive/commit/588598490ad0a80e44554ed5e608a9f9878220c0
2022-05-23 08:02:20 +08:00
tarsin f41f4dd8ba Support 7z read 2022-05-23 07:31:58 +08:00
tarsin cde39812ce Update proguard-rules.pro 2022-05-23 01:12:58 +08:00
tarsin 03c2abc901 Merge pull request #34 from txyyh/feature/Button
Use Material3 Button instead
2022-05-23 00:59:14 +08:00
tarsin 7646334525 Don't obfuscate native code 2022-05-23 00:59:00 +08:00
txyyh f8d1cd3fab Use Material3 Button instead 2022-05-23 00:47:28 +08:00
tarsin 4ea94be64a Update releases.yml 2022-05-23 00:44:56 +08:00
tarsin dc0470e75a Bump Version
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-05-23 00:34:33 +08:00
tarsin 5d49ecdd7d Add lzma xz v5.2.5 2022-05-23 00:29:01 +08:00
tarsin ef2441116d Use more friendly suffix recogniser and support all image file 2022-05-22 23:17:35 +08:00
tarsin 207909c7be Initial libarchive simple support 2022-05-22 22:27:31 +08:00
tarsin ddb3dd2d95 Merge pull request #33 from txyyh/feature/buildguide
Add build guide
2022-05-22 02:17:38 +08:00
txyyh 8bb980fe97 Add build guide 2022-05-22 01:55:38 +08:00
tarsin 3c3a201ed8 Merge pull request #30 from txyyh/feature/README
Rewrite README header
2022-05-22 01:31:13 +08:00
tarsin 81bb37e929 Merge pull request #31 from asuka-mio/renovate/org.jboss.forge.roaster-roaster-api-2.x
Update dependency org.jboss.forge.roaster:roaster-api to v2.25.0.Final
2022-05-22 01:30:54 +08:00
Renovate Bot ae34690eb2 Update dependency org.jboss.forge.roaster:roaster-api to v2.25.0.Final 2022-05-21 17:30:16 +00:00
tarsin 285b0f5a69 Merge pull request #32 from asuka-mio/renovate/org.jboss.forge.roaster-roaster-jdt-2.x
Update dependency org.jboss.forge.roaster:roaster-jdt to v2.25.0.Final
2022-05-22 01:29:43 +08:00
Renovate Bot a5327fce07 Update dependency org.jboss.forge.roaster:roaster-jdt to v2.25.0.Final 2022-05-21 15:11:11 +00:00
txyyh a4b750f9cd Add description title 2022-05-21 21:58:53 +08:00
txyyh 943344d4bb Rewrite shields 2022-05-21 21:58:50 +08:00
txyyh 79f1b92960 Rewrite icon header 2022-05-21 21:26:48 +08:00
tarsin 575ae38a28 Re-checkout 2022-05-21 20:46:25 +08:00
tarsin f90e2a5c80 Checkout to our libarchive 2022-05-21 20:34:06 +08:00
tarsin 4d9b2877fe Build libarchive 2022-05-21 20:08:03 +08:00
tarsin 4a955947a2 Add libarchive 2022-05-21 19:41:09 +08:00
tarsin 7307c136ee Drop useless native part 2022-05-21 17:24:03 +08:00
tarsin 657e1b1d0f Drop a7zip for libarchive integrate 2022-05-21 17:04:27 +08:00
tarsin 43edf11275 Bump Version
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-05-21 00:10:34 +08:00
tarsin 32ec4c6715 Merge pull request #29 from txyyh/feature/AutoSign
AutoSign and Releases
2022-05-21 00:04:58 +08:00
txyyh 84ac57d69e Releases with tag 2022-05-20 23:57:51 +08:00
txyyh 328e7745b7 Add step name 2022-05-20 23:23:14 +08:00
txyyh ccf22eaef4 Upload right artifact 2022-05-20 23:23:14 +08:00
txyyh c89d42f368 Use keystore in project 2022-05-20 23:23:14 +08:00
txyyh 41192ea7f5 Format 2022-05-20 23:23:14 +08:00
tarsin 7840987a7c native cmake tweaks 2022-05-20 22:59:23 +08:00
tarsin fcbe0a7f55 layout tweaks 2022-05-20 20:33:37 +08:00
tarsin f12894c95d Extract help message from add action 2022-05-20 20:32:15 +08:00
tarsin 40f139fda2 Merge pull request #28 from txyyh/master
Change gallery_detail button theme
2022-05-20 17:32:01 +08:00
txyyh b8507a371e Button side-by-side 2022-05-20 14:32:57 +08:00
txyyh e5a71ce906 Use TextButton instead 2022-05-20 14:32:57 +08:00
txyyh f9e708fe1e Disable bold text 2022-05-20 14:32:57 +08:00
txyyh 50ba6f2bd2 Use elevated instead 2022-05-20 14:32:57 +08:00
tarsin 2484371f8c Import compose activity 2022-05-20 13:42:02 +08:00
tarsin ba675c4212 Use SwitchPreferenceCompat 2022-05-20 13:40:52 +08:00
tarsin 6f9bc3135a Import Jetpack Compose 2022-05-20 13:04:54 +08:00
tarsin 4475d4d018 Search layout tweaks 2022-05-20 12:31:00 +08:00
tarsin 08e45db49d Update README.md 2022-05-20 01:34:25 +08:00
tarsin 85c76065a5 Delete codeql-analysis.yml 2022-05-20 01:28:53 +08:00
tarsin 13582c3f1b Create codeql-analysis.yml 2022-05-20 01:24:27 +08:00
tarsin ae941a4955 Bump Version
EhViewer / Build (push) Has been cancelled
2022-05-20 00:39:47 +08:00
tarsin 72da7ebfe4 Fix search crash 2022-05-20 00:39:07 +08:00
tarsin 8e1bacbfac Refactor Search 2022-05-19 23:59:18 +08:00
tarsin 747c19c028 Rename .java to .kt 2022-05-19 23:59:18 +08:00
txyyh aca83fc1ee Cut photo edges 2022-05-19 21:25:36 +08:00
tarsin 35babd089d Image search layout tweaks 2022-05-19 21:06:23 +08:00
tarsin 457ea56aab Reformat code 2022-05-19 20:18:59 +08:00
txyyh ed22de2360 Cut photo background 2022-05-19 20:17:13 +08:00
tarsin 8d386b44bc Kill redundant things in navmain 2022-05-19 20:16:35 +08:00
tarsin c4c88c40c5 Surround Image with round corner cardview 2022-05-19 20:07:37 +08:00
tarsin e637ad6acb Honor uploader with Chip 2022-05-19 19:48:27 +08:00
tarsin 3b9e08e1c7 Don't disturb nekochan 2022-05-19 18:30:34 +08:00
tarsin 4a2aa8a975 Update README.md 2022-05-19 14:14:42 +08:00
txyyh 9c08eda38a Rewrite README commit
Rewrite commit 713d972d16 to make it look normal
2022-05-19 13:18:01 +08:00
tarsin 713d972d16 Update README.md 2022-05-19 00:46:49 +08:00
tarsin d4b3705a9e Bump version
EhViewer / Build (push) Has been cancelled
2022-05-19 00:30:50 +08:00
tarsin c543d1540c Drop unused res
* Use dyn color as much as possible, try avoid hardcode color schemes
2022-05-19 00:28:35 +08:00
tarsin 2956ff08bd Fix black theme 2022-05-19 00:16:01 +08:00
tarsin ff09dec4e4 Refine color schemes 2022-05-19 00:00:10 +08:00
tarsin 5e02479aa5 inline a blank class 2022-05-18 22:33:06 +08:00
tarsin 6157b9cf82 Drop e-ink mode 2022-05-18 22:26:21 +08:00
tarsin 9260633e26 Merge pull request #27 from txyyh/master
Change download section in README
2022-05-18 21:23:41 +08:00
txyyh 1a2ae6b1a8 Redirect download link 2022-05-18 17:42:49 +08:00
txyyh 282b4adaff Move download link below introduce 2022-05-18 17:40:47 +08:00
tarsin 8519b5a096 New screenshot 2022-05-18 16:10:52 +08:00
tarsin 4a00be922e Merge pull request #25 from asuka-mio/renovate/actions-checkout-3.x
Update actions/checkout action to v3
2022-05-18 15:54:45 +08:00
tarsin 31f30cf1b1 Merge pull request #26 from asuka-mio/renovate/dev.rikka.rikkax.material-material-2.x
Update dependency dev.rikka.rikkax.material:material to v2
2022-05-18 15:54:34 +08:00
Renovate Bot 62737cc32f Update dependency dev.rikka.rikkax.material:material to v2 2022-05-18 07:08:08 +00:00
Renovate Bot c7306b2358 Update actions/checkout action to v3 2022-05-18 07:08:05 +00:00
tarsin 779958dfeb Merge pull request #20 from asuka-mio/renovate/com.squareup.okhttp3-okhttp-dnsoverhttps-4.x
Update dependency com.squareup.okhttp3:okhttp-dnsoverhttps to v4.9.3
2022-05-18 14:56:35 +08:00
tarsin d0ca6872b0 Merge pull request #23 from asuka-mio/renovate/actions-setup-java-3.x
Update actions/setup-java action to v3
2022-05-18 14:56:11 +08:00
tarsin 161f191f3d Merge pull request #21 from asuka-mio/renovate/org.jboss.forge.roaster-roaster-api-2.x
Update dependency org.jboss.forge.roaster:roaster-api to v2.24.0.Final
2022-05-18 14:55:59 +08:00
tarsin 2cfa46ed05 Revert "Update dependency com.github.seven332:yorozuya-thread to v0.1.2"
This reverts commit e19759a7e4.
2022-05-18 14:52:42 +08:00
tarsin 005e2b3fa1 Revert "Update dependency com.github.seven332:glview to v0.1.4"
This reverts commit 2fd688ff9e.
2022-05-18 14:49:02 +08:00
tarsin 3826dfb1d4 Revert "Update dependency com.github.seven332:beerbelly to v0.2.1"
This reverts commit b0274946bf.
2022-05-18 14:43:58 +08:00
Renovate Bot 81bb9a852f Update actions/setup-java action to v3 2022-05-18 06:22:35 +00:00
Renovate Bot 37e60673d7 Update dependency org.jboss.forge.roaster:roaster-api to v2.24.0.Final 2022-05-18 06:22:29 +00:00
Renovate Bot 9b618f7ecc Update dependency com.squareup.okhttp3:okhttp-dnsoverhttps to v4.9.3 2022-05-18 06:22:26 +00:00
tarsin 0ce3ceb900 Merge pull request #14 from asuka-mio/renovate/org.jboss.forge.roaster-roaster-jdt-2.x
Update dependency org.jboss.forge.roaster:roaster-jdt to v2.24.0.Final
2022-05-18 14:20:56 +08:00
tarsin 4833226241 Merge pull request #12 from asuka-mio/renovate/dev.rikka.rikkax.core-core-1.x
Update dependency dev.rikka.rikkax.core:core to v1.4.0
2022-05-18 14:20:45 +08:00
tarsin 5cae964a1e Merge pull request #10 from asuka-mio/renovate/com.github.seven332-beerbelly-0.x
Update dependency com.github.seven332:beerbelly to v0.2.1
2022-05-18 14:20:34 +08:00
tarsin 70eaa35bc9 Merge pull request #11 from asuka-mio/renovate/com.github.seven332-glview-0.x
Update dependency com.github.seven332:glview to v0.1.4
2022-05-18 14:20:21 +08:00
tarsin 38bd3fede1 Merge pull request #15 from asuka-mio/renovate/org.jsoup-jsoup-1.x
Update dependency org.jsoup:jsoup to v1.15.1
2022-05-18 14:20:09 +08:00
tarsin e1e32a7f5e Merge pull request #9 from asuka-mio/renovate/gradle-7.x
Update dependency gradle to v7.4.2
2022-05-18 14:19:57 +08:00
tarsin b77d0fb47a Update android.yml 2022-05-18 14:02:02 +08:00
Renovate Bot 4076b2f900 Update dependency org.jsoup:jsoup to v1.15.1 2022-05-18 05:53:38 +00:00
Renovate Bot 1bc409b54a Update dependency org.jboss.forge.roaster:roaster-jdt to v2.24.0.Final 2022-05-18 05:53:35 +00:00
Renovate Bot 1247a0ade9 Update dependency dev.rikka.rikkax.core:core to v1.4.0 2022-05-18 05:53:29 +00:00
Renovate Bot 2fd688ff9e Update dependency com.github.seven332:glview to v0.1.4 2022-05-18 05:53:26 +00:00
Renovate Bot b0274946bf Update dependency com.github.seven332:beerbelly to v0.2.1 2022-05-18 05:53:24 +00:00
Renovate Bot b78c132742 Update dependency gradle to v7.4.2 2022-05-18 05:53:20 +00:00
tarsin b33c46de82 Merge pull request #7 from asuka-mio/renovate/com.squareup.okhttp3-okhttp-4.x
Update dependency com.squareup.okhttp3:okhttp to v4.9.3
2022-05-18 13:48:56 +08:00
Renovate Bot 25c8bba1b0 Update dependency com.squareup.okhttp3:okhttp to v4.9.3 2022-05-18 05:48:23 +00:00
tarsin 089f5cb7ec Merge pull request #4 from asuka-mio/renovate/com.github.seven332-glview-image-0.x
Update dependency com.github.seven332:glview-image to v0.1.3
2022-05-18 13:45:16 +08:00
tarsin 95a6a9dca6 Merge pull request #5 from asuka-mio/renovate/com.github.seven332-yorozuya-thread-0.x
Update dependency com.github.seven332:yorozuya-thread to v0.1.2
2022-05-18 13:45:02 +08:00
Renovate Bot e19759a7e4 Update dependency com.github.seven332:yorozuya-thread to v0.1.2 2022-05-18 05:44:21 +00:00
Renovate Bot 8f95ffeebf Update dependency com.github.seven332:glview-image to v0.1.3 2022-05-18 05:44:19 +00:00
tarsin 98f08feb16 Merge pull request #3 from asuka-mio/renovate/configure
Configure Renovate
2022-05-18 13:43:53 +08:00
Renovate Bot 50baaffc36 Add renovate.json 2022-05-18 05:39:46 +00:00
tarsin 66e9f43bba Revert "Revert minsdk to sdk29"
* No dynamic color support, crashed
* Won't fix
This reverts commit 0cb13bb6a3.
2022-05-18 12:02:21 +08:00
tarsin 0cb13bb6a3 Revert minsdk to sdk29
* Sadly my HW-SCM09 is still Android Q and seems it will never upgrade, damn

* Huawei sucks
2022-05-18 11:59:03 +08:00
159 changed files with 1660 additions and 10735 deletions

No files matched your search

+30 -45
View File
@@ -1,57 +1,42 @@
name: EhViewer
on:
push:
branches:
- master
on: push
jobs:
linux:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
- name: Checkout
uses: actions/checkout@v3
- name: Setup Java 11
uses: actions/setup-java@v3
with:
java-version: 1.8
- uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build
env:
KEYSTORE_PASS: ${{ secrets.KEYSTORE_PASS }}
ALIAS_NAME: ${{ secrets.ALIAS_NAME }}
ALIAS_PASS: ${{ secrets.ALIAS_PASS }}
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
./gradlew assembleRelease
echo ::set-env name=APK_FILE::$(find app/build/outputs/apk -name "*.apk")
- name: Create Release
id: create_release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;24.0.8215888" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
with:
tag_name: 1.7.4_${{ github.run_id }}
release_name: 1.7.4_${{ github.run_id }}
body: ""
draft: false
prerelease: true
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
name: arm64-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Update x86_64
uses: actions/upload-artifact@v3
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: ${{ env.APK_FILE }}
asset_name: app-release.apk
asset_content_type: application/vnd.android.package-archive
- uses: actions/upload-artifact@master
name: Upload artifact
with:
name: EhViewer
path: app/build/outputs/
name: x86_64-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86_64-release.apk
+41
View File
@@ -0,0 +1,41 @@
name: Releases
on:
push:
tags:
- "*"
jobs:
linux:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Java 11
uses: actions/setup-java@v3
with:
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;24.0.8215888" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
- name: Releases
uses: softprops/action-gh-release@v0.1.14
with:
body: Bump Version
files: |
app/build/outputs/apk/release/app-arm64-v8a-release.apk
app/build/outputs/apk/release/app-x86_64-release.apk
+10 -4
View File
@@ -1,12 +1,18 @@
[submodule "native/src/main/jni/a7zip/p7zip/p7zip"]
path = native/src/main/jni/a7zip/p7zip/p7zip
url = https://github.com/seven332/p7zip.git
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
url = https://gitlab.com/NekoInverter/libjpeg-turbo.git
url = https://github.com/asuka-mio/libjpeg-turbo
[submodule "native/src/main/jni/image/giflib/giflib"]
path = native/src/main/jni/image/giflib/giflib
url = https://gitlab.com/NekoInverter/giflib.git
[submodule "native/src/main/jni/image/libpng/libpng"]
path = native/src/main/jni/image/libpng/libpng
url = https://gitlab.com/NekoInverter/libpng.git
[submodule "native/src/main/jni/libarchive"]
path = native/src/main/jni/libarchive
url = https://github.com/asuka-mio/libarchive
[submodule "native/src/main/jni/liblzma"]
path = native/src/main/jni/liblzma
url = https://git.tukaani.org/xz.git
[submodule "native/src/main/jni/libzstd"]
path = native/src/main/jni/libzstd
url = https://github.com/facebook/zstd
+45 -5
View File
@@ -1,21 +1,59 @@
### [下载](https://install.appcenter.ms/users/nekoinverter/apps/ehviewer/distribution_groups/public)
<h1 align="center">
<img src="art/launcher_icon-web.png" width="200">
<br>EhViewer<br>
</h1>
[Download](https://install.appcenter.ms/users/nekoinverter/apps/ehviewer/distribution_groups/public)
<p align="center">
<a href="https://github.com/asuka-mio/Ehviewer/actions">
<img src="https://img.shields.io/github/workflow/status/asuka-mio/Ehviewer/EhViewer" alt="Github Actions">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/asuka-mio/Ehviewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/asuka-mio/ehviewer">
<img src="https://www.codefactor.io/repository/github/asuka-mio/ehviewer/badge" alt="CodeFactor">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/asuka-mio/ehviewer?include_prereleases" alt="Release">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/asuka-mio/ehviewer" alt="Issues">
</a>
</p>
# EhViewer
# Description
这是一个 E-Hentai Android 平台的浏览器
![Icon](art/launcher_icon-web.png)
已针对性适配 Material Design 3 及其动态取色系统
仅供 Android 12 及以上平台使用!
这是一个 E-Hentai Android 平台的浏览器。
An E-Hentai Application for Android.
with Material Design 3 and Dynamic Color Support.
Only Support Android S and above!
# Download
[Github 下载](https://github.com/asuka-mio/Ehviewer/releases)
# Screenshot
![screenshot-01](art/screenshot-01.png)
# Build
本项目使用了 CMake 3.23.1 作为构建工具链之一,而当前版本的 Android Studio SDK Tool 无法满足本项目需求。
因此,在构建本项目前需要提前配置好 CMake >=3.23.1。
This Project use CMake 3.23.1, with is highter than Android Studio bundle CMake.
So you need to pre-install CMake>=3.23.1 before build.
# Thanks
本项目受到了诸多开源项目的帮助
@@ -36,6 +74,8 @@ Here is the libraries
- [ShowcaseView](https://github.com/amlcurran/ShowcaseView)
- [Slabo](https://github.com/TiroTypeworks/Slabo)
- [TagSoup](http://home.ccil.org/~cowan/tagsoup/)
- [libarchive](http://www.libarchive.org/)
- [XZ Utils](https://tukaani.org/xz/)
# License
+30 -23
View File
@@ -22,22 +22,24 @@ android {
}
signingConfigs {
if (localProperties.getProperty("storeFile") != null) {
sign {
storeFile file(localProperties.getProperty("storeFile"))
storePassword localProperties.getProperty("storePassword")
keyAlias localProperties.getProperty("keyAlias")
keyPassword localProperties.getProperty("keyPassword")
}
sign {
storeFile file(signStoreFile)
storePassword signStorePassword
keyAlias signkeyAlias
keyPassword signkeyPassword
}
}
buildFeatures {
compose true
}
defaultConfig {
applicationId "io.github.nekoinverter.ehviewer"
minSdkVersion 31
targetSdkVersion 32
versionCode 114516
versionName "1.7.26.7"
versionCode 114522
versionName "1.7.27.2"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -72,17 +74,11 @@ android {
minifyEnabled true
shrinkResources true
proguardFiles 'proguard-rules.pro'
if (localProperties.getProperty("storeFile") != null) {
signingConfig signingConfigs.sign
}
signingConfig signingConfigs.sign
}
debug {
applicationIdSuffix ".debug"
if (localProperties.getProperty("storeFile") != null) {
signingConfig signingConfigs.sign
} else {
signingConfig signingConfigs.debug
}
signingConfig signingConfigs.debug
}
}
@@ -91,6 +87,10 @@ android {
java.srcDirs += 'src/main/java-gen'
}
}
composeOptions {
kotlinCompilerExtensionVersion compose
}
namespace 'com.hippo.ehviewer'
}
@@ -100,6 +100,7 @@ tasks.withType(JavaCompile) {
dependencies {
implementation 'androidx.activity:activity:1.4.0'
implementation "androidx.activity:activity-compose:1.4.0"
implementation 'androidx.biometric:biometric:1.1.0'
implementation 'androidx.browser:browser:1.4.0'
implementation 'androidx.core:core:1.7.0'
@@ -115,29 +116,35 @@ dependencies {
implementation 'com.github.seven332:beerbelly:0.1.4'
implementation 'com.github.seven332:glgallery:0.1.2'
implementation 'com.github.seven332:glview:0.1.0'
implementation 'com.github.seven332:glview-image:0.1.0'
implementation 'com.github.seven332:glview-image:0.1.3'
implementation 'com.github.seven332:streampipe:0.1.0'
implementation 'com.github.seven332:tuxiang:0.1.6'
implementation 'com.github.seven332:unifile:9ec57bcd8f'
implementation 'com.github.seven332:yorozuya-thread:0.1.1'
implementation 'com.github.seven332:yorozuya-collect:0.1.4'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.2'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
implementation 'com.gitlab.NekoInverter:yorozuya:723dc867a9'
implementation 'dev.rikka.rikkax.appcompat:appcompat:1.4.1'
implementation 'dev.rikka.rikkax.core:core:1.3.4'
implementation 'dev.rikka.rikkax.core:core:1.4.0'
implementation 'dev.rikka.rikkax.insets:insets:1.2.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
implementation 'dev.rikka.rikkax.material:material:1.6.6'
implementation 'dev.rikka.rikkax.material:material:2.4.0'
implementation 'dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.1'
implementation 'dev.rikka.rikkax.widget:borderview:1.1.0'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1'
implementation 'org.greenrobot:greendao:3.3.0'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'org.jsoup:jsoup:1.15.1'
implementation projects.native
implementation "androidx.compose.material3:material3:1.0.0-alpha12"
implementation "androidx.compose.ui:ui:$compose"
implementation "androidx.compose.ui:ui-util:$compose"
implementation "androidx.compose.animation:animation-graphics:$compose"
}
configurations.all {
Binary file not shown.
+1 -2
View File
@@ -91,8 +91,7 @@
android:name="com.hippo.ehviewer.ui.GalleryActivity"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/AppTheme.Gallery">
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -1,45 +0,0 @@
package androidx.recyclerview.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class BridgeRecyclerView extends RecyclerView {
public BridgeRecyclerView(@NonNull Context context) {
super(context);
}
public BridgeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BridgeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
try {
super.scrollStep(dx, dy, consumed);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
return super.scrollByInternal(x, y, ev, type);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
try {
super.onLayout(changed, l, t, r, b);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -26,7 +26,6 @@ import com.hippo.conaco.ConacoTask;
import com.hippo.conaco.Unikery;
import com.hippo.image.ImageBitmap;
import com.hippo.image.ImageDrawable;
import com.hippo.image.RecycledException;
import com.hippo.widget.ObservedTextView;
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
@@ -127,7 +126,7 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
ImageDrawable drawable;
try {
drawable = new ImageDrawable(value);
} catch (RecycledException e) {
} catch (Exception e) {
Log.d(TAG, "The ImageBitmap is recycled", e);
return false;
}
@@ -16,9 +16,7 @@
package com.hippo.easyrecyclerview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.SensorManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
@@ -26,33 +24,20 @@ import android.util.SparseBooleanArray;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.Checkable;
import androidx.annotation.NonNull;
import androidx.collection.LongSparseArray;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.BridgeRecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.hippo.ehviewer.Settings;
import com.hippo.yorozuya.NumberUtils;
/**
* Add setChoiceMode for RecyclerView
*/
// Get some code from twoway-view and AbsListView.
public class EasyRecyclerView extends BridgeRecyclerView {
/**
* Represents an invalid position. All valid positions are in the range 0 to 1 less than the
* number of items in the current adapter.
*/
public static final int INVALID_POSITION = -1;
public class EasyRecyclerView extends RecyclerView {
/**
* Normal list that does not indicate choices
@@ -78,14 +63,8 @@ public class EasyRecyclerView extends BridgeRecyclerView {
* The list allows multiple choices in custom action
*/
public static final int CHOICE_MODE_MULTIPLE_CUSTOM = 4;
private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
// Fling friction
private final float mFlingFriction = ViewConfiguration.getScrollFriction();
public float pageScrollThreshold;
double flingMoveDistanceX;
double flingMoveDistanceY;
boolean canScrollVertical = true;
boolean canScrollHorizontally = true;
/**
* Wrapper for the multiple choice mode callback; AbsListView needs to perform
* a few extra actions around what application code does.
@@ -131,30 +110,20 @@ public class EasyRecyclerView extends BridgeRecyclerView {
private LongSparseArray<Integer> mCheckedIdStates;
private Adapter<?> mAdapter;
private boolean isScrollBefore = false;
private int initialTouchX;
private int initialTouchY;
// A context-specific coefficient adjusted to physical values.
private float mPhysicalCoeff;
private int defaultOverflowItemScrollKeep;
private boolean enableScroll = false;
public EasyRecyclerView(Context context) {
super(context);
EasyRecyclerView.this.init(context);
setEnableScroll(!Settings.getEInkMode());
}
public EasyRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
EasyRecyclerView.this.init(context);
setEnableScroll(!Settings.getEInkMode());
}
public EasyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
EasyRecyclerView.this.init(context);
setEnableScroll(!Settings.getEInkMode());
}
public static void setViewChecked(View view, boolean checked) {
@@ -202,18 +171,7 @@ public class EasyRecyclerView extends BridgeRecyclerView {
}
}
/**
* Returns the number of items currently selected. This will only be valid
* if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
*
* <p>To determine the specific items that are currently selected, use one of
* the <code>getChecked*</code> methods.
*
* @return The number of items currently selected
* @see #getCheckedItemPosition()
* @see #getCheckedItemPositions()
* @see #getCheckedItemIds()
*/
public int getCheckedItemCount() {
return mCheckedItemCount;
}
@@ -237,22 +195,6 @@ public class EasyRecyclerView extends BridgeRecyclerView {
return false;
}
/**
* Returns the currently checked item. The result is only valid if the choice
* mode has been set to {@link #CHOICE_MODE_SINGLE}.
*
* @return The position of the currently checked item or
* {@link #INVALID_POSITION} if nothing is selected
* @see #setChoiceMode(int)
*/
public int getCheckedItemPosition() {
if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
return mCheckStates.keyAt(0);
}
return INVALID_POSITION;
}
/**
* Returns the set of checked items in the list. The result is only valid if
* the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
@@ -269,30 +211,6 @@ public class EasyRecyclerView extends BridgeRecyclerView {
return null;
}
/**
* Returns the set of checked items ids. The result is only valid if the
* choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
* has stable IDs. ({@link android.widget.ListAdapter#hasStableIds()} == {@code true})
*
* @return A new array which contains the id of each checked item in the
* list.
*/
public long[] getCheckedItemIds() {
if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
return new long[0];
}
final LongSparseArray<Integer> idStates = mCheckedIdStates;
final int count = idStates.size();
final long[] ids = new long[count];
for (int i = 0; i < count; i++) {
ids[i] = idStates.keyAt(i);
}
return ids;
}
public boolean isInCustomChoice() {
return mCustomChoice;
}
@@ -475,14 +393,6 @@ public class EasyRecyclerView extends BridgeRecyclerView {
updateOnScreenCheckedViews();
}
/**
* @return The current choice mode
* @see #setChoiceMode(int)
*/
public int getChoiceMode() {
return mChoiceMode;
}
/**
* Defines the choice behavior for the List. By default, Lists do not have any choice behavior
* ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
@@ -518,24 +428,6 @@ public class EasyRecyclerView extends BridgeRecyclerView {
}
}
/**
* Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
* selection {@link android.view.ActionMode}. Only used when the choice mode is set to
* {@link #CHOICE_MODE_MULTIPLE_MODAL}.
*
* @param listener Listener that will manage the selection mode
* @see #setChoiceMode(int)
*/
public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
if (mMultiChoiceModeCallback == null) {
mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
}
mMultiChoiceModeCallback.setWrapped(listener);
}
/**
* @param listener
*/
public void setCustomCheckedListener(CustomChoiceListener listener) {
mCustomChoiceListener = listener;
}
@@ -556,299 +448,13 @@ public class EasyRecyclerView extends BridgeRecyclerView {
private void init(Context context) {
float scale = context.getResources().getDisplayMetrics().density;
final float ppi = scale * 160.0f;
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* ppi
* 0.84f; // look and feel tuning
// g (m/s^2)
// inch/meter
// A context-specific coefficient adjusted to physical values.
// look and feel tuning
pageScrollThreshold = scale * 80;
}
public void setPageScrollThreshold(float pageScrollThreshold) {
this.pageScrollThreshold = pageScrollThreshold;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent e) {
if (enableScroll) {
return super.onTouchEvent(e);
}
boolean superTouchResult = super.onTouchEvent(e);
if (superTouchResult) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
initialTouchX = (int) (e.getX() + 0.5f);
initialTouchY = (int) (e.getY() + 0.5f);
break;
case MotionEvent.ACTION_MOVE:
if (initialTouchX < 0 || initialTouchY < 0) {
initialTouchX = (int) (e.getX() + 0.5f);
initialTouchY = (int) (e.getY() + 0.5f);
}
break;
case MotionEvent.ACTION_UP:
if (isScrollBefore) {
int moveDistanceX = (int) (initialTouchX - e.getX() + flingMoveDistanceX);
int moveDistanceY = (int) (initialTouchY - e.getY() + flingMoveDistanceY);
onTouchScroll(moveDistanceX, moveDistanceY);
initialTouchX = initialTouchY = -1;
}
break;
}
isScrollBefore = getScrollState() == SCROLL_STATE_DRAGGING;
}
return superTouchResult;
}
@Override
public boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
if (enableScroll) {
return super.scrollByInternal(x, y, ev, type);
}
//disable drag scroll effect,e-ink screen don't need it
return false;
}
@Override
public boolean fling(int velocityX, int velocityY) {
if (enableScroll) {
return super.fling(velocityX, velocityY);
}
flingMoveDistanceX = getSplineFlingDistance(velocityX) * Math.signum(velocityX);
flingMoveDistanceY = getSplineFlingDistance(velocityY) * Math.signum(velocityY);
//disable fling scroll effect,e-ink screen don't need it
return false;
}
protected void onTouchScroll(int dx, int dy) {
canScrollVertical = true;
canScrollHorizontally = true;
LayoutManager layout = getLayoutManager();
if (layout != null) {
if (dy > pageScrollThreshold) {
if (canScrollVertical && layout.canScrollVertically()) {
canScrollVertical = false;
onScrollDown();
} else if (canScrollHorizontally && layout.canScrollHorizontally()) {
canScrollHorizontally = false;
onScrollRight();
}
} else if (dy < -pageScrollThreshold) {
if (canScrollVertical && layout.canScrollVertically()) {
canScrollVertical = false;
onScrollUp();
} else if (canScrollHorizontally && layout.canScrollHorizontally()) {
canScrollHorizontally = false;
onScrollLeft();
}
}
if (dx > pageScrollThreshold) {
if (canScrollVertical && layout.canScrollVertically()) {
onScrollDown();
} else if (canScrollHorizontally && layout.canScrollHorizontally()) {
onScrollRight();
}
} else if (dx < -pageScrollThreshold) {
if (canScrollVertical && layout.canScrollVertically()) {
onScrollUp();
} else if (canScrollHorizontally && layout.canScrollHorizontally()) {
onScrollLeft();
}
}
}
}
protected void onScrollLeft() {
LayoutManager layout = getLayoutManager();
if (layout instanceof LinearLayoutManager) {
LinearLayoutManager realLayout = (LinearLayoutManager) layout;
int firstVisiblePosition = realLayout.findFirstVisibleItemPosition();
ViewHolder firstVisibleItem = findViewHolderForAdapterPosition(firstVisiblePosition);
if (firstVisibleItem == null) {
return;
}
//when the item width overscreen, findLastCompletelyVisibleItemPosition always return NO_POSITION
if (firstVisibleItem.itemView.getMeasuredWidth() > getMeasuredWidth()) {
super.scrollByInternal(-getMeasuredWidth() + defaultOverflowItemScrollKeep, 0, null, ViewCompat.TYPE_TOUCH);
} else if (firstVisibleItem.itemView.getMeasuredWidth() == getMeasuredWidth()) {
super.scrollByInternal(-getMeasuredWidth(), 0, null, ViewCompat.TYPE_TOUCH);
} else {
super.scrollByInternal(firstVisibleItem.itemView.getRight() - getMeasuredWidth(), 0, null, ViewCompat.TYPE_TOUCH);
}
} else if (layout instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager realLayout = (StaggeredGridLayoutManager) layout;
int[] firstVisiblePosition = realLayout.findFirstVisibleItemPositions(null);
ViewHolder[] firstVisibleItems = new ViewHolder[firstVisiblePosition.length];
for (int i = 0; i < firstVisiblePosition.length; i++) {
firstVisibleItems[i] = findViewHolderForAdapterPosition(firstVisiblePosition[i]);
}
int scrollDistance = Integer.MAX_VALUE;
for (ViewHolder firstVisibleItem : firstVisibleItems) {
if (firstVisibleItem != null) {
if (firstVisibleItem.itemView.getRight() < scrollDistance) {
scrollDistance = firstVisibleItem.itemView.getRight();
}
}
}
if (scrollDistance == getMeasuredWidth()) {
scrollDistance = 0;
}
super.scrollByInternal(scrollDistance - getMeasuredWidth(), 0, null, ViewCompat.TYPE_TOUCH);
}
}
protected void onScrollRight() {
LayoutManager layout = getLayoutManager();
if (layout instanceof LinearLayoutManager) {
LinearLayoutManager realLayout = (LinearLayoutManager) layout;
int lastVisiblePosition = realLayout.findLastVisibleItemPosition();
ViewHolder lastVisibleItem = findViewHolderForAdapterPosition(lastVisiblePosition);
if (lastVisibleItem == null) {
return;
}
//when the item width overflow screen, findLastCompletelyVisibleItemPosition always return NO_POSITION
if (lastVisibleItem.itemView.getMeasuredWidth() > getMeasuredWidth()) {
super.scrollByInternal(getMeasuredWidth() - defaultOverflowItemScrollKeep, 0, null, ViewCompat.TYPE_TOUCH);
} else if (lastVisibleItem.itemView.getMeasuredWidth() == getMeasuredWidth()) {
super.scrollByInternal(getMeasuredWidth(), 0, null, ViewCompat.TYPE_TOUCH);
} else {
super.scrollByInternal(lastVisibleItem.itemView.getLeft(), 0, null, ViewCompat.TYPE_TOUCH);
}
} else if (layout instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager realLayout = (StaggeredGridLayoutManager) layout;
int[] lastVisiblePosition = realLayout.findLastVisibleItemPositions(null);
ViewHolder[] lastVisibleItems = new ViewHolder[lastVisiblePosition.length];
for (int i = 0; i < lastVisiblePosition.length; i++) {
lastVisibleItems[i] = findViewHolderForAdapterPosition(lastVisiblePosition[i]);
}
int scrollDistance = 0;
for (ViewHolder lastVisibleItem : lastVisibleItems) {
if (lastVisibleItem != null) {
if (lastVisibleItem.itemView.getLeft() > scrollDistance) {
scrollDistance = lastVisibleItem.itemView.getLeft();
}
}
}
if (scrollDistance == 0) {
scrollDistance = getMeasuredWidth();
}
super.scrollByInternal(scrollDistance, 0, null, ViewCompat.TYPE_TOUCH);
}
}
protected void onScrollUp() {
LayoutManager layout = getLayoutManager();
if (layout instanceof LinearLayoutManager) {
LinearLayoutManager realLayout = (LinearLayoutManager) layout;
int firstVisiblePosition = realLayout.findFirstVisibleItemPosition();
ViewHolder firstVisibleItem = findViewHolderForAdapterPosition(firstVisiblePosition);
if (firstVisibleItem == null) {
return;
}
//when the item height overscreen, findLastCompletelyVisibleItemPosition always return NO_POSITION
if (firstVisibleItem.itemView.getMeasuredHeight() > getMeasuredHeight()) {
super.scrollByInternal(0, -getMeasuredHeight() + defaultOverflowItemScrollKeep, null, ViewCompat.TYPE_TOUCH);
} else if (firstVisibleItem.itemView.getMeasuredHeight() == getMeasuredHeight()) {
super.scrollByInternal(0, -getMeasuredHeight(), null, ViewCompat.TYPE_TOUCH);
} else {
super.scrollByInternal(0, firstVisibleItem.itemView.getBottom() - getMeasuredHeight(), null, ViewCompat.TYPE_TOUCH);
}
} else if (layout instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager realLayout = (StaggeredGridLayoutManager) layout;
int[] firstVisiblePosition = realLayout.findFirstVisibleItemPositions(null);
ViewHolder[] firstVisibleItems = new ViewHolder[firstVisiblePosition.length];
for (int i = 0; i < firstVisiblePosition.length; i++) {
firstVisibleItems[i] = findViewHolderForAdapterPosition(firstVisiblePosition[i]);
}
int scrollDistance = Integer.MAX_VALUE;
for (ViewHolder firstVisibleItem : firstVisibleItems) {
if (firstVisibleItem != null) {
if (firstVisibleItem.itemView.getBottom() < scrollDistance) {
scrollDistance = firstVisibleItem.itemView.getBaseline();
}
}
}
if (scrollDistance == getMeasuredWidth()) {
scrollDistance = 0;
}
super.scrollByInternal(0, scrollDistance - getMeasuredHeight(), null, ViewCompat.TYPE_TOUCH);
}
}
protected void onScrollDown() {
LayoutManager layout = getLayoutManager();
if (layout instanceof LinearLayoutManager) {
LinearLayoutManager realLayout = (LinearLayoutManager) layout;
int lastVisiblePosition = realLayout.findLastVisibleItemPosition();
ViewHolder lastVisibleItem = findViewHolderForAdapterPosition(lastVisiblePosition);
if (lastVisibleItem == null) {
return;
}
//when the item height overflow screen, findLastCompletelyVisibleItemPosition always return NO_POSITION
if (lastVisibleItem.itemView.getMeasuredHeight() > getMeasuredHeight()) {
super.scrollByInternal(0, getMeasuredHeight() - defaultOverflowItemScrollKeep, null, ViewCompat.TYPE_TOUCH);
} else if (lastVisibleItem.itemView.getMeasuredHeight() == getMeasuredHeight()) {
super.scrollByInternal(0, getMeasuredHeight(), null, ViewCompat.TYPE_TOUCH);
} else {
super.scrollByInternal(0, lastVisibleItem.itemView.getTop(), null, ViewCompat.TYPE_TOUCH);
}
} else if (layout instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager realLayout = (StaggeredGridLayoutManager) layout;
int[] lastVisiblePosition = realLayout.findLastVisibleItemPositions(null);
ViewHolder[] lastVisibleItems = new ViewHolder[lastVisiblePosition.length];
for (int i = 0; i < lastVisiblePosition.length; i++) {
lastVisibleItems[i] = findViewHolderForAdapterPosition(lastVisiblePosition[i]);
}
int scrollDistance = 0;
for (ViewHolder lastVisibleItem : lastVisibleItems) {
if (lastVisibleItem != null) {
if (lastVisibleItem.itemView.getTop() > scrollDistance) {
scrollDistance = lastVisibleItem.itemView.getTop();
}
}
}
if (scrollDistance == 0) {
scrollDistance = getMeasuredHeight();
}
super.scrollByInternal(0, scrollDistance, null, ViewCompat.TYPE_TOUCH);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
defaultOverflowItemScrollKeep = (int) (h * 0.05f);
}
private double getSplineDeceleration(int velocity) {
return Math.log(0.35f * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
}
private double getSplineFlingDistance(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
public boolean isEnableScroll() {
return enableScroll;
}
public void setEnableScroll(boolean enableScroll) {
this.enableScroll = enableScroll;
}
@Override
public Parcelable onSaveInstanceState() {
final SavedState ss = new SavedState(super.onSaveInstanceState());
@@ -1016,9 +622,6 @@ public class EasyRecyclerView extends BridgeRecyclerView {
class MultiChoiceModeWrapper implements MultiChoiceModeListener {
private MultiChoiceModeListener mWrapped;
public void setWrapped(MultiChoiceModeListener wrapped) {
mWrapped = wrapped;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean hasWrappedCallback() {
@@ -297,11 +297,6 @@ public class EhApplication extends SceneApplication {
return application.mFavouriteStatusRouter;
}
@NonNull
public static String getDeveloperEmail() {
return "nekoworkshop$protonmail.com".replace('$', '@');
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
@@ -457,14 +457,6 @@ public class Settings {
putBoolean(KEY_SELECT_SITE, value);
}
public static boolean getQuickSearchTip() {
return getBoolean(KEY_QUICK_SEARCH_TIP, DEFAULT_QUICK_SEARCH_TIP);
}
public static void putQuickSearchTip(boolean value) {
putBoolean(KEY_QUICK_SEARCH_TIP, value);
}
public static int getTheme() {
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
}
@@ -594,14 +586,6 @@ public class Settings {
putString(KEY_EXCLUDED_LANGUAGES, value);
}
public static void putEInkMode(boolean value) {
putBoolean(KEY_E_INK_MODE, value);
}
public static boolean getEInkMode() {
return getBoolean(KEY_E_INK_MODE, DEFAULT_E_INK_MODE);
}
public static boolean getMeteredNetworkWarning() {
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
}
@@ -1,174 +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 androidx.annotation.NonNull;
import com.hippo.a7zip.ArchiveException;
import com.hippo.a7zip.InArchive;
import com.hippo.a7zip.PropID;
import com.hippo.a7zip.PropType;
import com.hippo.a7zip.SeekableInputStream;
import com.hippo.unifile.UniRandomAccessFile;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
class A7ZipArchive implements Closeable {
private final InArchive archive;
private A7ZipArchive(InArchive archive) {
this.archive = archive;
}
private static boolean isSupportedFilename(String name) {
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
if (name.endsWith(extension)) {
return true;
}
}
return false;
}
static A7ZipArchive create(UniRandomAccessFile file) throws ArchiveException {
SeekableInputStream store = new UniRandomAccessFileInStream(file);
InArchive archive = InArchive.open(store);
if ((archive.getArchivePropertyType(PropID.ENCRYPTED) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.ENCRYPTED))
|| (archive.getArchivePropertyType(PropID.SOLID) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.SOLID))
|| (archive.getArchivePropertyType(PropID.IS_VOLUME) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.IS_VOLUME))) {
throw new ArchiveException("Unsupported archive");
}
return new A7ZipArchive(archive);
}
@Override
public void close() {
archive.close();
}
List<A7ZipArchiveEntry> getArchiveEntries() {
List<A7ZipArchiveEntry> entries = new ArrayList<>();
for (int i = 0, n = archive.getNumberOfEntries(); i < n; i++) {
if (!archive.getEntryBooleanProperty(i, PropID.ENCRYPTED)
&& !archive.getEntryBooleanProperty(i, PropID.IS_DIR)
&& !archive.getEntryBooleanProperty(i, PropID.IS_VOLUME)
&& !archive.getEntryBooleanProperty(i, PropID.SOLID)) {
String path = archive.getEntryPath(i);
if (isSupportedFilename(path.toLowerCase())) {
entries.add(new A7ZipArchiveEntry(archive, i, path));
}
}
}
return entries;
}
static class A7ZipArchiveEntry {
private final InArchive archive;
private final int index;
private final String path;
private A7ZipArchiveEntry(InArchive archive, int index, String path) {
this.archive = archive;
this.index = index;
this.path = path;
}
String getPath() {
return path;
}
void extract(OutputStream os) throws ArchiveException {
archive.extractEntry(index, new OutputStreamSequentialOutStream(os));
}
}
private static class UniRandomAccessFileInStream extends SeekableInputStream {
private final byte[] scratch = new byte[8];
private final UniRandomAccessFile file;
public UniRandomAccessFileInStream(UniRandomAccessFile file) {
this.file = file;
}
@Override
public void seek(long pos) throws IOException {
file.seek(pos);
}
@Override
public long tell() throws IOException {
return file.getFilePointer();
}
@Override
public long size() throws IOException {
return file.length();
}
@Override
public int read() throws IOException {
return (file.read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
}
@Override
public int read(@NonNull byte[] b) throws IOException {
return file.read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return file.read(b, off, len);
}
@Override
public void close() throws IOException {
file.close();
}
}
private static class OutputStreamSequentialOutStream extends OutputStream {
private final OutputStream stream;
public OutputStreamSequentialOutStream(OutputStream stream) {
this.stream = stream;
}
@Override
public void write(int b) throws IOException {
stream.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
stream.write(b, off, len);
}
@Override
public void close() throws IOException {
stream.close();
}
}
}
@@ -23,23 +23,18 @@ import android.os.Process;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.a7zip.ArchiveException;
import com.hippo.UriArchiveAccessor;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.unifile.UniRandomAccessFile;
import com.hippo.util.NaturalComparator;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
@@ -47,15 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class ArchiveGalleryProvider extends GalleryProvider2 {
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static final Comparator<A7ZipArchive.A7ZipArchiveEntry> naturalComparator = new Comparator<A7ZipArchive.A7ZipArchiveEntry>() {
private final NaturalComparator comparator = new NaturalComparator();
@Override
public int compare(A7ZipArchive.A7ZipArchiveEntry o1, A7ZipArchive.A7ZipArchiveEntry o2) {
return comparator.compare(o1.getPath(), o2.getPath());
}
};
private final UniFile file;
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<>();
@@ -66,7 +53,14 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
private String error;
public ArchiveGalleryProvider(Context context, Uri uri) {
file = UniFile.fromMediaUri(context, uri);
UriArchiveAccessor archiveAccessor1;
try {
archiveAccessor1 = new UriArchiveAccessor(context, uri);
} catch (FileNotFoundException e) {
archiveAccessor1 = null;
e.printStackTrace();
}
archiveAccessor = archiveAccessor1;
}
@Override
@@ -167,37 +161,14 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
private class ArchiveTask implements Runnable {
@Override
public void run() {
UniRandomAccessFile uraf = null;
try {
uraf = file.createRandomAccessFile("r");
} catch (IOException e) {
e.printStackTrace();
}
if (uraf == null) {
size = archiveAccessor.open();
if (size <= 0) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
notifyDataChanged();
return;
}
A7ZipArchive archive = null;
try {
archive = A7ZipArchive.create(uraf);
} catch (ArchiveException e) {
e.printStackTrace();
}
if (archive == null) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_invalid_archive);
notifyDataChanged();
return;
}
List<A7ZipArchive.A7ZipArchiveEntry> entries = archive.getArchiveEntries();
Collections.sort(entries, naturalComparator);
// Update size and notify changed
size = entries.size();
notifyDataChanged();
while (!Thread.currentThread().isInterrupted()) {
@@ -217,13 +188,13 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
}
// Check index valid
if (index < 0 || index >= entries.size()) {
if (index < 0 || index >= size) {
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
continue;
}
Pipe pipe = new Pipe(4 * 1024);
Pipe pipe = new Pipe(4096);
synchronized (streams) {
if (streams.get(index) != null) {
@@ -233,13 +204,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
streams.notify();
}
try {
entries.get(index).extract(pipe.getOutputStream());
} catch (ArchiveException e) {
e.printStackTrace();
} finally {
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
}
archiveAccessor.extractTargetIndexToOutputStream(index, pipe.getOutputStream());
}
}
}
@@ -282,4 +247,4 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
}
}
}
}
}
@@ -107,13 +107,6 @@ class Pipe {
public void write(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
while (len != 0) {
if (outClosed) {
throw new IOException("The OutputStream is closed");
}
if (inClosed) {
throw new IOException("The InputStream is closed");
}
if (head == tail && full) {
// The buffer is full, wait for InputStream read bytes
try {
@@ -1,58 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.preference;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.AttributeSet;
import androidx.preference.Preference;
import com.hippo.ehviewer.R;
public class VersionPreference extends Preference {
public VersionPreference(Context context) {
super(context);
init(context);
}
public VersionPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public VersionPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
setTitle(R.string.settings_about_version);
String version;
try {
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
version = pInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
version = context.getString(R.string.error_unknown);
}
setSummary(version);
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.preference
import android.content.Context
import android.content.pm.PackageManager
import android.util.AttributeSet
import androidx.appcompat.app.AlertDialog
import com.hippo.Native
import com.hippo.ehviewer.R
import com.hippo.preference.MessagePreference
class VersionPreference @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MessagePreference(context, attrs) {
init {
setTitle(R.string.settings_about_version)
val version: String = try {
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
pInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
context.getString(R.string.error_unknown)
}
summary = version
}
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() + "\nlibzstd " + Native.getlibzstdVersion() + "\nzlib " + Native.getzlibVersion() + "\nlibjpeg-turbo " + Native.getlibjpeg_turboVersion())
}
}
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowInsets;
@@ -47,10 +46,8 @@ public abstract class EhActivity extends MaterialActivity {
}
public static String getTheme(Context context) {
if (isBlackNightTheme()
&& isNightMode(context.getResources().getConfiguration()))
if (isBlackNightTheme() && isNightMode(context.getResources().getConfiguration()))
return THEME_BLACK;
return THEME_DEFAULT;
}
@@ -70,26 +67,21 @@ public abstract class EhActivity extends MaterialActivity {
@StyleRes
public int getThemeStyleRes(Context context) {
switch (getTheme(context)) {
case THEME_BLACK:
return R.style.ThemeOverlay_Black;
case THEME_DEFAULT:
default:
return R.style.ThemeOverlay;
if (THEME_BLACK.equals(getTheme(context))) {
return R.style.ThemeOverlay_Black;
}
return R.style.ThemeOverlay;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((EhApplication) getApplication()).registerActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
((EhApplication) getApplication()).unregisterActivity(this);
}
@@ -114,14 +106,10 @@ public abstract class EhActivity extends MaterialActivity {
WindowInsets rootWindowInsets = window.getDecorView().getRootWindowInsets();
if (rootWindowInsets != null && rootWindowInsets.getSystemWindowInsetBottom() >= Resources.getSystem().getDisplayMetrics().density * 40) {
window.setNavigationBarColor(ResourcesKt.resolveColor(getTheme(), android.R.attr.navigationBarColor) & 0x00ffffff | -0x20000000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(false);
}
window.setNavigationBarContrastEnforced(false);
} else {
window.setNavigationBarColor(Color.TRANSPARENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
window.setNavigationBarContrastEnforced(true);
}
});
}
@@ -66,6 +66,7 @@ import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
import com.hippo.ehviewer.R;
@@ -221,16 +222,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private int mLayoutMode;
private int mSize;
private int mCurrentIndex;
private int mSavingPage = -1; 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;
});
private int mSavingPage = -1;
private void buildProvider() {
if (mGalleryProvider != null) {
@@ -247,13 +239,9 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
} else if (Intent.ACTION_VIEW.equals(mAction)) {
if (mUri != null) {
// Only support zip now
try {
grantUriPermission(BuildConfig.APPLICATION_ID, mUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (Exception e) {
// Some stupid file manager send us a uri and don't allow us to read it
// java.lang.SecurityException: UID 10671 does not have permission to
// content://com.UCMobile.fileProvider/external_files/BaiduNetdisk/getvoice [user 0]
Toast.makeText(this, R.string.error_reading_failed, Toast.LENGTH_SHORT).show();
}
mGalleryProvider = new ArchiveGalleryProvider(this, mUri);
@@ -273,7 +261,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
mGalleryInfo = intent.getParcelableExtra(KEY_GALLERY_INFO);
mPage = intent.getIntExtra(KEY_PAGE, -1);
buildProvider();
}
} 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;
});
private void onRestore(@NonNull Bundle savedInstanceState) {
mAction = savedInstanceState.getString(KEY_ACTION);
@@ -1051,15 +1048,15 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private final Spinner mScaleMode;
private final Spinner mStartPosition;
private final Spinner mReadTheme;
private final Switch mKeepScreenOn;
private final Switch mShowClock;
private final Switch mShowProgress;
private final Switch mShowBattery;
private final Switch mShowPageInterval;
private final Switch mVolumePage;
private final Switch mReverseVolumePage;
private final Switch mReadingFullscreen;
private final Switch mCustomScreenLightness;
private final SwitchMaterial mKeepScreenOn;
private final SwitchMaterial mShowClock;
private final SwitchMaterial mShowProgress;
private final SwitchMaterial mShowBattery;
private final SwitchMaterial mShowPageInterval;
private final SwitchMaterial mVolumePage;
private final SwitchMaterial mReverseVolumePage;
private final SwitchMaterial mReadingFullscreen;
private final SwitchMaterial mCustomScreenLightness;
private final SeekBar mScreenLightness;
@SuppressLint("InflateParams")
@@ -1304,4 +1301,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.Uri;
@@ -33,7 +32,6 @@ import android.text.TextUtils;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
@@ -43,7 +41,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@@ -85,7 +82,6 @@ import com.hippo.unifile.UniFile;
import com.hippo.util.BitmapUtils;
import com.hippo.util.ClipboardUtil;
import com.hippo.widget.DrawerView;
import com.hippo.widget.LoadImageView;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.ViewUtils;
@@ -96,8 +92,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import rikka.material.app.DayNightDelegate;
public final class MainActivity extends StageActivity
implements NavigationView.OnNavigationItemSelectedListener {
@@ -139,10 +133,6 @@ public final class MainActivity extends StageActivity
private NavigationView mNavView;
@Nullable
private DrawerView mRightDrawer;
@Nullable
private LoadImageView mAvatar;
@Nullable
private TextView mDisplayName;
private int mNavCheckedItem = 0;
@Override
@@ -320,19 +310,6 @@ public final class MainActivity extends StageActivity
mDrawerLayout.setStatusBarBackgroundColor(0);
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
View headerLayout = mNavView.getHeaderView(0);
mAvatar = (LoadImageView) ViewUtils.$$(headerLayout, R.id.avatar);
mDisplayName = (TextView) ViewUtils.$$(headerLayout, R.id.display_name);
ViewUtils.$$(headerLayout, R.id.night_mode).setOnClickListener(v -> {
int theme = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) > 0 ? DayNightDelegate.MODE_NIGHT_NO : DayNightDelegate.MODE_NIGHT_YES;
DayNightDelegate.setDefaultNightMode(theme);
recreate();
if (Settings.getTheme() != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
Settings.putTheme(theme);
}
});
updateProfile();
if (mNavView != null) {
mNavView.setNavigationItemSelectedListener(this);
@@ -440,23 +417,18 @@ public final class MainActivity extends StageActivity
mDrawerLayout = null;
mNavView = null;
mRightDrawer = null;
mAvatar = null;
mDisplayName = null;
}
@Override
protected void onResume() {
super.onResume();
setNavCheckedItem(mNavCheckedItem);
checkClipboardUrl();
}
@Override
protected void onTransactScene() {
super.onTransactScene();
checkClipboardUrl();
}
@@ -546,25 +518,6 @@ public final class MainActivity extends StageActivity
}
}
public void updateProfile() {
if (null == mAvatar || null == mDisplayName) {
return;
}
String avatarUrl = Settings.getAvatar();
if (TextUtils.isEmpty(avatarUrl)) {
mAvatar.load(R.drawable.default_avatar);
} else {
mAvatar.load(avatarUrl, avatarUrl);
}
String displayName = Settings.getDisplayName();
if (TextUtils.isEmpty(displayName)) {
displayName = getString(R.string.default_display_name);
}
mDisplayName.setText(displayName);
}
public void addAboveSnackView(View view) {
if (mStageLayout != null) {
mStageLayout.addAboveSnackView(view);
@@ -21,40 +21,19 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.text.Html;
import com.hippo.util.AppHelper;
public class AboutFragment extends BasePreferenceFragment {
private static final String KEY_AUTHOR = "author";
//private static final String KEY_DONATE = "donate";
private static final String KEY_CHECK_FOR_UPDATES = "check_for_updates";
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.about_settings);
Preference author = findPreference(KEY_AUTHOR);
//Preference donate = findPreference(KEY_DONATE);
Preference checkForUpdate = findPreference(KEY_CHECK_FOR_UPDATES);
author.setSummary(Html.fromHtml(getString(R.string.settings_about_author_summary).replace('$', '@')));
author.setOnPreferenceClickListener(this);
//donate.setOnPreferenceClickListener(this);
checkForUpdate.setOnPreferenceClickListener(this);
}
@Override
public boolean onPreferenceClick(Preference preference) {
String key = preference.getKey();
if (KEY_AUTHOR.equals(key)) {
AppHelper.sendEmail(requireActivity(), EhApplication.getDeveloperEmail(),
"About EhViewer", null);
}
return true;
}
@Override
@@ -58,13 +58,6 @@ public abstract class BaseScene extends SceneFragment {
private SparseArray<Parcelable> drawerViewState;
private boolean needWhiteStatusBar = needWhiteStatusBar();
public void updateAvatar() {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).updateProfile();
}
}
public void addAboveSnackView(View view) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
@@ -69,7 +69,6 @@ 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.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
@@ -709,15 +708,10 @@ public final class GalleryListScene extends BaseScene
if (null == context) {
return;
}
final CheckBoxDialogBuilder builder = new CheckBoxDialogBuilder(
context, getString(R.string.add_quick_search_tip), getString(R.string.get_it), false);
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
builder.setMessage(R.string.add_quick_search_tip);
builder.setTitle(R.string.readme);
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
if (builder.isChecked()) {
Settings.putQuickSearchTip(false);
}
showAddQuickSearchDialog(adapter, recyclerView, tip);
}).show();
builder.show();
}
@@ -823,11 +817,9 @@ public final class GalleryListScene extends BaseScene
toolbar.setOnMenuItemClickListener(item -> {
int id = item.getItemId();
if (id == R.id.action_add) {
if (Settings.getQuickSearchTip()) {
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
} else {
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
}
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
} else if (id == R.id.action_help) {
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
}
return true;
});
@@ -357,7 +357,6 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
public void onGetProfileEnd() {
mSigningIn = false;
updateAvatar();
redirectTo();
}
@@ -1,163 +0,0 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TableLayout;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.widget.CheckTextView;
import com.hippo.yorozuya.NumberUtils;
public class CategoryTable extends TableLayout implements View.OnLongClickListener {
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_CATEGORY = "category";
private CheckTextView mDoujinshi;
private CheckTextView mManga;
private CheckTextView mArtistCG;
private CheckTextView mGameCG;
private CheckTextView mWestern;
private CheckTextView mNonH;
private CheckTextView mImageSets;
private CheckTextView mCosplay;
private CheckTextView mAsianPorn;
private CheckTextView mMisc;
private CheckTextView[] mOptions;
public CategoryTable(Context context) {
super(context);
init();
}
public CategoryTable(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public void init() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_category_table, this);
ViewGroup row0 = (ViewGroup) getChildAt(0);
mDoujinshi = (CheckTextView) row0.getChildAt(0);
mManga = (CheckTextView) row0.getChildAt(1);
ViewGroup row1 = (ViewGroup) getChildAt(1);
mArtistCG = (CheckTextView) row1.getChildAt(0);
mGameCG = (CheckTextView) row1.getChildAt(1);
ViewGroup row2 = (ViewGroup) getChildAt(2);
mWestern = (CheckTextView) row2.getChildAt(0);
mNonH = (CheckTextView) row2.getChildAt(1);
ViewGroup row3 = (ViewGroup) getChildAt(3);
mImageSets = (CheckTextView) row3.getChildAt(0);
mCosplay = (CheckTextView) row3.getChildAt(1);
ViewGroup row4 = (ViewGroup) getChildAt(4);
mAsianPorn = (CheckTextView) row4.getChildAt(0);
mMisc = (CheckTextView) row4.getChildAt(1);
mOptions = new CheckTextView[]{
mDoujinshi, mManga, mArtistCG, mGameCG, mWestern,
mNonH, mImageSets, mCosplay, mAsianPorn, mMisc
};
for (CheckTextView option : mOptions) {
option.setOnLongClickListener(this);
}
}
@Override
public boolean onLongClick(View v) {
if (v instanceof CheckTextView) {
boolean checked = ((CheckTextView) v).isChecked();
for (CheckTextView option : mOptions) {
if (option != v) {
option.setChecked(!checked);
}
}
}
return true;
}
/**
* Get category according to button.
*
* @return the category of this view
*/
public int getCategory() {
int category = 0;
if (!mDoujinshi.isChecked()) category |= EhConfig.DOUJINSHI;
if (!mManga.isChecked()) category |= EhConfig.MANGA;
if (!mArtistCG.isChecked()) category |= EhConfig.ARTIST_CG;
if (!mGameCG.isChecked()) category |= EhConfig.GAME_CG;
if (!mWestern.isChecked()) category |= EhConfig.WESTERN;
if (!mNonH.isChecked()) category |= EhConfig.NON_H;
if (!mImageSets.isChecked()) category |= EhConfig.IMAGE_SET;
if (!mCosplay.isChecked()) category |= EhConfig.COSPLAY;
if (!mAsianPorn.isChecked()) category |= EhConfig.ASIAN_PORN;
if (!mMisc.isChecked()) category |= EhConfig.MISC;
return category;
}
/**
* Set each button checked or not according to category.
*
* @param category target category
*/
public void setCategory(int category) {
mDoujinshi.setChecked(!NumberUtils.int2boolean(category & EhConfig.DOUJINSHI));
mManga.setChecked(!NumberUtils.int2boolean(category & EhConfig.MANGA));
mArtistCG.setChecked(!NumberUtils.int2boolean(category & EhConfig.ARTIST_CG));
mGameCG.setChecked(!NumberUtils.int2boolean(category & EhConfig.GAME_CG));
mWestern.setChecked(!NumberUtils.int2boolean(category & EhConfig.WESTERN));
mNonH.setChecked(!NumberUtils.int2boolean(category & EhConfig.NON_H));
mImageSets.setChecked(!NumberUtils.int2boolean(category & EhConfig.IMAGE_SET));
mCosplay.setChecked(!NumberUtils.int2boolean(category & EhConfig.COSPLAY));
mAsianPorn.setChecked(!NumberUtils.int2boolean(category & EhConfig.ASIAN_PORN));
mMisc.setChecked(!NumberUtils.int2boolean(category & EhConfig.MISC));
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_CATEGORY, getCategory());
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
setCategory(savedState.getInt(STATE_KEY_CATEGORY));
}
}
}
@@ -141,7 +141,7 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os);
mImagePath = temp.getPath();
mPreview.setImageBitmap(bitmap);
mPreview.setVisibility(VISIBLE);
((View) mPreview.getParent()).setVisibility(VISIBLE);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
@@ -166,7 +166,7 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
}
mImagePath = imagePath;
mPreview.setImageBitmap(bitmap);
mPreview.setVisibility(VISIBLE);
((View) mPreview.getParent()).setVisibility(VISIBLE);
} catch (FileNotFoundException e) {
// Ignore
} finally {
@@ -1,403 +0,0 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.tabs.TabLayout;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.easyrecyclerview.SimpleHolder;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.widget.RadioGridGroup;
import com.hippo.yorozuya.ViewUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class SearchLayout extends EasyRecyclerView implements CompoundButton.OnCheckedChangeListener,
View.OnClickListener, ImageSearchLayout.Helper, TabLayout.OnTabSelectedListener {
public static final int SEARCH_MODE_NORMAL = 0;
public static final int SEARCH_MODE_IMAGE = 1;
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_SEARCH_MODE = "search_mode";
private static final String STATE_KEY_ENABLE_ADVANCE = "enable_advance";
private static final int ITEM_TYPE_NORMAL = 0;
private static final int ITEM_TYPE_NORMAL_ADVANCE = 1;
private static final int ITEM_TYPE_IMAGE = 2;
private static final int ITEM_TYPE_ACTION = 3;
private static final int[] SEARCH_ITEM_COUNT_ARRAY = {
3, 2
};
private static final int[][] SEARCH_ITEM_TYPE = {
{ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION}, // SEARCH_MODE_NORMAL
{ITEM_TYPE_IMAGE, ITEM_TYPE_ACTION}, // SEARCH_MODE_IMAGE
};
private LayoutInflater mInflater;
@SearchMode
private int mSearchMode = SEARCH_MODE_NORMAL;
private boolean mEnableAdvance = false;
private View mNormalView;
private CategoryTable mCategoryTable;
private RadioGridGroup mNormalSearchMode;
private ImageView mNormalSearchModeHelp;
private Switch mEnableAdvanceSwitch;
private View mAdvanceView;
private AdvanceSearchTable mTableAdvanceSearch;
private ImageSearchLayout mImageView;
private View mActionView;
private TabLayout mAction;
private LinearLayoutManager mLayoutManager;
private SearchAdapter mAdapter;
private Helper mHelper;
public SearchLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SearchLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
@SuppressLint("InflateParams")
private void init(Context context) {
Resources resources = context.getResources();
mInflater = LayoutInflater.from(context);
mLayoutManager = new SearchLayoutManager(context);
mAdapter = new SearchAdapter();
mAdapter.setHasStableIds(true);
setLayoutManager(mLayoutManager);
setAdapter(mAdapter);
setHasFixedSize(true);
setClipToPadding(false);
((DefaultItemAnimator) getItemAnimator()).setSupportsChangeAnimations(false);
int interval = resources.getDimensionPixelOffset(R.dimen.search_layout_interval);
int paddingH = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_h);
int paddingV = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_v);
MarginItemDecoration decoration = new MarginItemDecoration(
interval, paddingH, paddingV, paddingH, paddingV);
addItemDecoration(decoration);
decoration.applyPaddings(this);
// Create normal view
View normalView = mInflater.inflate(R.layout.search_normal, null);
mNormalView = normalView;
mCategoryTable = normalView.findViewById(R.id.search_category_table);
mNormalSearchMode = normalView.findViewById(R.id.normal_search_mode);
mNormalSearchModeHelp = normalView.findViewById(R.id.normal_search_mode_help);
mEnableAdvanceSwitch = normalView.findViewById(R.id.search_enable_advance);
mNormalSearchModeHelp.setOnClickListener(this);
mEnableAdvanceSwitch.setOnCheckedChangeListener(this);
mEnableAdvanceSwitch.setSwitchPadding(resources.getDimensionPixelSize(R.dimen.switch_padding));
// Create advance view
mAdvanceView = mInflater.inflate(R.layout.search_advance, null);
mTableAdvanceSearch = mAdvanceView.findViewById(R.id.search_advance_search_table);
// Create image view
mImageView = (ImageSearchLayout) mInflater.inflate(R.layout.search_image, null);
mImageView.setHelper(this);
// Create action view
mActionView = mInflater.inflate(R.layout.search_action, null);
mActionView.setLayoutParams(new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
mAction = mActionView.findViewById(R.id.action);
mAction.addOnTabSelectedListener(this);
}
public void setHelper(Helper helper) {
mHelper = helper;
}
public void scrollSearchContainerToTop() {
mLayoutManager.scrollToPositionWithOffset(0, 0);
}
public void setImageUri(Uri imageUri) {
mImageView.setImageUri(imageUri);
}
public void setNormalSearchMode(int id) {
mNormalSearchMode.check(id);
}
@Override
public void onSelectImage() {
if (mHelper != null) {
mHelper.onSelectImage();
}
}
@Override
protected void dispatchSaveInstanceState(@NonNull SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
mNormalView.saveHierarchyState(container);
mAdvanceView.saveHierarchyState(container);
mImageView.saveHierarchyState(container);
mActionView.saveHierarchyState(container);
}
@Override
protected void dispatchRestoreInstanceState(@NonNull SparseArray<Parcelable> container) {
super.dispatchRestoreInstanceState(container);
mNormalView.restoreHierarchyState(container);
mAdvanceView.restoreHierarchyState(container);
mImageView.restoreHierarchyState(container);
mActionView.restoreHierarchyState(container);
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_SEARCH_MODE, mSearchMode);
state.putBoolean(STATE_KEY_ENABLE_ADVANCE, mEnableAdvance);
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
mSearchMode = savedState.getInt(STATE_KEY_SEARCH_MODE);
mEnableAdvance = savedState.getBoolean(STATE_KEY_ENABLE_ADVANCE);
}
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView == mEnableAdvanceSwitch) {
post(() -> {
mEnableAdvance = isChecked;
if (mSearchMode == SEARCH_MODE_NORMAL) {
if (mEnableAdvance) {
mAdapter.notifyItemInserted(1);
} else {
mAdapter.notifyItemRemoved(1);
}
if (mHelper != null) {
mHelper.onChangeSearchMode();
}
}
});
}
}
@SuppressLint("NonConstantResourceId")
public void formatListUrlBuilder(ListUrlBuilder urlBuilder, String query) throws EhException {
urlBuilder.reset();
switch (mSearchMode) {
case SEARCH_MODE_NORMAL:
int nsMode = mNormalSearchMode.getCheckedRadioButtonId();
if (nsMode == R.id.search_subscription_search) {
urlBuilder.setMode(ListUrlBuilder.MODE_SUBSCRIPTION);
} else if (nsMode == R.id.search_specify_uploader) {
urlBuilder.setMode(ListUrlBuilder.MODE_UPLOADER);
} else if (nsMode == R.id.search_specify_tag) {
urlBuilder.setMode(ListUrlBuilder.MODE_TAG);
} else {
urlBuilder.setMode(ListUrlBuilder.MODE_NORMAL);
}
urlBuilder.setKeyword(query);
urlBuilder.setCategory(mCategoryTable.getCategory());
if (mEnableAdvance) {
urlBuilder.setAdvanceSearch(mTableAdvanceSearch.getAdvanceSearch());
urlBuilder.setMinRating(mTableAdvanceSearch.getMinRating());
urlBuilder.setPageFrom(mTableAdvanceSearch.getPageFrom());
urlBuilder.setPageTo(mTableAdvanceSearch.getPageTo());
}
break;
case SEARCH_MODE_IMAGE:
urlBuilder.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
mImageView.formatListUrlBuilder(urlBuilder);
break;
}
}
@Override
public void onClick(View v) {
if (mNormalSearchModeHelp == v) {
new MaterialAlertDialogBuilder(getContext())
.setMessage(R.string.search_tip)
.show();
}
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
post(() -> setSearchMode(tab.getPosition()));
}
public void setSearchMode(@SearchMode int mode) {
int oldItemCount = mAdapter.getItemCount();
mSearchMode = mode;
int newItemCount = mAdapter.getItemCount();
mAdapter.notifyItemRangeRemoved(0, oldItemCount - 1);
mAdapter.notifyItemRangeInserted(0, newItemCount - 1);
if (mHelper != null) {
mHelper.onChangeSearchMode();
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
@IntDef({SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE})
@Retention(RetentionPolicy.SOURCE)
private @interface SearchMode {
}
public interface Helper {
void onChangeSearchMode();
void onSelectImage();
}
static class SearchLayoutManager extends LinearLayoutManager {
public SearchLayoutManager(Context context) {
super(context);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
private class SearchAdapter extends EasyRecyclerView.Adapter<SimpleHolder> {
@Override
public int getItemCount() {
int count = SEARCH_ITEM_COUNT_ARRAY[mSearchMode];
if (mSearchMode == SEARCH_MODE_NORMAL && !mEnableAdvance) {
count--;
}
return count;
}
@Override
public int getItemViewType(int position) {
int type = SEARCH_ITEM_TYPE[mSearchMode][position];
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION;
}
return type;
}
@NonNull
@Override
public SimpleHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
if (viewType == ITEM_TYPE_ACTION) {
ViewUtils.removeFromParent(mActionView);
view = mActionView;
} else {
view = mInflater.inflate(R.layout.search_category, parent, false);
TextView title = view.findViewById(R.id.category_title);
FrameLayout content = view.findViewById(R.id.category_content);
switch (viewType) {
case ITEM_TYPE_NORMAL:
title.setText(R.string.search_normal);
ViewUtils.removeFromParent(mNormalView);
content.addView(mNormalView);
break;
case ITEM_TYPE_NORMAL_ADVANCE:
title.setText(R.string.search_advance);
ViewUtils.removeFromParent(mAdvanceView);
content.addView(mAdvanceView);
break;
case ITEM_TYPE_IMAGE:
title.setText(R.string.search_image);
ViewUtils.removeFromParent(mImageView);
content.addView(mImageView);
break;
}
}
return new SimpleHolder(view);
}
@Override
public void onBindViewHolder(SimpleHolder holder, int position) {
if (holder.getItemViewType() == ITEM_TYPE_ACTION) {
mAction.selectTab(mAction.getTabAt(mSearchMode));
}
}
@Override
public long getItemId(int position) {
int type = SEARCH_ITEM_TYPE[mSearchMode][position];
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION;
}
return type;
}
}
}
@@ -0,0 +1,354 @@
/*
* Copyright (C) 2022 tarsin norbin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.annotation.IntDef
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.hippo.easyrecyclerview.EasyRecyclerView
import com.hippo.easyrecyclerview.MarginItemDecoration
import com.hippo.easyrecyclerview.SimpleHolder
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhConfig
import com.hippo.ehviewer.client.data.ListUrlBuilder
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.widget.RadioGridGroup
import com.hippo.yorozuya.NumberUtils
import com.hippo.yorozuya.ViewUtils
@SuppressLint("InflateParams")
class SearchLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : EasyRecyclerView(context, attrs), CompoundButton.OnCheckedChangeListener, View.OnClickListener,
ImageSearchLayout.Helper, OnTabSelectedListener {
@SearchMode
private var mSearchMode = SEARCH_MODE_NORMAL
private var mEnableAdvance = false
private var mCategoryGroup: ChipGroup
private var mNormalView: View
private var mNormalSearchMode: RadioGridGroup
private var mNormalSearchModeHelp: ImageView
private var mEnableAdvanceSwitch: SwitchMaterial
private var mAdvanceView: View
private var mTableAdvanceSearch: AdvanceSearchTable
private var mImageView: ImageSearchLayout
private var mActionView: View
private var mAction: TabLayout
private var mAdapter: SearchAdapter
private var mHelper: Helper? = null
private val mSharePref: SharedPreferences
private val mInflater: LayoutInflater
init {
mSharePref = PreferenceManager.getDefaultSharedPreferences(context)
mInflater = LayoutInflater.from(context)
val resources = context.resources
layoutManager = SearchLayoutManager(context)
mAdapter = SearchAdapter()
mAdapter.setHasStableIds(true)
adapter = mAdapter
setHasFixedSize(true)
clipToPadding = false
(itemAnimator as DefaultItemAnimator?)?.supportsChangeAnimations = false
val interval = resources.getDimensionPixelOffset(R.dimen.search_layout_interval)
val paddingH = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_h)
val paddingV = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_v)
val decoration = MarginItemDecoration(
interval, paddingH, paddingV, paddingH, paddingV
)
addItemDecoration(decoration)
decoration.applyPaddings(this)
// Create normal view
mNormalView = mInflater.inflate(R.layout.search_normal, null)
val mCategoryStored = mSharePref.getInt(SEARCH_CATEGORY_PREF, EhConfig.ALL_CATEGORY)
mCategoryGroup = mNormalView.findViewById(R.id.search_category_chipgroup)
for (mPair in mCategoryTable) {
val mChip = IdentifiedChip(context)
mChip.isCheckable = true
mChip.setText(mPair.second)
mChip.idt = mPair.first
mChip.isChecked = NumberUtils.int2boolean(mPair.first and mCategoryStored)
mCategoryGroup.addView(mChip)
}
mCategoryGroup.setOnCheckedStateChangeListener { _, checkedIds ->
var mCategory = 0
for (index in checkedIds) {
mCategory = mCategory or findViewById<IdentifiedChip>(index).idt
}
mSharePref.edit { putInt(SEARCH_CATEGORY_PREF, mCategory) }
}
mNormalSearchMode = mNormalView.findViewById(R.id.normal_search_mode)
mNormalSearchModeHelp = mNormalView.findViewById(R.id.normal_search_mode_help)
mEnableAdvanceSwitch = mNormalView.findViewById(R.id.search_enable_advance)
mNormalSearchModeHelp.setOnClickListener(this)
mEnableAdvanceSwitch.setOnCheckedChangeListener(this)
mEnableAdvanceSwitch.switchPadding =
resources.getDimensionPixelSize(R.dimen.switch_padding)
// Create advance view
mAdvanceView = mInflater.inflate(R.layout.search_advance, null)
mTableAdvanceSearch = mAdvanceView.findViewById(R.id.search_advance_search_table)
// Create image view
mImageView = mInflater.inflate(R.layout.search_image, null) as ImageSearchLayout
mImageView.setHelper(this)
// Create action view
mActionView = mInflater.inflate(R.layout.search_action, null)
mActionView.layoutParams = LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
mAction = mActionView.findViewById(R.id.action)
mAction.addOnTabSelectedListener(this)
}
fun setHelper(helper: Helper?) {
mHelper = helper
}
fun scrollSearchContainerToTop() {
(layoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0)
}
fun setImageUri(imageUri: Uri?) {
mImageView.setImageUri(imageUri)
}
fun setNormalSearchMode(id: Int) {
mNormalSearchMode.check(id)
}
override fun onSelectImage() {
if (mHelper != null) {
mHelper!!.onSelectImage()
}
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
if (buttonView === mEnableAdvanceSwitch) {
post {
mEnableAdvance = isChecked
if (mSearchMode == SEARCH_MODE_NORMAL) {
if (mEnableAdvance) {
mAdapter.notifyItemInserted(1)
} else {
mAdapter.notifyItemRemoved(1)
}
if (mHelper != null) {
mHelper!!.onChangeSearchMode()
}
}
}
}
}
@SuppressLint("NonConstantResourceId")
@Throws(EhException::class)
fun formatListUrlBuilder(urlBuilder: ListUrlBuilder, query: String?) {
urlBuilder.reset()
when (mSearchMode) {
SEARCH_MODE_NORMAL -> {
when (mNormalSearchMode.checkedRadioButtonId) {
R.id.search_subscription_search -> {
urlBuilder.mode = ListUrlBuilder.MODE_SUBSCRIPTION
}
R.id.search_specify_uploader -> {
urlBuilder.mode = ListUrlBuilder.MODE_UPLOADER
}
R.id.search_specify_tag -> {
urlBuilder.mode = ListUrlBuilder.MODE_TAG
}
else -> {
urlBuilder.mode = ListUrlBuilder.MODE_NORMAL
}
}
urlBuilder.keyword = query
urlBuilder.category = PreferenceManager.getDefaultSharedPreferences(context).getInt(
SEARCH_CATEGORY_PREF, 0
)
if (mEnableAdvance) {
urlBuilder.advanceSearch = mTableAdvanceSearch.advanceSearch
urlBuilder.minRating = mTableAdvanceSearch.minRating
urlBuilder.pageFrom = mTableAdvanceSearch.pageFrom
urlBuilder.pageTo = mTableAdvanceSearch.pageTo
}
}
SEARCH_MODE_IMAGE -> {
urlBuilder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
mImageView.formatListUrlBuilder(urlBuilder)
}
}
}
override fun onClick(v: View) {
if (mNormalSearchModeHelp === v) {
MaterialAlertDialogBuilder(context)
.setMessage(R.string.search_tip)
.show()
}
}
override fun onTabSelected(tab: TabLayout.Tab) {
post { setSearchMode(tab.position) }
}
fun setSearchMode(@SearchMode mode: Int) {
val oldItemCount = mAdapter.itemCount
mSearchMode = mode
val newItemCount = mAdapter.itemCount
mAdapter.notifyItemRangeRemoved(0, oldItemCount - 1)
mAdapter.notifyItemRangeInserted(0, newItemCount - 1)
if (mHelper != null) {
mHelper!!.onChangeSearchMode()
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {}
@IntDef(SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
private annotation class SearchMode
interface Helper {
fun onChangeSearchMode()
fun onSelectImage()
}
internal class SearchLayoutManager(context: Context?) : LinearLayoutManager(context) {
override fun onLayoutChildren(recycler: Recycler, state: State) {
try {
super.onLayoutChildren(recycler, state)
} catch (e: IndexOutOfBoundsException) {
e.printStackTrace()
}
}
}
private inner class SearchAdapter : Adapter<SimpleHolder>() {
override fun getItemCount(): Int {
var count = SEARCH_ITEM_COUNT_ARRAY[mSearchMode]
if (mSearchMode == SEARCH_MODE_NORMAL && !mEnableAdvance) {
count--
}
return count
}
override fun getItemViewType(position: Int): Int {
var type = SEARCH_ITEM_TYPE[mSearchMode][position]
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION
}
return type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleHolder {
val view: View?
if (viewType == ITEM_TYPE_ACTION) {
ViewUtils.removeFromParent(mActionView)
view = mActionView
} else {
view = mInflater.inflate(R.layout.search_category, parent, false)
val title = view.findViewById<TextView>(R.id.category_title)
val content = view.findViewById<FrameLayout>(R.id.category_content)
when (viewType) {
ITEM_TYPE_NORMAL -> {
title.setText(R.string.search_normal)
ViewUtils.removeFromParent(mNormalView)
content.addView(mNormalView)
}
ITEM_TYPE_NORMAL_ADVANCE -> {
title.setText(R.string.search_advance)
ViewUtils.removeFromParent(mAdvanceView)
content.addView(mAdvanceView)
}
ITEM_TYPE_IMAGE -> {
title.setText(R.string.search_image)
ViewUtils.removeFromParent(mImageView)
content.addView(mImageView)
}
}
}
return SimpleHolder(view)
}
override fun onBindViewHolder(holder: SimpleHolder, position: Int) {
if (holder.itemViewType == ITEM_TYPE_ACTION) {
mAction.selectTab(mAction.getTabAt(mSearchMode))
}
}
override fun getItemId(position: Int): Long {
var type = SEARCH_ITEM_TYPE[mSearchMode][position]
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION
}
return type.toLong()
}
}
inner class IdentifiedChip @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : Chip(context, attrs) {
var idt = 0
}
companion object {
const val SEARCH_MODE_NORMAL = 0
const val SEARCH_MODE_IMAGE = 1
const val SEARCH_CATEGORY_PREF = "search_pref"
private const val ITEM_TYPE_NORMAL = 0
private const val ITEM_TYPE_NORMAL_ADVANCE = 1
private const val ITEM_TYPE_IMAGE = 2
private const val ITEM_TYPE_ACTION = 3
private val SEARCH_ITEM_COUNT_ARRAY = intArrayOf(
3, 2
)
private val mCategoryTable: ArrayList<Pair<Int, Int>> = arrayListOf(
Pair(EhConfig.DOUJINSHI, R.string.doujinshi),
Pair(EhConfig.MANGA, R.string.manga),
Pair(EhConfig.ARTIST_CG, R.string.artist_cg),
Pair(EhConfig.GAME_CG, R.string.game_cg),
Pair(EhConfig.WESTERN, R.string.western),
Pair(EhConfig.NON_H, R.string.non_h),
Pair(EhConfig.IMAGE_SET, R.string.image_set),
Pair(EhConfig.COSPLAY, R.string.cosplay),
Pair(EhConfig.ASIAN_PORN, R.string.asian_porn),
Pair(EhConfig.MISC, R.string.misc)
)
private val SEARCH_ITEM_TYPE = arrayOf(
intArrayOf(ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION), intArrayOf(
ITEM_TYPE_IMAGE, ITEM_TYPE_ACTION
)
)
}
}
@@ -41,7 +41,12 @@ public class BatteryView extends AppCompatTextView {
private boolean mCharging = false;
private BatteryDrawable mDrawable;
private boolean mAttached = false; private final Runnable mCharger = new Runnable() {
private boolean mAttached = false;
private boolean mIsChargerWorking = false;
public BatteryView(Context context) {
super(context);
init();
} private final Runnable mCharger = new Runnable() {
private int level = 0;
@@ -55,43 +60,10 @@ public class BatteryView extends AppCompatTextView {
getHandler().postDelayed(mCharger, 200);
}
};
private boolean mIsChargerWorking = false;
public BatteryView(Context context) {
super(context);
init();
}
public BatteryView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@SuppressLint("SetTextI18n")
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
== BatteryManager.BATTERY_STATUS_CHARGING);
if (mLevel != level || mCharging != charging) {
mLevel = level;
mCharging = charging;
if (mCharging && mLevel != 100) {
startCharger();
} else {
stopCharger();
mDrawable.setElect(mLevel);
}
if (level <= BatteryDrawable.WARN_LIMIT && !charging) {
setTextColor(mWarningColor);
} else {
setTextColor(mColor);
}
setText(mLevel + "%");
}
}
};
}
public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -129,7 +101,36 @@ public class BatteryView extends AppCompatTextView {
getHandler().post(mCharger);
mIsChargerWorking = true;
}
}
} private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@SuppressLint("SetTextI18n")
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
== BatteryManager.BATTERY_STATUS_CHARGING);
if (mLevel != level || mCharging != charging) {
mLevel = level;
mCharging = charging;
if (mCharging && mLevel != 100) {
startCharger();
} else {
stopCharger();
mDrawable.setElect(mLevel);
}
if (level <= BatteryDrawable.WARN_LIMIT && !charging) {
setTextColor(mWarningColor);
} else {
setTextColor(mColor);
}
setText(mLevel + "%");
}
}
};
private void stopCharger() {
if (mIsChargerWorking) {
@@ -173,4 +174,6 @@ public class BatteryView extends AppCompatTextView {
}
@@ -1,236 +0,0 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatCheckedTextView;
import com.hippo.ehviewer.R;
public class CheckTextView extends AppCompatCheckedTextView implements View.OnClickListener {
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
protected boolean mForegroundInPadding = true;
boolean mForegroundBoundsChanged = false;
private Drawable mForeground;
private int mForegroundGravity = Gravity.FILL;
public CheckTextView(Context context) {
this(context, null);
}
public CheckTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CheckTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckTextView,
defStyleAttr, 0);
mForegroundGravity = a.getInt(
R.styleable.CheckTextView_android_foregroundGravity, mForegroundGravity);
Drawable d = a.getDrawable(R.styleable.CheckTextView_android_foreground);
if (d != null) {
setForeground(d);
}
mForegroundInPadding = a.getBoolean(
R.styleable.CheckTextView_foregroundInsidePadding, true);
a.recycle();
setOnClickListener(this);
}
/**
* Describes how the foreground is positioned.
*
* @return foreground gravity.
* @see #setForegroundGravity(int)
*/
public int getForegroundGravity() {
return mForegroundGravity;
}
/**
* Describes how the foreground is positioned. Defaults to START and TOP.
*
* @param foregroundGravity See {@link android.view.Gravity}
* @see #getForegroundGravity()
*/
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.START;
}
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.TOP;
}
mForegroundGravity = foregroundGravity;
if (mForegroundGravity == Gravity.FILL && mForeground != null) {
Rect padding = new Rect();
mForeground.getPadding(padding);
}
requestLayout();
}
}
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
return super.verifyDrawable(who) || (who == mForeground);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mForeground != null) {
mForeground.jumpToCurrentState();
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mForeground != null && mForeground.isStateful()) {
mForeground.setState(getDrawableState());
}
}
/**
* Returns the drawable used as the foreground of this FrameLayout. The
* foreground drawable, if non-null, is always drawn on top of the children.
*
* @return A Drawable or null if no foreground was set.
*/
public Drawable getForeground() {
return mForeground;
}
/**
* Supply a Drawable that is to be rendered on top of all of the child
* views in the frame layout. Any padding in the Drawable will be taken
* into account by ensuring that the children are inset to be placed
* inside of the padding area.
*
* @param drawable The Drawable to be drawn on top of the children.
*/
public void setForeground(Drawable drawable) {
if (mForeground != drawable) {
if (mForeground != null) {
mForeground.setCallback(null);
unscheduleDrawable(mForeground);
}
mForeground = drawable;
if (drawable != null) {
setWillNotDraw(false);
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
if (mForegroundGravity == Gravity.FILL) {
Rect padding = new Rect();
drawable.getPadding(padding);
}
} else {
setWillNotDraw(true);
}
requestLayout();
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mForegroundBoundsChanged |= changed;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mForegroundBoundsChanged = true;
}
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if (mForeground != null) {
final Drawable foreground = mForeground;
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
final Rect selfBounds = mSelfBounds;
final Rect overlayBounds = mOverlayBounds;
final int w = getRight() - getLeft();
final int h = getBottom() - getTop();
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
w - getPaddingRight(), h - getPaddingBottom());
}
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
@Override
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
if (mForeground != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mForeground.setHotspot(x, y);
}
}
}
@Override
public void onClick(View v) {
setChecked(!isChecked());
}
}
@@ -41,7 +41,6 @@ import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.image.ImageBitmap;
import com.hippo.image.ImageDrawable;
import com.hippo.image.RecycledException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -285,7 +284,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
Drawable drawable;
try {
drawable = new ImageDrawable(value);
} catch (RecycledException e) {
} catch (Exception e) {
// The image might be recycled because it is removed from memory cache.
Log.d(TAG, "The image is recycled", e);
return false;
@@ -55,7 +55,20 @@ public class TextClock extends AppCompatTextView {
private boolean mAttached;
private Calendar mTime;
private String mTimeZone; private final Runnable mTicker = new Runnable() {
private String mTimeZone;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra("time-zone");
createTime(timeZone);
}
onTimeChanged();
}
};
public TextClock(Context context) {
this(context, null);
} private final Runnable mTicker = new Runnable() {
@Override
public void run() {
onTimeChanged();
@@ -66,31 +79,7 @@ public class TextClock extends AppCompatTextView {
getHandler().postAtTime(mTicker, next);
}
};
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra("time-zone");
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();
}
};
public TextClock(Context context) {
this(context, null);
}
public TextClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -109,7 +98,19 @@ public class TextClock extends AppCompatTextView {
createTime(mTimeZone);
// Wait until onAttachedToWindow() to handle the ticker
chooseFormat(false);
}
} 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();
}
};
private void createTime(String timeZone) {
if (timeZone != null) {
@@ -261,4 +262,6 @@ public class TextClock extends AppCompatTextView {
}
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2018 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/content_activated" android:state_activated="true" />
<item android:color="@color/content" />
</selector>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2018 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/content_activated_black" android:state_activated="true" />
<item android:color="@color/content_black" />
</selector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

@@ -52,13 +52,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="?android:attr/colorBackground"
app:headerLayout="@layout/nav_header_main"
app:insetForeground="@null"
app:itemShapeAppearanceOverlay="@style/ShapeAppearanceOverlay.MaterialComponents.NavigationView"
app:itemShapeInsetBottom="0dp"
app:itemShapeInsetStart="0dp"
app:itemShapeInsetTop="0dp"
app:menu="@menu/nav_drawer_main" />
<com.hippo.widget.DrawerView
+24 -37
View File
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
<rikka.widget.borderview.BorderNestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@@ -37,8 +37,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:text="@string/settings_read_screen_rotation"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_screen_rotation" />
<com.hippo.widget.CuteSpinner
android:id="@+id/screen_rotation"
@@ -60,8 +59,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:text="@string/settings_read_reading_direction"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_reading_direction" />
<com.hippo.widget.CuteSpinner
android:id="@+id/reading_direction"
@@ -83,8 +81,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:text="@string/settings_read_page_scaling"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_page_scaling" />
<com.hippo.widget.CuteSpinner
android:id="@+id/page_scaling"
@@ -106,8 +103,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:text="@string/settings_read_start_position"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_start_position" />
<com.hippo.widget.CuteSpinner
android:id="@+id/start_position"
@@ -129,8 +125,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:text="@string/read_theme"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/read_theme" />
<com.hippo.widget.CuteSpinner
android:id="@+id/read_theme"
@@ -142,68 +137,60 @@
</LinearLayout>
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/keep_screen_on"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_keep_screen_on"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_keep_screen_on" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/show_clock"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_show_clock"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_show_clock" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/show_progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_show_progress"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_show_progress" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/show_battery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_show_battery"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_show_battery" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/show_page_interval"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_show_page_interval"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_show_page_interval" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/volume_page"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_volume_page"
android:textColor="?android:attr/textColorPrimary" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/reverse_volume_page"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_reverse_volume"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_reverse_volume" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/reading_fullscreen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_reading_fullscreen"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_reading_fullscreen" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/custom_screen_lightness"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_read_custom_screen_lightness"
android:textColor="?android:attr/textColorPrimary" />
android:text="@string/settings_read_custom_screen_lightness" />
<SeekBar
android:id="@+id/screen_lightness"
@@ -212,4 +199,4 @@
android:max="200" />
</LinearLayout>
</rikka.widget.borderview.BorderNestedScrollView>
</androidx.core.widget.NestedScrollView>
+2 -5
View File
@@ -14,12 +14,10 @@
~ limitations under the License.
-->
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Widget.Material3.CardView.Filled"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:cardBackgroundColor="?android:attr/windowBackground">
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
@@ -32,7 +30,6 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
android:saveEnabled="false" />
<FrameLayout
@@ -29,7 +29,6 @@
android:gravity="center"
android:padding="8dp"
android:text="@string/newer_version_avaliable"
android:textColor="?attr/textColorThemeAccent"
android:visibility="gone" />
<HorizontalScrollView
@@ -29,7 +29,6 @@
android:id="@+id/comments_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="?attr/textColorThemeAccent" />
android:layout_gravity="center_horizontal" />
</LinearLayout>
@@ -65,41 +65,32 @@
android:layout_alignTop="@id/thumb"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
android:layout_toRightOf="@id/thumb"
android:layout_toEndOf="@id/thumb"
android:ellipsize="end"
android:maxLines="5"
android:textSize="@dimen/text_little_large" />
<TextView
<Button
android:id="@+id/uploader"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignLeft="@id/title"
android:layout_marginTop="8dp"
android:layout_marginRight="@dimen/keyline_margin"
android:background="?selectableItemBackground"
android:layout_alignStart="@id/title"
android:ellipsize="end"
android:singleLine="true"
android:textSize="@dimen/text_little_small" />
android:textAllCaps="true" />
<TextView
<Button
android:id="@+id/category"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/uploader"
android:layout_alignLeft="@id/title"
android:layout_marginTop="12dp"
android:layout_marginRight="@dimen/keyline_margin"
android:background="@drawable/category_background"
android:layout_below="@id/title"
android:layout_toRightOf="@id/uploader"
android:ellipsize="end"
android:paddingLeft="8dp"
android:paddingTop="2dp"
android:paddingRight="8dp"
android:paddingBottom="2dp"
android:singleLine="true"
android:textAllCaps="true"
android:textStyle="bold" />
android:textAllCaps="true" />
<ImageView
android:id="@+id/other_actions"
@@ -119,47 +110,37 @@
</RelativeLayout>
<com.google.android.material.card.MaterialCardView
<LinearLayout
android:id="@+id/action_card"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
app:cardUseCompatPadding="true">
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
<Button
android:id="@+id/download"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:layout_weight="1"
android:clickable="true"
android:focusable="true" />
<TextView
android:id="@+id/download"
style="@style/ButtonInCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true" />
<android.widget.Space
android:layout_width="8dp"
android:layout_height="wrap_content" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?attr/dividerColor" />
<Button
android:id="@+id/read"
style="@style/Widget.Material3.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:text="@string/read" />
<TextView
android:id="@+id/read"
style="@style/ButtonInCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:text="@string/read" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>
</FrameLayout>
@@ -88,7 +88,6 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:text="@string/more_information"
android:textColor="?attr/textColorThemeAccent" />
android:text="@string/more_information" />
</LinearLayout>
@@ -39,7 +39,6 @@
android:id="@+id/preview_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textColor="?attr/textColorThemeAccent" />
android:layout_gravity="center_horizontal" />
</LinearLayout>
@@ -26,7 +26,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/no_tags"
android:textColor="?attr/textColorThemeAccent" />
android:text="@string/no_tags" />
</LinearLayout>
+13 -14
View File
@@ -46,7 +46,7 @@
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginRight="8dp"
android:layout_toRightOf="@id/thumb" />
android:layout_toEndOf="@id/thumb" />
<TextView
android:id="@+id/uploader"
@@ -54,7 +54,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginTop="2dp" />
<com.hippo.ehviewer.widget.SimpleRatingView
@@ -62,7 +62,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/uploader"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginTop="2dp" />
<TextView
@@ -70,7 +70,7 @@
style="@style/CardMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_alignBottom="@id/thumb"
android:layout_marginBottom="8dp"
android:paddingLeft="8dp"
@@ -85,8 +85,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/thumb"
android:layout_alignParentRight="true"
android:paddingRight="8dp">
android:layout_alignParentEnd="true"
android:paddingEnd="8dp">
<ImageView
android:id="@+id/start"
@@ -115,10 +115,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/actions"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginBottom="4dp"
android:paddingRight="16dp"
android:textColor="?attr/textColorThemeAccent" />
android:paddingRight="16dp" />
<ProgressBar
android:id="@+id/progress_bar"
@@ -126,8 +125,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/actions"
android:layout_alignLeft="@id/title"
android:layout_marginRight="8dp"
android:layout_alignStart="@id/title"
android:layout_marginEnd="8dp"
android:indeterminate="false" />
<TextView
@@ -135,7 +134,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/progress_bar"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:textSize="@dimen/text_super_small" />
<TextView
@@ -143,8 +142,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/progress_bar"
android:layout_alignParentRight="true"
android:layout_marginRight="8dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="8dp"
android:textSize="@dimen/text_super_small" />
</RelativeLayout>
+24 -16
View File
@@ -29,13 +29,21 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
<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"
app:maxAspect="1.333"
app:minAspect="0.333"
app:retryType="click" />
android:layout_height="@dimen/gallery_list_thumb_height">
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:maxAspect="1.333"
app:minAspect="0.333"
app:retryType="click" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/title"
@@ -46,7 +54,7 @@
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginRight="8dp"
android:layout_toRightOf="@id/thumb" />
android:layout_toEndOf="@id/thumb_card" />
<TextView
android:id="@+id/uploader"
@@ -54,7 +62,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginTop="2dp" />
<TextView
@@ -62,8 +70,8 @@
style="@style/CardMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/title"
android:layout_alignBottom="@id/thumb"
android:layout_alignStart="@id/title"
android:layout_alignBottom="@id/thumb_card"
android:layout_marginBottom="8dp"
android:paddingLeft="8dp"
android:paddingTop="2dp"
@@ -77,7 +85,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/category"
android:layout_alignLeft="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginBottom="4dp" />
<TextView
@@ -85,17 +93,17 @@
style="@style/CardMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/thumb"
android:layout_alignParentRight="true"
android:layout_marginRight="6dp"
android:layout_alignBottom="@id/thumb_card"
android:layout_alignParentEnd="true"
android:layout_marginEnd="6dp"
android:layout_marginBottom="4dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/posted"
android:layout_alignParentRight="true"
android:layout_marginRight="6dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="6dp"
android:divider="@drawable/spacer_x6"
android:gravity="center_vertical"
android:orientation="horizontal"
@@ -19,15 +19,21 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<com.hippo.ehviewer.widget.FixedThumb xmlns:auto="http://schemas.android.com/apk/res-auto"
android:id="@+id/image"
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?selectableItemBackground"
auto:aspect="0.667"
auto:maxAspect="0.8"
auto:minAspect="0.5"
auto:retryType="longClick" />
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.FixedThumb xmlns:auto="http://schemas.android.com/apk/res-auto"
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?selectableItemBackground"
auto:aspect="0.667"
auto:maxAspect="0.8"
auto:minAspect="0.5"
auto:retryType="longClick" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/text"
@@ -27,43 +27,4 @@
android:scaleType="centerCrop"
app:srcCompat="@drawable/sadpanda_low_poly" />
<com.hippo.widget.LoadImageView
android:id="@+id/avatar"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="bottom"
android:layout_marginLeft="16dp"
android:layout_marginBottom="48dp"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize50Percent" />
<TextView
android:id="@+id/display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginLeft="16dp"
android:layout_marginBottom="16dp"
android:textColor="@android:color/white"
android:textSize="@dimen/text_small" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:background="@android:color/transparent"
android:clipToPadding="false"
android:padding="8dp">
<ImageView
android:id="@+id/night_mode"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="6dp"
app:srcCompat="@drawable/ic_baseline_dark_mode_24"
app:tint="#fff" />
</FrameLayout>
</FrameLayout>
@@ -154,8 +154,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:text="@string/from_clipboard"
android:textColor="?attr/textColorThemeAccent" />
android:text="@string/from_clipboard" />
</LinearLayout>
</LinearLayout>
+3 -6
View File
@@ -135,8 +135,7 @@
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/sign_in_via_webview"
android:textColor="?attr/textColorThemeAccent" />
android:text="@string/sign_in_via_webview" />
<TextView
android:id="@+id/sign_in_via_cookies"
@@ -144,8 +143,7 @@
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/sign_in_via_cookies"
android:textColor="?attr/textColorThemeAccent" />
android:text="@string/sign_in_via_cookies" />
</LinearLayout>
@@ -156,8 +154,7 @@
android:layout_gravity="center_horizontal"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/skip_signing_in"
android:textColor="?attr/textColorThemeAccent" />
android:text="@string/skip_signing_in" />
</LinearLayout>
</ScrollView>
+3 -3
View File
@@ -20,8 +20,8 @@
android:layout_height="match_parent"
android:orientation="vertical">
<com.hippo.ehviewer.widget.CategoryTable
android:id="@+id/search_category_table"
<com.google.android.material.chip.ChipGroup
android:id="@+id/search_category_chipgroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/search_item_interval" />
@@ -89,7 +89,7 @@
android:layout_height="wrap_content"
android:layout_weight="1" />
<Switch
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/search_enable_advance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -1,149 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2015 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/doujinshi"
android:text="@string/doujinshi"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/manga"
android:text="@string/manga"
android:textColor="@android:color/white" />
</TableRow>
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/artist_cg"
android:text="@string/artist_cg"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/game_cg"
android:text="@string/game_cg"
android:textColor="@android:color/white" />
</TableRow>
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/western"
android:text="@string/western"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/non_h"
android:text="@string/non_h"
android:textColor="@android:color/white" />
</TableRow>
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/image_set"
android:text="@string/image_set"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginBottom="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/cosplay"
android:text="@string/cosplay"
android:textColor="@android:color/white" />
</TableRow>
<TableRow>
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_marginRight="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/asian_porn"
android:text="@string/asian_porn"
android:textColor="@android:color/white" />
<com.hippo.widget.CheckTextView
style="@style/CategoryText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/category_table_item_margin"
android:layout_marginTop="@dimen/category_table_item_margin"
android:layout_weight="1"
android:background="@color/misc"
android:text="@string/misc"
android:textColor="@android:color/white" />
</TableRow>
</merge>
+18 -11
View File
@@ -16,19 +16,26 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<com.hippo.widget.FixedAspectImageView
android:id="@+id/preview"
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:adjustViewBounds="true"
android:maxWidth="@dimen/image_search_max_size"
android:maxHeight="@dimen/image_search_max_size"
android:visibility="gone" />
android:visibility="gone">
<com.hippo.widget.FixedAspectImageView
android:id="@+id/preview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:maxWidth="@dimen/image_search_max_size"
android:maxHeight="@dimen/image_search_max_size" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/select_image"
style="@style/Widget.MaterialComponents.Button"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -44,7 +51,7 @@
android:id="@+id/search_uss"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/advance_search_table_item_margin"
android:layout_marginEnd="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="true"
android:minWidth="0dp"
@@ -55,7 +62,7 @@
android:id="@+id/search_osc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
android:layout_marginStart="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="false"
android:minWidth="0dp"
@@ -69,7 +76,7 @@
android:id="@+id/search_se"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/advance_search_table_item_margin"
android:layout_marginEnd="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="false"
android:minWidth="0dp"
@@ -79,7 +86,7 @@
<Space
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
android:layout_marginStart="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:checked="false" />
+5 -6
View File
@@ -14,20 +14,19 @@
~ limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_add"
android:icon="@drawable/v_plus_dark_x24"
android:title="@string/settings"
app:showAsAction="always" />
android:showAsAction="always"
android:title="@string/settings" />
<item
android:id="@+id/action_default_download_label"
android:icon="@drawable/v_download_box_dark_x24"
android:title="@string/default_download_label"
app:showAsAction="ifRoom" />
android:showAsAction="ifRoom"
android:title="@string/default_download_label" />
</menu>
@@ -14,13 +14,18 @@
~ limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_help"
android:icon="@drawable/v_help_circle_x24"
android:showAsAction="ifRoom"
android:title="@string/add" />
<item
android:id="@+id/action_add"
android:icon="@drawable/v_plus_dark_x24"
android:title="@string/add"
app:showAsAction="always" />
android:showAsAction="always"
android:title="@string/add" />
</menu>
-6
View File
@@ -17,12 +17,6 @@
<resources>
<color name="primary_drawable">#ffffffff</color>
<color name="secondary_drawable">#4dffffff</color>
<color name="content">@color/grey_825</color>
<color name="content_activated">@color/grey_600</color>
<color name="tag_group_background">@color/grey_775</color>
<color name="tag_background">@color/grey_775</color>
<!-- divider -->
<color name="divider">#20ffffff</color>
@@ -759,4 +759,6 @@
<string name="app_link_not_verified_message">对于 Android 12 及更新的版本,您需要手动添加链接到已验证链接才能在 EhViewer 中打开 E-Hentai 链接。</string>
<string name="app_link_not_verified_title">应用链接未验证</string>
<string name="open_by_default">默认打开</string>
<string name="show_library_version">依赖库之版本</string>
</resources>
-18
View File
@@ -18,34 +18,16 @@
<declare-styleable name="Theme">
<attr name="colorEdgeEffect" format="color" />
<attr name="dividerColor" format="color" />
<attr name="textColorThemeAccent" format="color" />
<!-- For light theme, it's secondary dark color -->
<!-- For dark theme, it's secondary light color -->
<!-- For black theme, it's secondary light color -->
<attr name="drawableColorSecondary" format="color" />
<!-- For light theme, it's primary color -->
<!-- For dark theme, it's primary light color -->
<!-- For black theme, it's primary light color -->
<attr name="drawableColorThemePrimary" format="color" />
<!-- Color of card background -->
<attr name="contentColorThemePrimary" format="color" />
<attr name="contentColorThemeAccent" format="color" />
<attr name="contentColorReactive" format="color" />
<attr name="guideBackgroundColor" format="color" />
<attr name="guideTitleColor" format="color" />
<attr name="guideTextColor" format="color" />
<attr name="tagGroupBackgroundColor" format="color" />
<attr name="tagBackgroundColor" format="color" />
<attr name="galleryDetailHeaderTitleColor" format="color" />
<attr name="galleryDetailHeaderBackgroundColor" format="color" />
</declare-styleable>
<declare-styleable name="ProgressView">
-33
View File
@@ -15,52 +15,20 @@
-->
<resources>
<!-- primary_drawable 54% -->
<!-- secondary_drawable 26% -->
<!-- primary_drawable_dark 100% -->
<!-- secondary_drawable_dark 30% -->
<!-- primary_drawable_black 80% -->
<!-- secondary_drawable_black 24% -->
<color name="primary_drawable">#8a000000</color>
<color name="secondary_drawable">#42000000</color>
<color name="primary_drawable_dark">#ffffffff</color>
<color name="primary_drawable_black">#ccffffff</color>
<color name="secondary_drawable_black">#3dffffff</color>
<color name="content">#fff</color>
<color name="content_black">@color/grey_925</color>
<color name="content_activated">@color/grey_300</color>
<color name="content_activated_black">@color/grey_700</color>
<color name="tag_group_background">@color/colorAccent</color>
<color name="tag_group_background_black">@color/grey_875</color>
<color name="tag_background">@color/colorPrimary</color>
<color name="tag_background_black">@color/grey_875</color>
<color name="red_500">#fff44336</color>
<color name="teal_500">#ff009688</color>
<color name="purple_a200">#ffe040fb</color>
<color name="purple_a700">#ffaa00ff</color>
<color name="cyan_600">#ff00acc1</color>
<color name="light_green_600">#ff7cb342</color>
<color name="yellow_800">#fff9a825</color>
<color name="deep_orange_400">#ffff7043</color>
<color name="grey_925">#ff191919</color>
<color name="grey_875">#ff292929</color>
<color name="grey_825">#ff3a3a3a</color>
<color name="grey_775">#ff4a4a4a</color>
<color name="grey_700">#ff616161</color>
<color name="grey_600">#ff757575</color>
<color name="grey_500">#ff9e9e9e</color>
<color name="grey_300">#ffe0e0e0</color>
<color name="brown_500">#ff795548</color>
<color name="colorPrimary">@color/teal_500</color>
<color name="colorAccent">@color/purple_a200</color>
<color name="loading_indicator_red">#ffe53935</color>
<color name="loading_indicator_purple">@color/purple_a700</color>
<color name="loading_indicator_blue">#ff1d87e4</color>
@@ -68,7 +36,6 @@
<color name="loading_indicator_green">@color/light_green_600</color>
<color name="loading_indicator_yellow">@color/yellow_800</color>
<color name="loading_indicator_orange">@color/deep_orange_400</color>
<color name="check_text_view_mask">#4a000000</color>
<!-- Red -->
+2
View File
@@ -823,4 +823,6 @@
<string name="app_link_not_verified_message">For Android 12 and newer, you need to manually add link to verified links in order to open E-Hentai links in EhViewer.</string>
<string name="app_link_not_verified_title">App links not verified</string>
<string name="open_by_default">Open by default</string>
<string name="show_library_version">Library Version</string>
</resources>
+6 -21
View File
@@ -19,7 +19,7 @@
<style name="AppTheme" parent="Base.AppTheme" />
<!-- TextAppearance -->
<style name="TextAppearance" parent="TextAppearance.AppCompat" />
<style name="TextAppearance" parent="TextAppearance.Material3.TitleMedium" />
<!-- Progress View -->
<style name="ProgressView">
@@ -30,7 +30,6 @@
<style name="TextAppearance.CardTitle">
<item name="android:textSize">@dimen/text_little_small</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="CardTitle">
@@ -42,7 +41,6 @@
<style name="TextAppearance.CardMessage">
<item name="android:textSize">@dimen/text_small</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="CardMessage">
@@ -54,16 +52,15 @@
<!-- CategoryText -->
<style name="CategoryText">
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Subhead</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.ActionBar.Subtitle
</item>
<item name="android:textStyle">bold</item>
<item name="android:gravity">center</item>
<item name="android:minHeight">@dimen/category_table_item_height</item>
<item name="android:foreground">@drawable/check_text_view_foreground</item>
<item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
</style>
<style name="TextAppearance.CategoryTitle" parent="TextAppearance.AppCompat.Small">
<item name="android:textColor">?attr/textColorThemeAccent</item>
<style name="TextAppearance.CategoryTitle" parent="TextAppearance.Material3.TitleMedium">
<item name="android:textStyle">bold</item>
</style>
@@ -75,13 +72,12 @@
</style>
<style name="ButtonInCard">
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Button</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.LabelLarge</item>
<item name="android:textStyle">bold</item>
<item name="android:gravity">center</item>
<item name="android:minHeight">48dp</item>
<item name="android:minWidth">88dp</item>
<item name="android:padding">8dp</item>
<item name="android:textColor">?attr/colorPrimary</item>
</style>
<style name="Slider">
@@ -95,18 +91,7 @@
<item name="indicatorColor">?attr/dividerColor</item>
</style>
<style name="Guide.Title" parent="TextAppearance.AppCompat.Title">
<style name="Guide.Title" parent="TextAppearance.Material3.TitleMedium">
<item name="android:textColor">?attr/guideTitleColor</item>
</style>
<style name="ShapeAppearanceOverlay.App.CornerSize50Percent" parent="">
<item name="cornerSize">50%</item>
</style>
<style name="ShapeAppearanceOverlay.MaterialComponents.NavigationView" parent="">
<item name="cornerSizeTopLeft">0dp</item>
<item name="cornerSizeBottomLeft">0dp</item>
<item name="cornerSize">50%</item>
</style>
</resources>
+10 -21
View File
@@ -24,44 +24,33 @@
<item name="colorControlActivated">@color/colorPrimary</item>
<item name="colorEdgeEffect">@color/grey_500</item>
<item name="dividerColor">@color/divider</item>
<item name="textColorThemeAccent">?android:attr/textColorSecondary</item>
<item name="colorControlNormal">@color/primary_drawable</item>
<item name="drawableColorSecondary">@color/secondary_drawable</item>
<item name="drawableColorThemePrimary">@color/primary_drawable</item>
<item name="contentColorReactive">@color/content_reactive</item>
<item name="guideBackgroundColor">#e5303030</item>
<item name="guideTitleColor">?android:attr/textColorPrimary</item>
<item name="guideTextColor">?android:attr/textColorSecondary</item>
<item name="tagGroupBackgroundColor">@color/tag_group_background</item>
<item name="tagBackgroundColor">@color/tag_background</item>
<item name="galleryDetailHeaderTitleColor">?android:attr/textColorPrimary</item>
<item name="galleryDetailHeaderBackgroundColor">?attr/contentColorThemePrimary</item>
<item name="drawableColorSecondary">@color/material_dynamic_secondary50</item>
<item name="drawableColorThemePrimary">@color/material_dynamic_primary50</item>
<item name="guideBackgroundColor">@color/material_dynamic_neutral80</item>
<item name="guideTitleColor">@color/material_dynamic_primary70</item>
<item name="guideTextColor">@color/material_dynamic_primary50</item>
<item name="tagGroupBackgroundColor">@color/material_dynamic_primary50</item>
<item name="tagBackgroundColor">@color/material_dynamic_neutral50</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<!-- =============== -->
<!-- Base Black Theme -->
<!-- =============== -->
<style name="ThemeOverlay" />
<style name="ThemeOverlay.Black">
<item name="colorPrimaryDark">@android:color/black</item>
<item name="android:colorBackground">@android:color/black</item>
<item name="toolbarColor">@android:color/black</item>
<item name="dividerColor">@color/divider_black</item>
<item name="colorControlNormal">@color/primary_drawable_black</item>
<item name="drawableColorSecondary">@color/secondary_drawable_black</item>
<item name="drawableColorThemePrimary">@color/primary_drawable_black</item>
<item name="contentColorReactive">@color/content_reactive_black</item>
<item name="guideBackgroundColor">#e5000000</item>
<item name="tagGroupBackgroundColor">@color/tag_group_background_black</item>
<item name="tagBackgroundColor">@color/tag_background_black</item>
<item name="galleryDetailHeaderBackgroundColor">@android:color/black</item>
</style>
<style name="AppTheme.Gallery" parent="AppTheme"></style>
<!-- ============ -->
<!-- Widget Theme -->
<!-- ============ -->
-31
View File
@@ -36,12 +36,6 @@
app:iconSpaceReserved="false"
app:url="https://github.com/asuka-mio/Ehviewer/releases" />
<!--<com.hippo.preference.UrlPreference
android:key="website"
android:title="@string/settings_about_website"
app:iconSpaceReserved="false"
app:url="http://www.ehviewer.com" />-->
<com.hippo.preference.UrlPreference
android:key="source"
android:title="@string/settings_about_source"
@@ -49,18 +43,6 @@
app:iconSpaceReserved="false"
app:url="https://github.com/asuka-mio/Ehviewer" />
<!--<com.hippo.preference.UrlPreference
android:key="changelog"
android:title="@string/settings_about_changelog"
app:iconSpaceReserved="false"
app:url="http://www.ehviewer.com/changelog" />
<com.hippo.preference.UrlPreference
android:key="help"
android:title="@string/settings_about_help"
app:iconSpaceReserved="false"
app:url="http://www.ehviewer.com/help" />-->
<com.hippo.ehviewer.preference.VersionPreference
android:key="version"
app:iconSpaceReserved="false" />
@@ -69,17 +51,4 @@
android:key="check_for_updates"
android:title="@string/settings_about_check_for_updates"
app:iconSpaceReserved="false" />
<!--<SwitchPreferenceCompat
android:defaultValue="@bool/beta"
android:key="beta_update_channel"
android:summary="@string/settings_about_beta_update_channel_summary"
android:title="@string/settings_about_beta_update_channel"
app:iconSpaceReserved="false" />
<Preference
android:key="donate"
android:title="@string/settings_about_donate"
app:iconSpaceReserved="false" />-->
</PreferenceScreen>
+4 -11
View File
@@ -17,14 +17,14 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="save_parse_error_body"
android:summary="@string/settings_advanced_save_parse_error_body_summary"
android:title="@string/settings_advanced_save_parse_error_body"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="save_crash_log"
android:summary="@string/settings_advanced_save_crash_log_summary"
@@ -72,7 +72,7 @@
android:title="@string/settings_advanced_proxy"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="built_in_hosts_2"
android:summary="@string/settings_advanced_built_in_hosts_summary"
@@ -86,20 +86,13 @@
android:title="@string/settings_advanced_custom_hosts_title"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="domain_fronting"
android:summary="@string/settings_advanced_domain_fronting_summary"
android:title="@string/settings_advanced_domain_fronting_title"
app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="false"
android:key="e_ink_mode_2"
android:summary="@string/settings_e_ink_mode_summary"
android:title="@string/settings_e_ink_mode_title"
app:iconSpaceReserved="false" />
<Preference
android:key="export_data"
android:summary="@string/settings_advanced_export_data_summary"
+2 -2
View File
@@ -22,7 +22,7 @@
android:title="@string/settings_download_download_location"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="media_scan"
android:title="@string/settings_download_media_scan"
@@ -66,7 +66,7 @@
app:entryValues="@array/image_resolution_entry_values"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="download_origin_image"
android:summary="@string/settings_download_download_origin_image_summary"
+7 -7
View File
@@ -61,7 +61,7 @@
android:title="@string/dark_theme"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:key="black_dark_theme"
android:title="@string/black_dark_theme"
app:iconSpaceReserved="false" />
@@ -111,27 +111,27 @@
app:entryValues="@array/thumb_resolution_entry_values"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="fix_thumb_url"
android:title="@string/settings_eh_fix_thumb_url"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="show_jpn_title"
android:summary="@string/settings_eh_show_jpn_title_summary"
android:title="@string/settings_eh_show_jpn_title"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="show_gallery_pages"
android:summary="@string/settings_eh_show_gallery_pages_summary"
android:title="@string/settings_eh_show_gallery_pages"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="show_tag_translations"
android:summary="@string/settings_eh_show_tag_translations_summary"
@@ -151,13 +151,13 @@
android:title="@string/settings_eh_filter"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="cellular_network_warning"
android:title="@string/settings_eh_metered_network_warning"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="request_news"
android:title="@string/settings_request_news_title"
+1 -1
View File
@@ -8,7 +8,7 @@
android:title="@string/settings_privacy_pattern_protection_title"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="enable_secure"
android:title="@string/settings_privacy_secure"
+9 -9
View File
@@ -62,56 +62,56 @@
app:entryValues="@array/read_theme_entry_values"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="keep_screen_on"
android:title="@string/settings_read_keep_screen_on"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="gallery_show_clock"
android:title="@string/settings_read_show_clock"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="gallery_show_progress"
android:title="@string/settings_read_show_progress"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="gallery_show_battery"
android:title="@string/settings_read_show_battery"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="gallery_show_page_interval"
android:title="@string/settings_read_show_page_interval"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="volume_page"
android:title="@string/settings_read_volume_page"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="volume_page"
android:key="reserve_volume_page"
android:title="@string/settings_read_reverse_volume"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="reading_fullscreen"
android:title="@string/settings_read_reading_fullscreen"
app:iconSpaceReserved="false" />
<SwitchPreference
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="custom_screen_lightness"
android:title="@string/settings_read_custom_screen_lightness"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 735 KiB

+6
View File
@@ -1,3 +1,9 @@
buildscript {
ext {
compose = '1.2.0-beta02'
}
}
plugins {
id 'com.android.application' version '7.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
+2 -2
View File
@@ -24,6 +24,6 @@ task executeDaoGenerator(type:JavaExec) {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'org.greenrobot:greendao-generator:3.3.0'
implementation 'org.jboss.forge.roaster:roaster-api:2.20.1.Final'
implementation 'org.jboss.forge.roaster:roaster-jdt:2.20.1.Final'
implementation 'org.jboss.forge.roaster:roaster-api:2.25.0.Final'
implementation 'org.jboss.forge.roaster:roaster-jdt:2.25.0.Final'
}
+7
View File
@@ -21,3 +21,10 @@ android.useAndroidX=true
android.enableJetifier=true
android.enableAppCompileTimeRClass=true
android.nonTransitiveRClass=true
# signing environment variable
signStoreFile=keystore/androidkey.jks
signStorePassword=000000
signkeyAlias=key0
signkeyPassword=000000
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+1 -6
View File
@@ -12,12 +12,6 @@ android {
targetSdkVersion 32
consumerProguardFiles 'proguard-rules.pro'
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
@@ -25,6 +19,7 @@ android {
externalNativeBuild {
cmake {
version "3.23.1"
path 'src/main/jni/CMakeLists.txt'
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
-keep class com.hippo.a7zip.* { *; }
-keep class com.hippo.* { *; }
-keep class com.hippo.image.* { *; }
-1
View File
@@ -1 +0,0 @@
<manifest />
@@ -5,4 +5,14 @@ public class Native {
public static void initialize() {
System.loadLibrary("ehviewer");
}
public static native String getlibarchiveVersion();
public static native String getliblzmaVersion();
public static native String getlibzstdVersion();
public static native String getlibjpeg_turboVersion();
public static native String getzlibVersion();
}
@@ -0,0 +1,105 @@
package com.hippo;
import android.content.Context;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
public class UriArchiveAccessor implements Closeable {
public static int OPEN_OK = 1;
public static int OPEN_ERR = 2;
private final OsReadableFile osf;
public UriArchiveAccessor(Context ctx, Uri uri) throws FileNotFoundException {
osf = new OsReadableFile(ctx, uri);
}
public int open() {
return openArchive(osf);
}
public void extractTargetIndexToOutputStream(int index, OutputStream os) {
extracttoOutputStream(osf, index, os);
}
private native int openArchive(OsReadableFile osf);
private native int extracttoOutputStream(OsReadableFile osf, int index, OutputStream os);
@Override
public void close() throws IOException {
osf.close();
}
static class OsReadableFile {
ParcelFileDescriptor pfd;
FileDescriptor fd;
public OsReadableFile(Context ctx, Uri uri) throws FileNotFoundException {
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
fd = pfd.getFileDescriptor();
}
public int read(byte[] b, int off, int len) throws IOException, ErrnoException {
return Os.read(fd, b, off, len);
}
public int read(byte[] b) throws IOException, ErrnoException {
return read(b, 0, b.length);
}
public long length() throws IOException, ErrnoException {
return Os.fstat(fd).st_size;
}
public long seek(long pos, int whence) throws IOException, ErrnoException {
try {
return Os.lseek(fd, pos, whence);
} catch (Exception e) {
e.printStackTrace();
return -30;
}
}
public void rewind() throws IOException, ErrnoException {
Os.lseek(fd, 0, OsConstants.SEEK_SET);
}
public long skip(long n) throws IOException, ErrnoException {
long pos;
long len;
long newpos;
if (n <= 0) {
return 0;
}
pos = Os.lseek(fd, 0L, OsConstants.SEEK_CUR);
len = length();
newpos = pos + n;
if (newpos > len) {
newpos = len;
}
seek(newpos, OsConstants.SEEK_SET);
/* return the actual number of bytes skipped */
return newpos - pos;
}
public void close() throws IOException {
pfd.close();
try {
Os.close(fd);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
}
}
@@ -1,28 +0,0 @@
/*
* Copyright 2018 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.a7zip;
public class ArchiveException extends Exception {
public ArchiveException(String message) {
super(message);
}
public ArchiveException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,76 +0,0 @@
/*
* Copyright 2020 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.a7zip;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class FileSeekableInputStream extends SeekableInputStream {
private final RandomAccessFile file;
public FileSeekableInputStream(String path) throws FileNotFoundException {
this(new File(path));
}
public FileSeekableInputStream(File file) throws FileNotFoundException {
this(new RandomAccessFile(file, "r"));
}
public FileSeekableInputStream(RandomAccessFile file) {
this.file = file;
}
@Override
public void seek(long pos) throws IOException {
file.seek(pos);
}
@Override
public long tell() throws IOException {
return file.getFilePointer();
}
@Override
public long size() throws IOException {
return file.length();
}
@Override
public int read() throws IOException {
return file.read();
}
@Override
public int read(@NonNull byte[] b) throws IOException {
return file.read(b);
}
@Override
public int read(@NonNull byte[] b, int off, int len) throws IOException {
return file.read(b, off, len);
}
@Override
public void close() throws IOException {
file.close();
}
}
@@ -1,81 +0,0 @@
/*
* Copyright 2020 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.a7zip;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class FileSeekableOutputStream extends SeekableOutputStream {
private final RandomAccessFile file;
public FileSeekableOutputStream(String path) throws FileNotFoundException {
this(new File(path));
}
public FileSeekableOutputStream(File file) throws FileNotFoundException {
this(new RandomAccessFile(file, "rw"));
}
public FileSeekableOutputStream(RandomAccessFile file) {
this.file = file;
}
@Override
public void seek(long pos) throws IOException {
file.seek(pos);
}
@Override
public long tell() throws IOException {
return file.getFilePointer();
}
@Override
public long size() throws IOException {
return file.length();
}
@Override
public void truncate(long size) throws IOException {
file.setLength(size);
}
@Override
public void write(int b) throws IOException {
file.write(b);
}
@Override
public void write(@NonNull byte[] b) throws IOException {
file.write(b);
}
@Override
public void write(@NonNull byte[] b, int off, int len) throws IOException {
file.write(b, off, len);
}
@Override
public void close() throws IOException {
file.close();
}
}
@@ -1,456 +0,0 @@
/*
* Copyright 2018 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.a7zip;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
public class InArchive implements Closeable {
private long nativePtr;
@Nullable
private final Charset charset;
@Nullable
private final String password;
private InArchive(long nativePtr, @Nullable Charset charset, @Nullable String password) {
this.nativePtr = nativePtr;
this.charset = charset;
this.password = password;
}
private void checkClosed() {
if (nativePtr == 0) {
throw new IllegalStateException("This InArchive is closed.");
}
}
// Sometimes p7zip returns string in the original charset, sometimes in utf-16.
// If it's in the original charset, each byte stores in each char,
// so every char in the string is smaller than 0xFF.
// But if every char in the string is smaller than 0xFF,
// it's hard to tell the string is in utf-16 or the original charset.
// TODO Let p7zip tell whether it have encoded the string.
private static String applyCharsetToString(String str, Charset charset) {
if (str == null || charset == null) {
return str;
}
int length = str.length();
for (int i = 0; i < length; i++) {
char c = str.charAt(i);
if (c > 0xFF) {
// It's not a pure byte list, can't apply charset
return str;
}
}
byte[] bytes = new byte[length];
for (int i = 0; i < length; i++) {
bytes[i] = (byte) str.charAt(i);
}
return new String(bytes, charset);
}
private static String applyCharsetToPassword(String str, Charset charset) {
if (str == null || charset == null) {
return str;
}
byte[] bytes = str.getBytes(charset);
char[] chars = new char[bytes.length];
for (int i = 0; i < chars.length; i++) {
chars[i] = (char) (bytes[i] & 0xFF);
}
return new String(chars);
}
/**
* Returns the format name of this archive.
* Empty string if can't get the name.
*/
@NonNull
public String getFormatName() {
checkClosed();
return nativeGetFormatName(nativePtr);
}
/**
* Returns the number of entries in this archive.
* {@code -1} if get error.
*/
public int getNumberOfEntries() {
checkClosed();
return nativeGetNumberOfEntries(nativePtr);
}
/**
* Returns the type of the property for the archive.
*
* @param propID the id of the property
* @return one of {@link PropType}
*/
public PropType getArchivePropertyType(PropID propID) {
int type = nativeGetArchivePropertyType(nativePtr, propID.ordinal());
if (type >= 0 && type < PropType.values().length) {
return PropType.values()[type];
} else {
return PropType.UNKNOWN;
}
}
/**
* Returns boolean property for the archive.
*
* @param propID the id of the property
* @return the boolean property, {@code false} if get error
*/
public boolean getArchiveBooleanProperty(PropID propID) {
checkClosed();
return nativeGetArchiveBooleanProperty(nativePtr, propID.ordinal());
}
/**
* Returns int property for the archive.
*
* @param propID the id of the property
* @return the int property, {@code 0} if get error
*/
public int getArchiveIntProperty(PropID propID) {
checkClosed();
return nativeGetArchiveIntProperty(nativePtr, propID.ordinal());
}
/**
* Returns long property for the archive.
*
* @param propID the id of the property
* @return the long property, {@code 0} if get error
*/
public long getArchiveLongProperty(PropID propID) {
checkClosed();
return nativeGetArchiveLongProperty(nativePtr, propID.ordinal());
}
/**
* Returns string property for the archive.
*
* @param propID the id of the property
* @return the string property, empty string if get error
*/
@NonNull
public String getArchiveStringProperty(PropID propID) {
return getArchiveStringProperty(propID, charset);
}
/**
* Returns string property for the archive.
*
* @param propID the id of the property
* @param charset the charset of the string, {@code null} to let p7zip handle it
* @return the string property, empty string if get error
*/
@NonNull
public String getArchiveStringProperty(PropID propID, @Nullable Charset charset) {
checkClosed();
String str = nativeGetArchiveStringProperty(nativePtr, propID.ordinal());
str = applyCharsetToString(str, charset);
return str != null ? str : "";
}
/**
* Returns the type of the property for the archive.
*
* @param propID the id of the property
* @return one of {@link PropType}
*/
public PropType getEntryPropertyType(int index, PropID propID) {
int type = nativeGetEntryPropertyType(nativePtr, index, propID.ordinal());
if (type >= 0 && type < PropType.values().length) {
return PropType.values()[type];
} else {
return PropType.UNKNOWN;
}
}
/**
* Returns boolean property for the entry.
*
* @param index the index of the entry
* @param propID the id of the property
* @return the boolean property, {@code false} if get error
*/
public boolean getEntryBooleanProperty(int index, PropID propID) {
checkClosed();
return nativeGetEntryBooleanProperty(nativePtr, index, propID.ordinal());
}
/**
* Returns int property for the entry.
*
* @param index the index of the entry
* @param propID the id of the property
* @return the int property, {@code 0} if get error
*/
public int getEntryIntProperty(int index, PropID propID) {
checkClosed();
return nativeGetEntryIntProperty(nativePtr, index, propID.ordinal());
}
/**
* Returns long property for the entry.
*
* @param index the index of the entry
* @param propID the id of the property
* @return the long property, {@code 0} if get error
*/
public long getEntryLongProperty(int index, PropID propID) {
checkClosed();
return nativeGetEntryLongProperty(nativePtr, index, propID.ordinal());
}
/**
* Returns string property for the entry.
*
* @param index the index of the entry
* @param propID the id of the property
* @return the string property, empty string if get error
*/
@NonNull
public String getEntryStringProperty(int index, PropID propID) {
return getEntryStringProperty(index, propID, charset);
}
/**
* Returns string property for the entry.
*
* @param index the index of the entry
* @param propID the id of the property
* @param charset the charset of the string, {@code null} to let p7zip handle it
* @return the string property, empty string if get error
*/
@NonNull
public String getEntryStringProperty(int index, PropID propID, @Nullable Charset charset) {
checkClosed();
String str = nativeGetEntryStringProperty(nativePtr, index, propID.ordinal());
str = applyCharsetToString(str, charset);
return str != null ? str : "";
}
/**
* Returns the path of the entry.
*
* @param index the index of the entry
* @return the path, empty string if get error
*/
@NonNull
public String getEntryPath(int index) {
return getEntryPath(index, charset);
}
/**
* Returns the path of the entry.
*
* @param index the index of the entry
* @param charset the charset of the string, {@code null} to let p7zip handle it
* @return the path, empty string if get error
*/
@NonNull
public String getEntryPath(int index, @Nullable Charset charset) {
return getEntryStringProperty(index, PropID.PATH, charset);
}
/**
* Returns the stream of the entry. Only a few archive formats support it.
*
* @param index the index of the entry
* @return the stream of the entry
* @throws ArchiveException if the archive format doesn't support the operation or get error
* @see #extractEntry(int, OutputStream)
* @see #extractEntry(int, String, OutputStream)
*/
@NonNull
public InputStream getEntryStream(int index) throws ArchiveException {
return nativeGetEntryStream(nativePtr, index);
}
/**
* Extracts the context of the entry into the output stream.
*
* @param index the index of the entry
* @param os the output steam to receive the content,
* it will be closed at the end of this method
* @throws ArchiveException if get error
* @see #getEntryStream(int)
*/
public void extractEntry(int index, @NonNull OutputStream os) throws ArchiveException {
extractEntry(index, password, os);
}
/**
* Extracts the context of the entry into the output stream.
*
* @param index the index of the entry
* @param password the password of the entry
* @param os the output steam to receive the content,
* it will be closed at the end of this method
* @throws ArchiveException if get error
*/
@SuppressWarnings("ThrowFromFinallyBlock")
public void extractEntry(int index, String password, @NonNull OutputStream os) throws ArchiveException {
try {
checkClosed();
nativeExtractEntry(nativePtr, index, password, os);
} finally {
try {
os.close();
} catch (IOException e) {
throw new ArchiveException("Catch IOException while closing the OutputStream", e);
}
}
}
@Override
public void close() {
if (nativePtr != 0) {
nativeClose(nativePtr);
nativePtr = 0;
}
}
/**
* Returns {@code true} if the archive is closed.
*/
public boolean isClosed() {
return nativePtr == 0;
}
@NonNull
public static InArchive open(File file) throws ArchiveException {
try {
return open(new FileSeekableInputStream(file), null, null, file.getName(), new OpenVolumeInDirCallback(file.getParentFile()));
} catch (FileNotFoundException e) {
throw new ArchiveException("Can't open the archive: " + file.getPath(), e);
}
}
@NonNull
public static InArchive open(SeekableInputStream stream) throws ArchiveException {
return open(stream, null, null, null, null);
}
/**
* Opens an archive to read from the specified stream.
* <p>
* {@code charset} is for password and string property.
* The charset of string property can reset in
* {@link InArchive#getArchiveStringProperty(PropID, Charset)}.
*/
@NonNull
public static InArchive open(
SeekableInputStream stream,
@Nullable Charset charset,
@Nullable String password,
@Nullable String filename,
@Nullable OpenVolumeCallback openVolumeCallback
) throws ArchiveException {
password = applyCharsetToPassword(password, charset);
long nativePtr = nativeOpen(stream, password, filename, openVolumeCallback);
if (nativePtr == 0) {
// It should not be 0
throw new ArchiveException("a7zip is buggy");
}
return new InArchive(nativePtr, charset, password);
}
@Keep
public interface OpenVolumeCallback {
@NonNull
SeekableInputStream openVolume(String filename) throws ArchiveException;
}
public static class OpenVolumeInDirCallback implements OpenVolumeCallback {
private final File dir;
public OpenVolumeInDirCallback(File dir) {
this.dir = dir;
}
@NonNull
@Override
public SeekableInputStream openVolume(String filename) throws ArchiveException {
try {
return new FileSeekableInputStream(new File(dir, filename));
} catch (FileNotFoundException e) {
throw new ArchiveException("Can't find the file<" + filename + "> in dir<" + dir.getPath() + ">", e);
}
}
}
private static native long nativeOpen(
SeekableInputStream stream,
String password,
String filename,
OpenVolumeCallback openVolumeCallback
) throws ArchiveException;
private static native String nativeGetFormatName(long nativePtr);
private static native int nativeGetNumberOfEntries(long nativePtr);
private static native int nativeGetArchivePropertyType(long nativePtr, int propID);
private static native boolean nativeGetArchiveBooleanProperty(long nativePtr, int propID);
private static native int nativeGetArchiveIntProperty(long nativePtr, int propID);
private static native long nativeGetArchiveLongProperty(long nativePtr, int propID);
@Nullable
private static native String nativeGetArchiveStringProperty(long nativePtr, int propID);
private static native int nativeGetEntryPropertyType(long nativePtr, int index, int propID);
private static native boolean nativeGetEntryBooleanProperty(long nativePtr, int index, int propID);
private static native int nativeGetEntryIntProperty(long nativePtr, int index, int propID);
private static native long nativeGetEntryLongProperty(long nativePtr, int index, int propID);
@Nullable
private static native String nativeGetEntryStringProperty(long nativePtr, int index, int propID);
@NonNull
private static native InputStream nativeGetEntryStream(long nativePtr, int index) throws ArchiveException;
private static native void nativeExtractEntry(long nativePtr, int index, String password, OutputStream os) throws ArchiveException;
private static native void nativeClose(long nativePtr);
}
@@ -1,66 +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.a7zip;
import androidx.annotation.NonNull;
import java.io.IOException;
import java.io.InputStream;
class NativeInputStream extends InputStream {
private long nativePtr;
private final byte[] scratch = new byte[8];
private NativeInputStream(long nativePtr) {
this.nativePtr = nativePtr;
}
private void checkClosed() {
if (nativePtr == 0) {
throw new IllegalStateException("This JavaInputStream is closed.");
}
}
@Override
public int read() throws IOException {
return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
}
@Override
public int read(@NonNull byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
checkClosed();
return nativeRead(nativePtr, b, off, len);
}
@Override
public void close() throws IOException {
if (nativePtr != 0) {
nativeClose(nativePtr);
nativePtr = 0;
}
}
private static native int nativeRead(long nativePtr, byte[] b, int off, int len) throws IOException;
private static native void nativeClose(long nativePtr) throws IOException;
}
@@ -1,89 +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.a7zip;
import androidx.annotation.NonNull;
import java.io.IOException;
class NativeSeekableInputStream extends SeekableInputStream {
private long nativePtr;
private final byte[] scratch = new byte[8];
private NativeSeekableInputStream(long nativePtr) {
this.nativePtr = nativePtr;
}
private void checkClosed() {
if (nativePtr == 0) {
throw new IllegalStateException("This JavaSeekableInputStream is closed.");
}
}
@Override
public void seek(long pos) throws IOException {
checkClosed();
nativeSeek(nativePtr, pos);
}
@Override
public long tell() throws IOException {
checkClosed();
return nativeTell(nativePtr);
}
@Override
public long size() throws IOException {
checkClosed();
return nativeSize(nativePtr);
}
@Override
public int read() throws IOException {
return (read(scratch, 0, 1) != -1) ? scratch[0] & 0xff : -1;
}
@Override
public int read(@NonNull byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
checkClosed();
return nativeRead(nativePtr, b, off, len);
}
@Override
public void close() throws IOException {
if (nativePtr != 0) {
nativeClose(nativePtr);
nativePtr = 0;
}
}
private static native void nativeSeek(long nativePtr, long pos) throws IOException;
private static native long nativeTell(long nativePtr) throws IOException;
private static native long nativeSize(long nativePtr) throws IOException;
private static native int nativeRead(long nativePtr, byte[] b, int off, int len) throws IOException;
private static native void nativeClose(long nativePtr) throws IOException;
}
@@ -1,31 +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.a7zip;
import androidx.annotation.Keep;
@Keep
public class PasswordException extends ArchiveException {
public PasswordException(String message) {
super(message);
}
public PasswordException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -1,116 +0,0 @@
/*
* Copyright 2018 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.a7zip;
public enum PropID {
NO_PROPERTY,
MAIN_SUBFILE,
HANDLER_ITEM_INDEX,
PATH,
NAME,
EXTENSION,
IS_DIR,
SIZE,
PACK_SIZE,
ATTRIB,
C_TIME,
A_TIME,
M_TIME,
SOLID,
COMMENTED,
ENCRYPTED,
SPLIT_BEFORE,
SPLIT_AFTER,
DICTIONARY_SIZE,
CRC,
TYPE,
IS_ANTI,
METHOD,
HOST_OS,
FILE_SYSTEM,
USER,
GROUP,
BLOCK,
COMMENT,
POSITION,
PREFIX,
NUM_SUB_DIRS,
NUM_SUB_FILES,
UNPACK_VER,
VOLUME,
IS_VOLUME,
OFFSET,
LINKS,
NUM_BLOCKS,
NUM_VOLUMES,
TIME_TYPE,
BIT64,
BIG_ENDIAN,
CPU,
PHY_SIZE,
HEADERS_SIZE,
CHECKSUM,
CHARACTS,
VA,
ID,
SHORT_NAME,
CREATOR_APP,
SECTOR_SIZE,
POSIX_ATTRIB,
SYM_LINK,
ERROR,
TOTAL_SIZE,
FREE_SPACE,
CLUSTER_SIZE,
VOLUME_NAME,
LOCAL_NAME,
PROVIDER,
NT_SECURE,
IS_ALT_STREAM,
IS_AUX,
IS_DELETED,
IS_TREE,
SHA1,
SHA256,
ERROR_TYPE,
NUM_ERRORS,
ERROR_FLAGS,
WARNING_FLAGS,
WARNING,
NUM_STREAMS,
NUM_ALT_STREAMS,
ALT_STREAMS_SIZE,
VIRTUAL_SIZE,
UNPACK_SIZE,
TOTAL_PHY_SIZE,
VOLUME_INDEX,
SUB_TYPE,
SHORT_COMMENT,
CODE_PAGE,
IS_NOT_ARC_TYPE,
PHY_SIZE_CANT_BE_DETECTED,
ZEROS_TAIL_IS_ALLOWED,
TAIL_SIZE,
EMBEDDED_STUB_SIZE,
NT_REPARSE,
HARD_LINK,
INODE,
STREAM_ID,
READ_ONLY,
OUT_NAME,
COPY_LINK,
}
@@ -1,28 +0,0 @@
/*
* Copyright 2018 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.a7zip;
public enum PropType {
UNKNOWN,
EMPTY,
BOOL,
INT,
LONG,
FLOAT,
DOUBLE,
STRING,
}
@@ -1,40 +0,0 @@
/*
* Copyright 2020 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.a7zip;
import java.io.IOException;
import java.io.InputStream;
public abstract class SeekableInputStream extends InputStream {
/**
* Sets the position, measured from the beginning,
* at which the next read occurs. The offset may be
* set beyond the end of the file.
*/
public abstract void seek(long pos) throws IOException;
/**
* Returns current position, measured from the beginning.
*/
public abstract long tell() throws IOException;
/**
* Returns the size.
*/
public abstract long size() throws IOException;
}
@@ -1,45 +0,0 @@
/*
* Copyright 2020 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.a7zip;
import java.io.IOException;
import java.io.OutputStream;
public abstract class SeekableOutputStream extends OutputStream {
/**
* Sets the position, measured from the beginning,
* at which the next write occurs. The offset may be
* set beyond the end of the file.
*/
public abstract void seek(long pos) throws IOException;
/**
* Returns current position, measured from the beginning.
*/
public abstract long tell() throws IOException;
/**
* Returns the size.
*/
public abstract long size() throws IOException;
/**
* Sets the size.
*/
public abstract void truncate(long size) throws IOException;
}
+78 -80
View File
@@ -55,12 +55,10 @@ public final class Image {
public static final int FORMAT_GIF = 3;
private static final AtomicInteger sImageCount = new AtomicInteger();
private long mNativePtr;
private final int mFormat;
private final int mWidth;
private final int mHeight;
private long mNativePtr;
private Throwable mRecycleTracker;
private Image(long nativePtr, int format, int width, int height) {
@@ -72,6 +70,83 @@ public final class Image {
sImageCount.getAndIncrement();
}
/**
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decode(InputStream is, boolean partially) {
if (!(is instanceof BufferedInputStream)) {
is = new BufferedInputStream(is);
}
return nativeDecode(is, partially);
}
/**
* Create a plain image from Bitmap
*/
@Nullable
public static Image create(Bitmap bitmap) {
return nativeCreate(bitmap);
}
/**
* Return all un-recycled {@code Image} instance count.
* It is useful for debug.
*/
public static int getImageCount() {
return sImageCount.get();
}
/**
* Return all supported image formats, exclude {@link #FORMAT_PLAIN}
*/
public static int[] getSupportedImageFormats() {
return nativeGetSupportedImageFormats();
}
/**
* Return decoder description of the image format,
* {@code null} for invalid image format.
*/
public static String getDecoderDescription(int format) {
return nativeGetDecoderDescription(format);
}
private static native Image nativeDecode(InputStream is, boolean partially);
private static native Image nativeCreate(Bitmap bitmap);
private static native int nativeGetByteCount(long nativePtr, int format);
private static native boolean nativeComplete(long nativePtr, int format);
private static native boolean nativeIsCompleted(long nativePtr, int format);
private static native void nativeRender(long nativePtr, int format,
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
int width, int height, boolean fillBlank, int defaultColor);
private static native void nativeTexImage(long nativePtr, int format,
boolean init, int offsetX, int offsetY, int width, int height);
private static native void nativeAdvance(long nativePtr, int format);
private static native int nativeGetDelay(long nativePtr, int format);
private static native int nativeFrameCount(long nativePtr, int format);
private static native boolean nativeIsOpaque(long nativePtr, int format);
private static native boolean nativeIsGray(long nativePtr, int format, int error);
private static native void nativeClahe(long nativePtr, int format, boolean toGray);
private static native void nativeRecycle(long nativePtr, int format);
private static native int[] nativeGetSupportedImageFormats();
private static native String nativeGetDecoderDescription(int format);
/**
* Return the format of the image
*/
@@ -218,81 +293,4 @@ public final class Image {
public boolean isRecycled() {
return mNativePtr == 0;
}
/**
* Decode image from {@code InputStream}
*/
@Nullable
public static Image decode(InputStream is, boolean partially) {
if (!(is instanceof BufferedInputStream)) {
is = new BufferedInputStream(is);
}
return nativeDecode(is, partially);
}
/**
* Create a plain image from Bitmap
*/
@Nullable
public static Image create(Bitmap bitmap) {
return nativeCreate(bitmap);
}
/**
* Return all un-recycled {@code Image} instance count.
* It is useful for debug.
*/
public static int getImageCount() {
return sImageCount.get();
}
/**
* Return all supported image formats, exclude {@link #FORMAT_PLAIN}
*/
public static int[] getSupportedImageFormats() {
return nativeGetSupportedImageFormats();
}
/**
* Return decoder description of the image format,
* {@code null} for invalid image format.
*/
public static String getDecoderDescription(int format) {
return nativeGetDecoderDescription(format);
}
private static native Image nativeDecode(InputStream is, boolean partially);
private static native Image nativeCreate(Bitmap bitmap);
private static native int nativeGetByteCount(long nativePtr, int format);
private static native boolean nativeComplete(long nativePtr, int format);
private static native boolean nativeIsCompleted(long nativePtr, int format);
private static native void nativeRender(long nativePtr, int format,
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
int width, int height, boolean fillBlank, int defaultColor);
private static native void nativeTexImage(long nativePtr, int format,
boolean init, int offsetX, int offsetY, int width, int height);
private static native void nativeAdvance(long nativePtr, int format);
private static native int nativeGetDelay(long nativePtr, int format);
private static native int nativeFrameCount(long nativePtr, int format);
private static native boolean nativeIsOpaque(long nativePtr, int format);
private static native boolean nativeIsGray(long nativePtr, int format, int error);
private static native void nativeClahe(long nativePtr, int format, boolean toGray);
private static native void nativeRecycle(long nativePtr, int format);
private static native int[] nativeGetSupportedImageFormats();
private static native String nativeGetDecoderDescription(int format);
}
@@ -40,19 +40,18 @@ import java.util.Set;
public final class ImageBitmap implements Animatable, Runnable {
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
@Nullable
private Image mImage;
@NonNull
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;
private int mReferences;
private int mAnimationReferences;
private boolean mRunning;
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
private ImageBitmap(@NonNull Image image) throws OutOfMemoryError {
int width = image.getWidth();
@@ -31,9 +31,9 @@ public class ImageDrawable extends Drawable implements Animatable, ImageBitmap.C
private final ImageBitmap mImageBitmap;
private final Paint mPaint;
public ImageDrawable(ImageBitmap imageBitmap) throws RecycledException {
public ImageDrawable(ImageBitmap imageBitmap) throws Exception {
if (!imageBitmap.obtain()) {
throw new RecycledException();
throw new Exception();
}
mImageBitmap = imageBitmap;
@@ -1,23 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.image;
/**
* Image is recycled.
*/
public class RecycledException extends Exception {
}
+31 -11
View File
@@ -1,17 +1,37 @@
project(ehviewer)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Oz -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Oz -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-rtti -fno-stack-protector")
# Build liblzma as shared lib
set(BUILD_SHARED_LIBS ON)
add_subdirectory(liblzma)
add_subdirectory(a7zip)
# Build libzstd
add_subdirectory(libzstd/build/cmake)
# Configure libarchive for android platform
include_directories(libarchive/contrib/android/include)
# 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 zstd support for libarchive
include_directories(libzstd/lib)
SET(HAVE_LIBZSTD 1)
SET(HAVE_LIBZSTD_COMPRESSOR 1)
SET(HAVE_ZSTD_H 1)
# Build libarchive
add_subdirectory(libarchive)
# Build hippo's image lib
add_subdirectory(image)
add_subdirectory(integrity)
add_library(${PROJECT_NAME} SHARED main.c)
target_compile_options(${PROJECT_NAME} PUBLIC -Oz -fvisibility=hidden -fvisibility-inlines-hidden -fno-exceptions)
target_include_directories(${PROJECT_NAME} PUBLIC a7zip/a7zip image/image integrity)
target_link_libraries(${PROJECT_NAME} a7zip image genuine)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED main.c archive.c)
target_include_directories(${PROJECT_NAME} PUBLIC image/image libarchive/libarchive)
target_link_libraries(${PROJECT_NAME} image archive)
-24
View File
@@ -1,24 +0,0 @@
cmake_minimum_required(VERSION 3.4.1)
project(a7zip C CXX)
add_subdirectory(p7zip)
set(A_SEVEN_ZIP_SOURCES
a7zip/BlackHole.cpp
a7zip/InArchive.cpp
a7zip/JavaEnv.cpp
a7zip/JavaHelper.cpp
a7zip/JavaInArchive.cpp
a7zip/JavaInitA7Zip.cpp
a7zip/JavaInputStream.cpp
a7zip/JavaSeekableInputStream.cpp
a7zip/OpenVolumeCallback.cpp
a7zip/OutputStream.cpp
a7zip/SeekableInputStream.cpp
a7zip/SevenZip.cpp
)
add_library(a7zip STATIC ${A_SEVEN_ZIP_SOURCES})
target_link_libraries(a7zip PUBLIC p7zip log)
@@ -1,69 +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.
*/
#include "BlackHole.h"
using namespace a7zip;
BlackHole::BlackHole(): pos(0), size(0) { }
HRESULT BlackHole::Write(const void*, UInt32 size, UInt32* processedSize) {
if (processedSize != nullptr) {
*processedSize = size;
}
this->pos += size;
if (this->size < this->pos) {
this->size = this->pos;
}
return S_OK;
}
HRESULT BlackHole::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) {
Int64 newPos = -1;
switch (seekOrigin) {
case STREAM_SEEK_SET:
newPos = offset;
break;
case STREAM_SEEK_CUR:
newPos = this->pos + offset;
break;
case STREAM_SEEK_END:
newPos = this->size + offset;
break;
default:
return E_INVALIDARG;
}
if (newPos < 0) {
return STG_E_INVALIDFUNCTION;
}
*newPosition = this->pos;
return S_OK;
}
HRESULT BlackHole::SetSize(UInt64 newSize) {
this->size = newSize;
if (this->pos > this->size) {
this->pos = this->size;
}
return S_OK;
}
@@ -1,47 +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.
*/
#ifndef __A7ZIP_BLACK_HOLE_H__
#define __A7ZIP_BLACK_HOLE_H__
#include <Common/MyCom.h>
#include <7zip/IStream.h>
#include "Log.h"
namespace a7zip {
class BlackHole :
public IOutStream,
public CMyUnknownImp
{
public:
BlackHole();
public:
MY_UNKNOWN_IMP2(ISequentialOutStream, IOutStream)
STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
STDMETHOD(SetSize)(UInt64 newSize);
private:
UInt64 pos;
UInt64 size;
};
}
#endif //__A7ZIP_BLACK_HOLE_H__
Loaded 100 of 159 files, more files were not shown because too many files have changed in this diff. Show more