Compare commits

..
Author SHA1 Message Date
tarsin 34ee9d39ad Release 1.7.28.3
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-19 19:41:19 +08:00
107 changed files with 2400 additions and 3068 deletions

No files matched your search

+4 -26
View File
@@ -1,17 +1,8 @@
name: Bug 反馈 / Bug report
description: 提交一个问题报告 / Create a bug report
description: 提交一个问题报告 / Create a bug report to help us improve
labels:
- "bug"
body:
- type: markdown
attributes:
value: |
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
提交问题报告前,还请首先完成文末的自查步骤
Thanks for your contribution on Ehviewer-Overhauled Project!
Please finish verify steps which list in the end first before create bug report
- type: textarea
id: reproduce
attributes:
@@ -53,25 +44,12 @@ body:
validations:
required: true
- type: textarea
id: more
attributes:
label: 备注 / Addition details
description: |
在此处写下其他您想说的内容
Describe addition details here
placeholder: |
其他有用的信息与附件
Additional details and attachments
validations:
required: false
- type: input
id: version
attributes:
label: EhViewer 版本号 / EhViewer version code
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
placeholder: 1.6.9.13
placeholder: 四位版本号 / 4 digits version code
validations:
required: true
@@ -79,7 +57,7 @@ body:
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 GitHub Action CI 下载页面网址填入即可 / Simply drop GitHub Action CI download page url here
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
validations:
required: true
@@ -141,4 +119,4 @@ body:
required: true
- label: 您确保已使用最新 CI 版本测试,并且该问题在最新 CI 版本中并未解决 / This bug have not solved in latest CI version
required: true
required: true
-1
View File
@@ -1,4 +1,3 @@
blank_issues_enabled: false
contact_links:
- name: 所有其他问题 / All other questions
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
+5 -14
View File
@@ -1,17 +1,8 @@
name: 功能请求 / Feature request
description: 提出一个功能建议 / Suggest an idea
description: 提出一个功能建议 / Suggest an idea for this project
labels:
- "enhancement"
body:
- type: markdown
attributes:
value: |
感谢您愿意为 Ehviewer-Overhauled 做出贡献!
提交功能建议前,还请首先完成文末的自查步骤
Thanks for your contribution on Ehviewer-Overhauled Project!
Please finish verify steps which list in the end first before suggest an idea
- type: textarea
id: request
attributes:
@@ -42,7 +33,7 @@ body:
- type: textarea
id: more
attributes:
label: 备注 / Addition details
label: 其他细节 / Addition details
description: |
在此处写下其他您想说的内容
Describe addition details here
@@ -57,7 +48,7 @@ body:
attributes:
label: EhViewer 版本号 / EhViewer version code
description: 您可以在设置 - 关于处找到版本号 / You can get version code in Settings - About
placeholder: 1.6.9.13
placeholder: 四位版本号 / 4 digits version code
validations:
required: true
@@ -65,7 +56,7 @@ body:
id: ci
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 GitHub Action CI 下载页面网址填入即可 / Simply drop GitHub Action CI download page url here
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
validations:
required: true
@@ -91,4 +82,4 @@ body:
required: true
- label: 您确保已使用最新 CI 版本测试,并且该功能在最新 CI 版本中并未按照预期实现 / This feature don't implement correctly in latest CI version
required: true
required: true
@@ -1,11 +1,6 @@
name: CI
name: EhViewer
on:
push:
branches:
- '*'
tags-ignore:
- "*"
on: [push, pull_request]
jobs:
linux:
@@ -14,8 +9,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -23,20 +16,23 @@ jobs:
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
with:
gradle-home-cache-cleanup: true
arguments: assembleRelease
- run: |
git submodule update --init --recursive
gradle assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
with:
name: arm64-v8a-${{ github.sha }}
name: arm64-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Update x86_64
-52
View File
@@ -1,52 +0,0 @@
name: Pull Request Check
on: [pull_request]
jobs:
linux:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
with:
arguments: assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Update x86_64
uses: actions/upload-artifact@v3
with:
name: x86_64-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86_64-release.apk
- name: Update ARM32
uses: actions/upload-artifact@v3
with:
name: armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
- name: Update x86
uses: actions/upload-artifact@v3
with:
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
+11 -19
View File
@@ -12,8 +12,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Java 11
uses: actions/setup-java@v3
@@ -21,31 +19,25 @@ jobs:
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
with:
arguments: assembleRelease
- name: Rename Apks
run: |
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal-A11-.apk
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a-A11-.apk
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64-A11-.apk
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86-A11-.apk
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a-A11-.apk
- run: |
git submodule update --init --recursive
gradle assembleRelease
- name: Releases
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v0.1.14
with:
body: Bump Version
discussion_category_name: Announcements
files: |
app/build/outputs/apk/release/EhViewer-universal-A11-.apk
app/build/outputs/apk/release/EhViewer-arm64-v8a-A11-.apk
app/build/outputs/apk/release/EhViewer-x86_64-A11-.apk
app/build/outputs/apk/release/EhViewer-x86-A11-.apk
app/build/outputs/apk/release/EhViewer-armeabi-v7a-A11-.apk
app/build/outputs/apk/release/app-arm64-v8a-release.apk
app/build/outputs/apk/release/app-x86_64-release.apk
app/build/outputs/apk/release/app-x86-release.apk
app/build/outputs/apk/release/app-armeabi-v7a-release.apk
+1 -4
View File
@@ -12,7 +12,4 @@
url = https://github.com/Ehviewer-Overhauled/libarchive
[submodule "native/src/main/jni/liblzma"]
path = native/src/main/jni/liblzma
url = https://github.com/Ehviewer-Overhauled/xz
[submodule "native/src/main/jni/nettle/nettle"]
path = native/src/main/jni/nettle/nettle
url = https://github.com/Ehviewer-Overhauled/nettle
url = https://git.tukaani.org/xz.git
+25 -54
View File
@@ -1,25 +1,13 @@
<p align="right">
<strong>English<strong>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md">
简体中文
</a>
<span> | <span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md">
正體中文
</a>
</p>
<h1 align="center">
<img src="art/launcher_icon-web.svg" width="200">
<img src="art/launcher_icon-web.png" width="200">
<br>EhViewer<br>
</h1>
<p align="center">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions">
<img src="https://img.shields.io/github/workflow/status/Ehviewer-Overhauled/Ehviewer/EhViewer" alt="Github Actions">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
@@ -33,57 +21,37 @@
</a>
</p>
<div align="center">
<h3>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#description">
Description
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#download">
Download
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#screenshot">
Screenshot
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#thanks">
Thanks
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/tree/1.7.x.x#license">
License
</a>
</h3>
</div>
## Description
这是一个 E-Hentai Android 平台的浏览器
已针对性适配 Material Design 3 及其动态取色系统
动态配色仅供 Android 12 及以上平台使用!
# Description
An E-Hentai Application for Android.
EhViewer fork dedicated to lightweight and high-performance
with Material Design 3 and Dynamic Color Support.
with Material Design 3 and Dynamic Color Support
Dynamic Color only support Android S and above!
# Download
## Download
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="art/get-it-on-github.svg" width="200px"/></a>
[Github 下载](https://github.com/Ehviewer-Overhauled/Ehviewer/releases)
App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 and above, also the develop mainline. Version 1.7 support Android 11 and below, drop down some spicetify functions for Android 12 to make app useable on lower Android System
# Screenshot
## Screenshot
![screenshot-01](art/screenshot-01.png)
![screenshot-02](art/screenshot-02.png)
# Thanks
## Thanks
本项目受到了诸多开源项目的帮助
Here is the libraries
- [AOSP & AndroidX](http://source.android.com/)
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
- [GnuTLS](https://gnutls.org/)
- [material-components-android](https://github.com/material-components/material-components-android)
- [material-design-icons](https://github.com/google/material-design-icons)
- [AOSP](http://source.android.com/)
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
- [apng](http://apng.sourceforge.net/)
- [giflib](http://giflib.sourceforge.net)
@@ -93,11 +61,12 @@ Here is the libraries
- [libpng](http://www.libpng.org/pub/png/libpng.html)
- [okhttp](https://github.com/square/okhttp)
- [roaster](https://github.com/forge/roaster)
- [TapTargetView](https://github.com/KeepSafe/TapTargetView)
- [libarchive](http://www.libarchive.org/)
- [XZ Utils](https://tukaani.org/xz/)
# License
## License
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
@@ -108,3 +77,5 @@ Here is the libraries
EhViewer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with EhViewer. If not, see <https://www.gnu.org/licenses/>.
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fasuka-mio%2FEhviewer.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fasuka-mio%2FEhviewer?ref=badge_large)
+21 -25
View File
@@ -18,7 +18,6 @@ android {
enable = true
reset()
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
universalApk = true
}
}
@@ -35,8 +34,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 26
targetSdkVersion 33
versionCode 114537
versionName "1.7.31.0-alpha02"
versionCode 114530
versionName "1.7.28.3"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -93,42 +92,42 @@ tasks.withType(JavaCompile) {
}
dependencies {
def activity_version = "1.6.1"
def activity_version = "1.6.0-alpha05"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.annotation:annotation-experimental:1.3.0"
implementation "androidx.appcompat:appcompat:1.6.0-rc01"
implementation "androidx.annotation:annotation:1.4.0"
implementation "androidx.annotation:annotation-experimental:1.2.0"
implementation "androidx.appcompat:appcompat:1.6.0-alpha05"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
implementation "androidx.arch.core:core-runtime:$arch_core_version"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
implementation "androidx.browser:browser:1.4.0"
def collection_version = "1.3.0-alpha02"
def collection_version = "1.3.0-alpha01"
implementation "androidx.collection:collection:$collection_version"
implementation "androidx.collection:collection-ktx:$collection_version"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
def core_version = "1.9.0"
def core_version = "1.9.0-alpha05"
implementation "androidx.core:core:$core_version"
implementation "androidx.core:core-ktx:$core_version"
implementation "androidx.customview:customview:1.2.0-alpha02"
implementation "androidx.customview:customview:1.2.0-alpha01"
implementation "androidx.documentfile:documentfile:1.1.0-alpha01"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.5.4"
def fragment_version = "1.5.0"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.6.0-alpha03"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
def lifecycle_version = "2.6.0-alpha01"
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
@@ -137,7 +136,7 @@ dependencies {
implementation "androidx.loader:loader:1.1.0"
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta01'
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation "androidx.transition:transition-ktx:1.4.1"
@@ -145,23 +144,20 @@ dependencies {
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.webkit:webkit:1.5.0'
implementation 'androidx.webkit:webkit:1.5.0-beta01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.8.0-alpha02'
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
implementation 'com.google.android.material:material:1.7.0-alpha03'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
implementation 'org.greenrobot:greendao:3.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jsoup:jsoup:1.15.3'
implementation 'org.jsoup:jsoup:1.15.2'
implementation projects.native
}
+32 -19
View File
@@ -26,11 +26,10 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Required to maintain app compatibility. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
@@ -120,21 +119,35 @@
<data android:mimeType="application/x-7z-compressed" />
<data android:mimeType="application/x-rar-compressed" />
<data android:mimeType="application/x-zip-compressed" />
<data android:mimeType="application/x-tar" />
<data android:mimeType="application/x-xz" />
<data android:mimeType="application/gzip" />
<data android:mimeType="application/x-cbz" />
<data android:mimeType="application/x-cbr" />
<data android:mimeType="application/x-cbt" />
<data android:mimeType="application/x-cb7" />
</intent-filter>
<!-- untested -->
<data android:mimeType="application/vnd.comicbook+zip" />
<data android:mimeType="application/vnd.comicbook-rar" />
<data android:mimeType="application/x-compressed-tar" />
<data android:mimeType="application/x-bzip-compressed-tar" />
<data android:mimeType="application/x-lzma-compressed-tar" />
<data android:mimeType="application/x-xz-compressed-tar" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:pathPattern=".*\\.7z" />
<data android:pathPattern=".*\\.rar" />
<data android:pathPattern=".*\\.zip" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.7z" />
<data android:pathPattern=".*\\.rar" />
<data android:pathPattern=".*\\.zip" />
</intent-filter>
</activity>
@@ -49,7 +49,7 @@ class EditTextDialogBuilder constructor(
return mDialog as AlertDialog
}
override fun onEditorAction(v: TextView?, p1: Int, event: KeyEvent?): Boolean {
override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent): Boolean {
return if (mDialog != null) {
val button = mDialog!!.getButton(DialogInterface.BUTTON_POSITIVE)
button?.performClick()
@@ -51,6 +51,7 @@ public final class ConacoTask<V> {
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
private final String mKey;
private final String mUrl;
private final DataContainer mDataContainer;
private final boolean mUseMemoryCache;
private final boolean mUseDiskCache;
private final boolean mUseNetwork;
@@ -72,6 +73,7 @@ public final class ConacoTask<V> {
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
mKey = builder.mKey;
mUrl = builder.mUrl;
mDataContainer = builder.mDataContainer;
mUseMemoryCache = builder.mUseMemoryCache;
mUseDiskCache = builder.mUseDiskCache;
mUseNetwork = builder.mUseNetwork;
@@ -83,6 +85,42 @@ public final class ConacoTask<V> {
mConaco = builder.mConaco;
}
private static void putFromDiskCacheToDataContainer(String key, ValueCache cache, DataContainer container) {
SimpleDiskCache diskCache = cache.getDiskCache();
if (diskCache != null) {
InputStreamPipe pipe = diskCache.getInputStreamPipe(key);
if (pipe != null) {
try {
pipe.obtain();
container.save(pipe.open(), -1L, null, null);
} catch (IOException e) {
Log.d(TAG, "Can't save value from disk cache to data container");
e.printStackTrace();
container.remove();
} finally {
pipe.close();
pipe.release();
}
}
}
}
private static void putFromDataContainerToDiskCache(String key, ValueCache cache, DataContainer container) {
InputStreamPipe pipe = container.get();
if (pipe != null) {
try {
pipe.obtain();
cache.putRawToDisk(key, pipe.open());
} catch (IOException e) {
Log.w(TAG, "Can't save value from data container to disk cache", e);
cache.removeFromDisk(key);
} finally {
pipe.close();
pipe.release();
}
}
}
int getId() {
return mId;
}
@@ -181,6 +219,7 @@ public final class ConacoTask<V> {
private Unikery<T> mUnikery;
private String mKey;
private String mUrl;
private DataContainer mDataContainer;
private boolean mUseMemoryCache = true;
private boolean mUseDiskCache = true;
private boolean mUseNetwork = true;
@@ -223,6 +262,11 @@ public final class ConacoTask<V> {
return this;
}
public Builder<T> setDataContainer(DataContainer dataContainer) {
mDataContainer = dataContainer;
return this;
}
boolean isUseMemoryCache() {
return mUseMemoryCache;
}
@@ -288,7 +332,7 @@ public final class ConacoTask<V> {
if (mUnikery == null) {
throw new IllegalStateException("Must set unikery");
}
if (mKey == null && mUrl == null) {
if (mKey == null && mUrl == null && mDataContainer == null) {
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
}
}
@@ -308,13 +352,26 @@ public final class ConacoTask<V> {
} else {
V value = null;
// First check data container
if (mDataContainer != null && mDataContainer.isEnabled()) {
InputStreamPipe isp = mDataContainer.get();
if (isp != null) {
value = mHelper.decode(isp);
}
}
// Then check disk cache
if (mKey != null) {
if (value == null && mUseDiskCache) {
value = mCache.getFromDisk(mKey);
// Put back to data container
if (value != null && mDataContainer != null && mDataContainer.isEnabled()) {
putFromDiskCacheToDataContainer(mKey, mCache, mDataContainer);
}
}
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
// Put it to memory
mCache.putToMemory(mKey, value);
}
}
@@ -394,6 +451,18 @@ public final class ConacoTask<V> {
}
}
private boolean putToDataContainer(InputStream is, ResponseBody body) {
// Get media type
String mediaType;
MediaType mt = body.contentType();
if (mt != null) {
mediaType = mt.type() + '/' + mt.subtype();
} else {
mediaType = null;
}
return mDataContainer.save(is, body.contentLength(), mediaType, this);
}
@Override
protected V doInBackground(Void... params) {
if (isNotNecessary(this)) {
@@ -420,7 +489,7 @@ public final class ConacoTask<V> {
return null;
}
if (mKey != null) {
if ((mDataContainer == null || !mDataContainer.isEnabled()) && mKey != null) {
if (putToDiskCache(is, body.contentLength())) {
// Get object from disk cache
value = mCache.getFromDisk(mKey);
@@ -437,6 +506,37 @@ public final class ConacoTask<V> {
mCache.removeFromDisk(mKey);
return null;
}
} else if (mDataContainer != null && mDataContainer.isEnabled()) {
// Check url Moved
HttpUrl requestHttpUrl = request.url();
HttpUrl responseHttpUrl = response.request().url();
if (!responseHttpUrl.equals(requestHttpUrl)) {
mDataContainer.onUrlMoved(mUrl, responseHttpUrl.url().toString());
}
// Put to data container
if (!putToDataContainer(is, body)) {
return null;
}
// Get value from data container
InputStreamPipe isp = mDataContainer.get();
if (isp == null) {
return null;
}
value = mHelper.decode(isp);
if (value == null) {
mDataContainer.remove();
} else if (mKey != null) {
// Put to disk cache
putFromDataContainerToDiskCache(mKey, mCache, mDataContainer);
if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
// Put it to memory
mCache.putToMemory(mKey, value);
}
}
return value;
} else {
return null;
}
@@ -0,0 +1,52 @@
/*
* Copyright 2015-2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.conaco;
import androidx.annotation.Nullable;
import com.hippo.streampipe.InputStreamPipe;
import java.io.InputStream;
public interface DataContainer {
/**
* Returns the enabled status for this DataContainer.
*/
boolean isEnabled();
/**
* Get 304 or something like that
*/
void onUrlMoved(String requestUrl, String responseUrl);
/**
* Save the {@code InputStream}
*/
boolean save(InputStream is, long length, @Nullable String mediaType, @Nullable ProgressNotifier notify);
/**
* Get {@code InputStreamPipe} for saved before
*/
@Nullable
InputStreamPipe get();
/**
* Remove saved stuff
*/
void remove();
}
@@ -41,7 +41,6 @@ import com.hippo.yorozuya.NumberUtils;
import java.io.File;
import java.util.Locale;
import java.util.Set;
public class Settings {
@@ -100,11 +99,6 @@ public class Settings {
private static final int DEFAULT_VERSION_CODE = 0;
private static final String KEY_DISPLAY_NAME = "display_name";
private static final String DEFAULT_DISPLAY_NAME = null;
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
private static int LIST_THUMB_SIZE = 40;
public static boolean LIST_THUMB_SIZE_INITED = false;
private static final String KEY_AVATAR = "avatar";
private static final String DEFAULT_AVATAR = null;
private static final String KEY_SHOW_WARNING = "show_warning";
@@ -277,11 +271,6 @@ public class Settings {
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
private static final String KEY_REQUEST_NEWS = "request_news";
private static final boolean DEFAULT_REQUEST_NEWS = true;
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
private static Context sContext;
private static SharedPreferences sSettingsPre;
private static EhConfig sEhConfig;
@@ -395,21 +384,6 @@ public class Settings {
sSettingsPre.edit().putString(key, value).apply();
}
public static Set<String> getStringSet(String key) {
return sSettingsPre.getStringSet(key, null);
}
public static void putStringToStringSet(String key, String value) {
Set<String> set = getStringSet(key);
if (set == null)
set = Set.of(value);
else if (set.contains(value))
return;
else
set.add(value);
sSettingsPre.edit().putStringSet(key, set).apply();
}
public static int getIntFromStr(String key, int defValue) {
try {
return NumberUtils.parseIntSafely(sSettingsPre.getString(key, Integer.toString(defValue)), defValue);
@@ -1165,22 +1139,4 @@ public class Settings {
public static void putClipboardTextHashCode(int value) {
putInt(KEY_CLIPBOARD_TEXT_HASH_CODE, value);
}
public static Set<String> getArchivePasswds() {
return getStringSet(KEY_ARCHIVE_PASSWDS);
}
public static void putPasswdToArchivePasswds(String value) {
putStringToStringSet(KEY_ARCHIVE_PASSWDS, value);
}
public static int getListThumbSize() {
if (LIST_THUMB_SIZE_INITED) {
return LIST_THUMB_SIZE;
}
int size = 3 * getInt(KEY_LIST_THUMB_SIZE, DEFAULT_LIST_THUMB_SIZE);
LIST_THUMB_SIZE = size;
LIST_THUMB_SIZE_INITED = true;
return size;
}
}
@@ -44,7 +44,7 @@ public class FavListUrlBuilder implements Parcelable {
}
};
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
private String mNext;
private int mIndex;
private String mKeyword;
private int mFavCat = FAV_CAT_ALL;
@@ -52,7 +52,7 @@ public class FavListUrlBuilder implements Parcelable {
}
protected FavListUrlBuilder(Parcel in) {
this.mNext = in.readString();
this.mIndex = in.readInt();
this.mKeyword = in.readString();
this.mFavCat = in.readInt();
}
@@ -61,8 +61,8 @@ public class FavListUrlBuilder implements Parcelable {
return favCat >= 0 && favCat <= 9;
}
public void setNext(String next) {
mNext = next;
public void setIndex(int index) {
mIndex = index;
}
public String getKeyword() {
@@ -105,8 +105,8 @@ public class FavListUrlBuilder implements Parcelable {
Log.e(TAG, "Can't URLEncoder.encode " + mKeyword);
}
}
if (mNext != null && !mNext.isEmpty()) {
ub.addQuery("next", mNext);
if (mIndex > 0) {
ub.addQuery("page", Integer.toString(mIndex));
}
return ub.build();
}
@@ -118,7 +118,7 @@ public class FavListUrlBuilder implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.mNext);
dest.writeInt(this.mIndex);
dest.writeString(this.mKeyword);
dest.writeInt(this.mFavCat);
}
@@ -64,8 +64,7 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
@Mode
private int mMode = MODE_NORMAL;
private int mNextGid = 0;
private int mPrevGid = 0;
private int mPageIndex = 0;
private int mCategory = EhUtils.NONE;
private String mKeyword = null;
@@ -87,8 +86,7 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
@SuppressWarnings("WrongConstant")
protected ListUrlBuilder(Parcel in) {
this.mMode = in.readInt();
this.mNextGid = in.readInt();
this.mPrevGid = in.readInt();
this.mPageIndex = in.readInt();
this.mCategory = in.readInt();
this.mKeyword = in.readString();
this.mAdvanceSearch = in.readInt();
@@ -107,8 +105,7 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
*/
public void reset() {
mMode = MODE_NORMAL;
mNextGid = 0;
mPrevGid = 0;
mPageIndex = 0;
mCategory = EhUtils.NONE;
mKeyword = null;
mAdvanceSearch = -1;
@@ -140,12 +137,12 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
mMode = mode;
}
public void setNextGid(int nextGid) {
mNextGid = nextGid;
public int getPageIndex() {
return mPageIndex;
}
public void setPrevGid(int prevGid) {
mPrevGid = prevGid;
public void setPageIndex(int pageIndex) {
mPageIndex = pageIndex;
}
public int getCategory() {
@@ -236,8 +233,7 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
*/
public void set(ListUrlBuilder lub) {
mMode = lub.mMode;
mNextGid = lub.mNextGid;
mPrevGid = lub.mPrevGid;
mPageIndex = lub.mPageIndex;
mCategory = lub.mCategory;
mKeyword = lub.mKeyword;
mAdvanceSearch = lub.mAdvanceSearch;
@@ -528,11 +524,9 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
if (mSHash != null) {
ub.addQuery("f_shash", mSHash);
}
if (mNextGid != 0) {
ub.addQuery("next", mNextGid);
}
if (mPrevGid != 0) {
ub.addQuery("prev", mPrevGid);
// Page index
if (mPageIndex != 0) {
ub.addQuery("page", mPageIndex);
}
// Advance search
if (mAdvanceSearch != -1) {
@@ -576,8 +570,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
} catch (UnsupportedEncodingException e) {
// Empty
}
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid);
if (mPageIndex != 0) {
sb.append('/').append(mPageIndex);
}
return sb.toString();
}
@@ -589,8 +583,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
} catch (UnsupportedEncodingException e) {
// Empty
}
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid);
if (mPageIndex != 0) {
sb.append('/').append(mPageIndex);
}
return sb.toString();
}
@@ -606,8 +600,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
} catch (UnsupportedEncodingException e) {
// Empty
}
if (mNextGid != 0) {
sb.append("&p=").append(mNextGid);
if (mPageIndex != 0) {
sb.append("&p=").append(mPageIndex);
}
return sb.toString();
}
@@ -621,8 +615,7 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mMode);
dest.writeInt(this.mNextGid);
dest.writeInt(this.mPrevGid);
dest.writeInt(this.mPageIndex);
dest.writeInt(this.mCategory);
dest.writeString(this.mKeyword);
dest.writeInt(this.mAdvanceSearch);
@@ -31,10 +31,8 @@ import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.List;
import java.util.regex.Pattern;
public class FavoritesParser {
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+-\\d+)");
public static Result parse(String body) throws Exception {
if (body.contains("This page requires you to log on.</p>")) {
@@ -61,25 +59,14 @@ public class FavoritesParser {
e.printStackTrace();
throw new ParseException("Parse favorites error", body);
}
Result re = new Result();
try {
var d = Jsoup.parse(body);
var snv = d.getElementsByClass("searchnav").first();
assert snv != null;
var href = snv.child(3).child(0).attr("href");
var matcher = PATTERN_NEXT_PAGE.matcher(href);
if (matcher.find()) {
re.nextPage = matcher.group(1);
}
} catch (Throwable e) {
e.printStackTrace();
}
GalleryListParser.Result result = GalleryListParser.parse(body);
Result re = new Result();
re.catArray = catArray;
re.countArray = countArray;
re.pages = result.pages;
re.nextPage = result.nextPage;
re.galleryInfoList = result.galleryInfoList;
return re;
@@ -88,7 +75,8 @@ public class FavoritesParser {
public static class Result {
public String[] catArray; // Size 10
public int[] countArray; // Size 10
public String nextPage;
public int pages;
public int nextPage;
public List<GalleryInfo> galleryInfoList;
}
}
@@ -51,8 +51,7 @@ public class GalleryListParser {
private static final Pattern PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px");
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("background-color:rgba\\((\\d+),(\\d+),(\\d+),");
private static final Pattern PATTERN_PAGES = Pattern.compile("(\\d+) page");
private static final Pattern PATTERN_GETINT = Pattern.compile("[^0-9]");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("page=(\\d+)");
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
new String[]{"0", "0", "0"},
@@ -298,21 +297,39 @@ public class GalleryListParser {
return gi;
}
private static int getInt(String str) {
var m = PATTERN_GETINT.matcher(str);
return Integer.parseInt(m.replaceAll("").trim());
}
public static Result parse(@NonNull String body) throws Exception {
Result result = new Result();
Document d = Jsoup.parse(body);
result.founds = Integer.MAX_VALUE;
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
if (body.contains("No hits found</p>")) {
result.founds = 0;
result.galleryInfoList = Collections.EMPTY_LIST;
return result;
try {
Element ptt = d.getElementsByClass("ptt").first();
Elements es = ptt.child(0).child(0).children();
result.pages = Integer.parseInt(es.get(es.size() - 2).text().trim());
Element e = es.get(es.size() - 1);
if (e != null) {
e = e.children().first();
if (e != null) {
String href = e.attr("href");
Matcher matcher = PATTERN_NEXT_PAGE.matcher(href);
if (matcher.find()) {
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0);
}
}
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
if (body.contains("No hits found</p>")) {
result.pages = 0;
//noinspection unchecked
result.galleryInfoList = Collections.EMPTY_LIST;
return result;
} else if (d.getElementsByClass("ptt").isEmpty()) {
result.pages = 1;
} else {
result.pages = Integer.MAX_VALUE;
}
}
try {
@@ -347,7 +364,8 @@ public class GalleryListParser {
}
public static class Result {
public int founds;
public int pages;
public int nextPage;
public boolean noWatchedTags;
public List<GalleryInfo> galleryInfoList;
}
@@ -18,7 +18,6 @@ package com.hippo.ehviewer.gallery;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Process;
import androidx.annotation.NonNull;
@@ -27,26 +26,22 @@ import androidx.annotation.Nullable;
import com.hippo.UriArchiveAccessor;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.thread.PVLock;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
public class ArchiveGalleryProvider extends GalleryProvider2 {
private static final AtomicInteger sIdGenerator = new AtomicInteger();
public static Handler showPasswd;
public static String passwd;
public static PVLock pv = new PVLock(0);
private final UriArchiveAccessor archiveAccessor;
private final Stack<Integer> requests = new Stack<>();
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
@@ -61,7 +56,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
UriArchiveAccessor archiveAccessor1;
try {
archiveAccessor1 = new UriArchiveAccessor(context, uri);
} catch (Exception e) {
} catch (FileNotFoundException e) {
archiveAccessor1 = null;
e.printStackTrace();
}
@@ -95,11 +90,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
decodeThread.interrupt();
decodeThread = null;
}
try {
archiveAccessor.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
@@ -169,27 +159,9 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
}
private class ArchiveTask implements Runnable {
private void waitPasswd() throws InterruptedException {
showPasswd.sendEmptyMessage(0);
pv.p();
}
private void notifyError() {
showPasswd.sendEmptyMessage(1);
}
private void notifyDismiss() {
showPasswd.sendEmptyMessage(2);
}
@Override
public void run() {
try {
size = archiveAccessor.open();
} catch (Exception e) {
e.printStackTrace();
size = 0;
}
size = archiveAccessor.open();
if (size <= 0) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
@@ -199,34 +171,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
// Update size and notify changed
notifyDataChanged();
if (archiveAccessor.needPassword()) {
boolean need_request = true;
Set<String> set = Settings.getArchivePasswds();
if (set != null) {
for (String passwd : set) {
if (archiveAccessor.providePassword(passwd)) {
need_request = false;
break;
}
}
}
while (!Thread.currentThread().isInterrupted() && need_request) {
try {
waitPasswd();
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
if (!archiveAccessor.providePassword(passwd))
notifyError();
else {
Settings.putPasswdToArchivePasswds(passwd);
notifyDismiss();
break;
}
}
}
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (requests) {
@@ -250,7 +194,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
Pipe pipe = new Pipe();
Pipe pipe = new Pipe(4096 * 16);
synchronized (streams) {
if (streams.get(index) != null) {
@@ -260,8 +204,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
streams.notify();
}
archiveAccessor.extracttoOutputStream(index, pipe.getOutputFd());
pipe.closeOutputFd();
archiveAccessor.extractTargetIndexToOutputStream(index, pipe.getOutputStream());
}
}
}
@@ -16,37 +16,141 @@
package com.hippo.ehviewer.gallery;
import android.os.ParcelFileDescriptor;
import androidx.annotation.NonNull;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
class Pipe {
private ParcelFileDescriptor[] fd;
{
try {
fd = ParcelFileDescriptor.createPipe();
} catch (IOException e) {
e.printStackTrace();
}
}
private final InputStream inputStream = new FileInputStream(fd[0].getFileDescriptor());
private final int capacity;
private final byte[] buffer;
private int head = 0;
private int tail = 0;
private boolean full = false;
private boolean inClosed = false;
private boolean outClosed = false;
private final InputStream inputStream = new InputStream() {
@Override
public int read() throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[1];
if (read(bytes, 0, 1) != -1) {
return bytes[0];
} else {
return -1;
}
}
}
@Override
public int read(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
for (; ; ) {
if (inClosed) {
throw new IOException("The InputStream is closed");
}
if (len == 0) {
return 0;
}
if (head == tail && !full) {
if (outClosed) {
// No bytes available and the OutputStream is closed. So it's the end.
return -1;
} else {
// Wait for OutputStream write bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
}
} else {
int read = Math.min(len, (head < tail ? tail : capacity) - head);
System.arraycopy(buffer, head, b, off, read);
head += read;
if (head == capacity) {
head = 0;
}
full = false;
Pipe.this.notifyAll();
return read;
}
}
}
}
@Override
public void close() {
synchronized (Pipe.this) {
inClosed = true;
Pipe.this.notifyAll();
}
}
};
private final OutputStream outputStream = new OutputStream() {
@Override
public void write(int b) throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[]{(byte) b};
write(bytes, 0, 1);
}
}
@Override
public void write(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
while (len != 0) {
if (head == tail && full) {
// The buffer is full, wait for InputStream read bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
} else {
int write = Math.min(len, (head <= tail ? capacity : head) - tail);
System.arraycopy(b, off, buffer, tail, write);
off += write;
len -= write;
tail += write;
if (tail == capacity) {
tail = 0;
}
if (head == tail) {
full = true;
}
Pipe.this.notifyAll();
}
}
}
}
@Override
public void close() {
synchronized (Pipe.this) {
outClosed = true;
Pipe.this.notifyAll();
}
}
};
Pipe(int capacity) {
this.capacity = capacity;
this.buffer = new byte[capacity];
}
InputStream getInputStream() {
return inputStream;
}
int getOutputFd() {
return fd[1].getFd();
}
void closeOutputFd() {
try {
fd[1].close();
} catch (IOException e) {
e.printStackTrace();
}
OutputStream getOutputStream() {
return outputStream;
}
}
@@ -41,28 +41,6 @@ class VersionPreference @JvmOverloads constructor(
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setTitle(R.string.show_library_version)
val info = NativeLibInfo().addLib(Native.getlibarchiveVersion())
.addLib("lzma(xz-utils)", Native.getliblzmaVersion())
.addLib("zlib", Native.getzlibVersion())
.addLib("libjpeg-turbo", Native.getlibjpeg_turboVersion())
.addLib(Native.getnettleVersion())
builder.setMessage(info.getMessage())
}
inner class NativeLibInfo {
private var message: String = ""
fun addLib(libname:String, libversion:String): NativeLibInfo {
message = "$message$libname $libversion\n"
return this
}
fun getMessage(): String {
return message;
}
fun addLib(msg: String): NativeLibInfo {
message = "$message$msg\n"
return this
}
builder.setMessage(Native.getlibarchiveVersion() + "\nlzma(xz-utils) " + Native.getliblzmaVersion() + "\nzlib " + Native.getzlibVersion() + "\nlibjpeg-turbo " + Native.getlibjpeg_turboVersion())
}
}
@@ -45,10 +45,6 @@ import java.util.List;
public final class CommonOperations {
public static int getPagesForFounds(int founds, int per) {
return (int)Math.ceil((double) founds/per);
}
private static void doAddToFavorites(Activity activity, GalleryInfo galleryInfo,
int slot, EhClient.Callback<Void> listener) {
if (slot == -1) {
@@ -37,8 +37,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.KeyEvent;
@@ -69,7 +67,6 @@ import androidx.core.view.WindowInsetsControllerCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
import com.hippo.ehviewer.R;
@@ -106,7 +103,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import rikka.core.res.ConfigurationKt;
import rikka.core.res.ResourcesKt;
@@ -318,19 +314,10 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
onRestore(savedInstanceState);
}
builder = new EditTextDialogBuilder(this, null, getString(R.string.archive_passwd));
builder.setTitle(getString(R.string.archive_need_passwd));
builder.setPositiveButton(getString(android.R.string.ok), null);
dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
if (mGalleryProvider == null) {
finish();
return;
}
ArchiveGalleryProvider.showPasswd = new ShowPasswdDialogHandler(this);
mGalleryProvider.start();
// Get start page
@@ -394,22 +381,17 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 300;
if (scroll < 0.0f) {
if (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0.0f) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageLeft();
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT){
} else {
mGalleryView.pageRight();
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
}
} else {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageRight();
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
} else {
mGalleryView.pageLeft();
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
}
}
}
@@ -1311,69 +1293,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
private EditTextDialogBuilder builder;
private boolean dialogShown = false;
private AlertDialog dialog;
private void showPasswdDialog() {
if (!dialogShown) {
dialogShown = true;
dialog.show();
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
GalleryActivity.this.onProvidePasswd();
});
}
dialog.setOnCancelListener(v -> finish());
}
}
private void onProvidePasswd() {
String passwd = builder.getText();
if (passwd.isEmpty())
builder.setError(getString(R.string.passwd_cannot_be_empty));
else {
ArchiveGalleryProvider.passwd = passwd;
ArchiveGalleryProvider.pv.v();
}
}
private void onPasswdWrong() {
builder.setError(getString(R.string.passwd_wrong));
}
private void onPasswdCorrect() {
dialog.dismiss();
}
private static class ShowPasswdDialogHandler extends Handler {
//弱引用持有HandlerActivity , GC 回收时会被回收掉
private final WeakReference<GalleryActivity> weakReference;
public ShowPasswdDialogHandler(GalleryActivity activity) {
this.weakReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
GalleryActivity activity = weakReference.get();
super.handleMessage(msg);
if (null != activity) {
switch (msg.what) {
case 0:
activity.showPasswdDialog();
break;
case 1:
activity.onPasswdWrong();
break;
case 2:
activity.onPasswdCorrect();
break;
}
}
}
}
}
@@ -307,6 +307,7 @@ public final class MainActivity extends StageActivity
mStageLayout = (EhStageLayout) ViewUtils.$$(this, R.id.fragment_container);
mDrawerLayout = (DrawerLayout) ViewUtils.$$(this, R.id.draw_view);
mDrawerLayout.setStatusBarBackgroundColor(0);
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
@@ -0,0 +1,68 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.fragment.SettingsFragment;
import com.hippo.ehviewer.ui.scene.BaseScene;
public final class SettingsActivity extends EhActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preference);
setSupportActionBar(findViewById(R.id.toolbar));
ActionBar bar = getSupportActionBar();
if (bar != null) {
bar.setDisplayHomeAsUpEnabled(true);
}
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
.replace(R.id.fragment, new SettingsFragment())
.commitAllowingStateLoss();
}
}
public void showTip(@StringRes int id, int length) {
showTip(getString(id), length);
}
public void showTip(CharSequence message, int length) {
Snackbar.make(findViewById(R.id.snackbar), message,
length == BaseScene.LENGTH_LONG ? Snackbar.LENGTH_LONG : Snackbar.LENGTH_SHORT).show();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}
@@ -1,64 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui
import android.content.res.Configuration
import android.os.Bundle
import android.view.MenuItem
import androidx.annotation.StringRes
import androidx.core.view.WindowCompat
import androidx.fragment.app.FragmentTransaction
import com.google.android.material.snackbar.Snackbar
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.fragment.SettingsFragment
import com.hippo.ehviewer.ui.scene.BaseScene
class SettingsActivity : EhActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_preference)
setSupportActionBar(findViewById(R.id.toolbar))
val bar = supportActionBar
bar?.setDisplayHomeAsUpEnabled(true)
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
.replace(R.id.fragment, SettingsFragment())
.commitAllowingStateLoss()
}
}
fun showTip(@StringRes id: Int, length: Int) {
showTip(getString(id), length)
}
fun showTip(message: CharSequence?, length: Int) {
Snackbar.make(
findViewById(R.id.snackbar), message!!,
if (length == BaseScene.LENGTH_LONG) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT
).show()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
}
@@ -17,13 +17,12 @@
package com.hippo.ehviewer.ui.fragment;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.provider.Settings;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@@ -40,11 +39,6 @@ import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
import com.hippo.ehviewer.client.parser.FavoritesParser;
import com.hippo.ehviewer.ui.scene.BaseScene;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
@@ -59,7 +53,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -71,7 +64,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
private static final String KEY_IMPORT_DATA = "import_data";
private static final String KEY_EXPORT_DATA = "export_data";
private static final String KEY_OPEN_BY_DEFAULT = "open_by_default";
private static final String KEY_BACKUP_FAVORITE = "backup_favorite";
ActivityResultLauncher<String> exportLauncher = registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
@@ -239,7 +231,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
Preference importData = findPreference(KEY_IMPORT_DATA);
Preference exportData = findPreference(KEY_EXPORT_DATA);
Preference openByDefault = findPreference(KEY_OPEN_BY_DEFAULT);
Preference backupFavorite = findPreference(KEY_BACKUP_FAVORITE);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
openByDefault.setVisible(false);
@@ -251,7 +242,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
clearMemoryCache.setOnPreferenceClickListener(this);
importData.setOnPreferenceClickListener(this);
exportData.setOnPreferenceClickListener(this);
backupFavorite.setOnPreferenceClickListener(this);
appLanguage.setOnPreferenceChangeListener(this);
}
@@ -298,62 +288,10 @@ public class AdvancedFragment extends BasePreferenceFragment {
startActivity(intent);
}
return true;
} else if (KEY_BACKUP_FAVORITE.equals(key)) {
try {
showTip(R.string.backup_favorite_start, BaseScene.LENGTH_SHORT);
backupFavorite();
} catch (Exception e) {
ExceptionUtils.throwIfFatal(e);
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
}
return true;
}
return false;
}
private void backupFavorite() {
AtomicInteger page = new AtomicInteger(0);
Context context = requireContext();
Activity activity = requireActivity();
EhClient mClient = EhApplication.getEhClient(context);
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
// favListUrlBuilder.setNext(page.get());
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
request.setCallback(new EhClient.Callback() {
@Override
public void onSuccess(Object result) {
try {
FavoritesParser.Result result1 = (FavoritesParser.Result) result;
EhDB.putLocalFavorites(result1.galleryInfoList);
Log.d("LocalFavorites", "now backup page " + page.get());
if (!result1.galleryInfoList.isEmpty()) {
// favListUrlBuilder.setNext(page.incrementAndGet());
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
mClient.execute(request);
} else {
showTip(R.string.backup_favorite_success, BaseScene.LENGTH_SHORT);
}
} catch (Exception e) {
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
}
}
@Override
public void onFailure(Exception e) {
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
}
@Override
public void onCancel() {
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
}
});
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
mClient.execute(request);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
@@ -38,7 +38,6 @@ public class EhFragment extends BasePreferenceFragment {
Preference blackDarkTheme = findPreference(Settings.KEY_BLACK_DARK_THEME);
Preference gallerySite = findPreference(Settings.KEY_GALLERY_SITE);
Preference listMode = findPreference(Settings.KEY_LIST_MODE);
Preference listThumbSize = findPreference(Settings.KEY_LIST_THUMB_SIZE);
Preference detailSize = findPreference(Settings.KEY_DETAIL_SIZE);
Preference thumbSize = findPreference(Settings.KEY_THUMB_SIZE);
Preference showTagTranslations = findPreference(Settings.KEY_SHOW_TAG_TRANSLATIONS);
@@ -47,7 +46,6 @@ public class EhFragment extends BasePreferenceFragment {
theme.setOnPreferenceChangeListener(this);
gallerySite.setOnPreferenceChangeListener(this);
listMode.setOnPreferenceChangeListener(this);
listThumbSize.setOnPreferenceChangeListener(this);
detailSize.setOnPreferenceChangeListener(this);
thumbSize.setOnPreferenceChangeListener(this);
showTagTranslations.setOnPreferenceChangeListener(this);
@@ -72,10 +70,6 @@ public class EhFragment extends BasePreferenceFragment {
} else if (Settings.KEY_LIST_MODE.equals(key)) {
requireActivity().setResult(Activity.RESULT_OK);
return true;
} else if (Settings.KEY_LIST_THUMB_SIZE.equals(key)) {
Settings.LIST_THUMB_SIZE_INITED = false;
requireActivity().setResult(Activity.RESULT_OK);
return true;
} else if (Settings.KEY_DETAIL_SIZE.equals(key)) {
requireActivity().setResult(Activity.RESULT_OK);
} else if (Settings.KEY_THUMB_SIZE.equals(key)) {
@@ -56,6 +56,7 @@ public abstract class BaseScene extends SceneFragment {
private View drawerView;
@Nullable
private SparseArray<Parcelable> drawerViewState;
private boolean needWhiteStatusBar = needWhiteStatusBar();
public void addAboveSnackView(View view) {
FragmentActivity activity = getActivity();
@@ -117,6 +118,10 @@ public abstract class BaseScene extends SceneFragment {
return true;
}
public boolean needWhiteStatusBar() {
return true;
}
public int getNavCheckedItem() {
return 0;
}
@@ -171,6 +176,14 @@ public abstract class BaseScene extends SceneFragment {
drawerView = null;
}
public void setLightStatusBar(boolean set) {
var insetsController = getInsetsController();
if (insetsController != null) {
insetsController.setAppearanceLightStatusBars(set && (requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
}
needWhiteStatusBar = set;
}
public void onDestroyDrawerView() {
}
@@ -200,11 +213,7 @@ public abstract class BaseScene extends SceneFragment {
// Hide soft ime
hideSoftInput();
insetsController = getInsetsController();
if (insetsController != null) {
insetsController.setAppearanceLightStatusBars((requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
}
setLightStatusBar(needWhiteStatusBar);
}
@Nullable
@@ -22,17 +22,21 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
@@ -41,20 +45,30 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
import com.hippo.app.CheckBoxDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.conaco.DataContainer;
import com.hippo.conaco.ProgressNotifier;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.HandlerDrawable;
@@ -71,6 +85,7 @@ import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.download.DownloadService;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.io.UniFileInputStreamPipe;
import com.hippo.scene.Announcer;
@@ -84,6 +99,7 @@ import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ObjectUtils;
import com.hippo.yorozuya.ViewUtils;
import com.hippo.yorozuya.collect.LongList;
@@ -374,9 +390,90 @@ public class DownloadsScene extends ToolbarScene
updateView();
guide();
return view;
}
private void guide() {
if (Settings.getGuideDownloadThumb() && null != mRecyclerView) {
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Settings.getGuideDownloadThumb()) {
guideDownloadThumb();
}
if (null != mRecyclerView) {
ViewUtils.removeOnGlobalLayoutListener(mRecyclerView.getViewTreeObserver(), this);
}
}
});
} else {
guideDownloadLabels();
}
}
private void guideDownloadThumb() {
MainActivity activity = getMainActivity();
if (null == activity || !Settings.getGuideDownloadThumb() || null == mLayoutManager || null == mRecyclerView) {
guideDownloadLabels();
return;
}
int position = mLayoutManager.findFirstCompletelyVisibleItemPositions(null)[0];
if (position < 0) {
guideDownloadLabels();
return;
}
RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(position);
if (null == holder) {
guideDownloadLabels();
return;
}
TapTargetView.showFor(requireActivity(),
TapTarget.forView(((DownloadHolder) holder).thumb,
getString(R.string.guide_download_thumb_title),
getString(R.string.guide_download_thumb_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetDismissed(view, userInitiated);
Settings.putGuideDownloadThumb(false);
guideDownloadLabels();
}
});
}
private void guideDownloadLabels() {
MainActivity activity = getMainActivity();
if (null == activity || !Settings.getGuideDownloadLabels()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_download_labels_title),
getString(R.string.guide_download_labels_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.puttGuideDownloadLabels(false);
openDrawer(Gravity.RIGHT);
}
});
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -542,13 +639,20 @@ public class DownloadsScene extends ToolbarScene
initLabels();
RecyclerViewDragDropManager dragDropManager = new RecyclerViewDragDropManager();
dragDropManager.setInitiateOnLongPress(true);
dragDropManager.setInitiateOnTouch(false);
dragDropManager.setDraggingItemAlpha(0.8f);
mLabelAdapter = new DownloadLabelAdapter(inflater);
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
mLabelAdapter.setHasStableIds(true);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new DownloadLabelItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.setAdapter(mLabelAdapter);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(mLabelAdapter));
dragDropManager.attachRecyclerView(recyclerView);
return view;
}
@@ -871,7 +975,7 @@ public class DownloadsScene extends ToolbarScene
holder.speed.setText(FileUtils.humanReadableByteCount(speed, false) + "/S");
}
private static class DownloadLabelHolder extends RecyclerView.ViewHolder {
private static class DownloadLabelHolder extends AbstractDraggableItemViewHolder {
private final TextView label;
private final ImageView option;
@@ -883,7 +987,74 @@ public class DownloadsScene extends ToolbarScene
}
}
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> {
private static class ThumbDataContainer implements DataContainer {
private final DownloadInfo mInfo;
@Nullable
private UniFile mFile;
public ThumbDataContainer(@NonNull DownloadInfo info) {
mInfo = info;
}
private void ensureFile() {
if (mFile == null) {
UniFile dir = SpiderDen.getGalleryDownloadDir(mInfo);
if (dir != null && dir.isDirectory()) {
mFile = dir.createFile(".thumb");
}
}
}
@Override
public boolean isEnabled() {
ensureFile();
return mFile != null;
}
@Override
public void onUrlMoved(String requestUrl, String responseUrl) {
}
@Override
public boolean save(InputStream is, long length, String mediaType, ProgressNotifier notify) {
ensureFile();
if (mFile == null) {
return false;
}
OutputStream os = null;
try {
os = mFile.openOutputStream();
IOUtils.copy(is, os);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
IOUtils.closeQuietly(os);
}
}
@Override
public InputStreamPipe get() {
ensureFile();
if (mFile != null) {
return new UniFileInputStreamPipe(mFile);
} else {
return null;
}
}
@Override
public void remove() {
if (mFile != null) {
mFile.delete();
}
}
}
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> implements DraggableItemAdapter<DownloadLabelHolder> {
private final LayoutInflater mInflater;
@@ -937,15 +1108,36 @@ public class DownloadsScene extends ToolbarScene
});
if (position > 0) {
holder.option.setVisibility(View.VISIBLE);
holder.option.setOnClickListener(v -> {
holder.itemView.setOnLongClickListener(v -> {
if (context != null) {
EditTextDialogBuilder builder = new EditTextDialogBuilder(
context, label, getString(R.string.download_labels));
builder.setTitle(R.string.rename_label_title);
builder.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.show();
new RenameLabelDialogHelper(builder, dialog, label);
PopupMenu popupMenu = new PopupMenu(context, holder.option);
popupMenu.inflate(R.menu.download_label_option);
popupMenu.show();
popupMenu.setOnMenuItemClickListener(item -> {
int itemId = item.getItemId();
if (itemId == R.id.menu_label_rename) {
EditTextDialogBuilder builder = new EditTextDialogBuilder(
context, label, getString(R.string.download_labels));
builder.setTitle(R.string.rename_label_title);
builder.setPositiveButton(android.R.string.ok, null);
AlertDialog dialog = builder.show();
new RenameLabelDialogHelper(builder, dialog, label);
} else if (itemId == R.id.menu_label_remove) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_label_title))
.setMessage(getString(R.string.delete_label_message, label))
.setPositiveButton(R.string.delete, (dialog, which) -> {
mDownloadManager.deleteLabel(label);
mLabels.remove(position);
notifyDataSetChanged();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
return false;
});
}
return true;
});
} else {
holder.option.setVisibility(View.GONE);
@@ -962,6 +1154,43 @@ public class DownloadsScene extends ToolbarScene
public int getItemCount() {
return mLabels != null ? mLabels.size() : 0;
}
@Override
public boolean onCheckCanStartDrag(@NonNull DownloadLabelHolder holder, int position, int x, int y) {
return position != 0 && x > holder.option.getX() && y > holder.option.getY();
}
@Override
public ItemDraggableRange onGetItemDraggableRange(@NonNull DownloadLabelHolder holder, int position) {
return new ItemDraggableRange(1, getItemCount() - 1);
}
@Override
public void onMoveItem(int fromPosition, int toPosition) {
Context context = getContext();
if (null == context || fromPosition == toPosition || toPosition == 0) {
return;
}
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
final String item = mLabels.remove(fromPosition);
mLabels.add(toPosition, item);
}
@Override
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
return dropPosition != 0;
}
@Override
public void onItemDragStarted(int position) {
notifyDataSetChanged();
}
@Override
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
notifyDataSetChanged();
}
}
private class DeleteRangeDialogHelper implements DialogInterface.OnClickListener {
@@ -1161,7 +1390,8 @@ public class DownloadsScene extends ToolbarScene
return;
}
DownloadInfo info = mList.get(position);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb, true);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb,
new ThumbDataContainer(info), true);
holder.title.setText(EhUtils.getSuitableTitle(info));
holder.uploader.setText(info.uploader);
holder.rating.setRating(info.rating);
@@ -1298,43 +1528,4 @@ public class DownloadsScene extends ToolbarScene
}
}
}
private class DownloadLabelItemTouchHelperCallback extends ItemTouchHelper.Callback {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
}
@SuppressLint("NotifyDataSetChanged")
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getBindingAdapterPosition();
int toPosition = target.getBindingAdapterPosition();
Context context = getContext();
if (null == context || fromPosition == toPosition || toPosition == 0 || fromPosition == 0) {
return false;
}
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
final String item = mLabels.remove(fromPosition);
mLabels.add(toPosition, item);
mLabelAdapter.notifyDataSetChanged();
return true;
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getBindingAdapterPosition();
if (mDownloadManager == null)
return;
if (position != 0) {
String label = mLabels.get(position);
mDownloadManager.deleteLabel(label);
}
mLabels.remove(position);
mLabelAdapter.notifyDataSetChanged();
}
}
}
@@ -24,7 +24,6 @@ import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.InputType;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
@@ -46,6 +45,8 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.annotation.Implemented;
@@ -310,9 +311,40 @@ public class FavoritesScene extends BaseScene implements
mHelper.firstRefresh();
}
guideCollections();
return view;
}
private void guideCollections() {
Activity activity = getMainActivity();
if (null == activity || !Settings.getGuideCollections()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_collections_title),
getString(R.string.guide_collections_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.putGuideCollections(false);
openDrawer(Gravity.RIGHT);
}
});
}
// keyword of mUrlBuilder, fav cat of mUrlBuilder, mFavCatArray.
// They changed, call it
private void updateSearchBar() {
@@ -855,14 +887,7 @@ public class FavoritesScene extends BaseScene implements
}
updateSearchBar();
int pages = 0;
assert mUrlBuilder != null;
if (FavListUrlBuilder.isValidFavCat(mUrlBuilder.getFavCat()))
pages = CommonOperations.getPagesForFounds(mFavCountArray[mUrlBuilder.getFavCat()], 50);
else if (mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_ALL)
pages = CommonOperations.getPagesForFounds(mFavCountSum, 50);
mHelper.nextPg = result.nextPage;
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
if (mDrawerAdapter != null) {
mDrawerAdapter.notifyDataSetChanged();
@@ -1181,8 +1206,6 @@ public class FavoritesScene extends BaseScene implements
}
private class FavoritesHelper extends GalleryInfoContentHelper {
public int pgCounter = 0;
public String nextPg = null;
@Override
protected void getPageData(final int taskId, int type, int page) {
@@ -1190,7 +1213,6 @@ public class FavoritesScene extends BaseScene implements
if (null == activity || null == mUrlBuilder || null == mClient) {
return;
}
pgCounter = page;
if (mEnableModify) {
mEnableModify = false;
@@ -1230,7 +1252,7 @@ public class FavoritesScene extends BaseScene implements
url = mUrlBuilder.build();
}
mUrlBuilder.setNext(nextPg);
mUrlBuilder.setIndex(page);
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_MODIFY_FAVORITES);
request.setCallback(new GetFavoritesListener(getContext(),
@@ -1243,7 +1265,7 @@ public class FavoritesScene extends BaseScene implements
final String keyword = mUrlBuilder.getKeyword();
SimpleHandler.getInstance().post(() -> onGetFavoritesLocal(keyword, taskId));
} else {
mUrlBuilder.setNext(nextPg);
mUrlBuilder.setIndex(page);
String url = mUrlBuilder.build();
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
@@ -1306,25 +1328,5 @@ public class FavoritesScene extends BaseScene implements
}
}
}
@Override
protected void onClearData() {
super.onClearData();
nextPg = null;
}
@Override
protected Parcelable saveInstanceState(Parcelable superState) {
Bundle bundle = (Bundle) super.saveInstanceState(superState);
bundle.putString(KEY_NEXT_PAGE, nextPg);
return bundle;
}
@Override
protected Parcelable restoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
nextPg = bundle.getString(KEY_NEXT_PAGE);
return super.restoreInstanceState(state);
}
}
}
@@ -184,7 +184,14 @@ public final class GalleryCommentsScene extends ToolbarScene
mEditPanel,
mFabLayout
));
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(getTheme(), androidx.appcompat.R.attr.colorPrimary));
mRefreshLayout.setColorSchemeResources(
R.color.loading_indicator_red,
R.color.loading_indicator_purple,
R.color.loading_indicator_blue,
R.color.loading_indicator_cyan,
R.color.loading_indicator_green,
R.color.loading_indicator_yellow);
mRefreshLayout.setOnRefreshListener(this);
Context context = requireContext();
@@ -559,6 +559,12 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
ViewGroup main = (ViewGroup) ViewUtils.$$(view, R.id.main);
ScrollView mainView = (ScrollView) ViewUtils.$$(main, R.id.scroll_view);
mainView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
if (mActionGroup != null && mHeader != null) {
//noinspection IntegerDivisionInFloatingPointContext
setLightStatusBar(mActionGroup.getY() - mHeader.findViewById(R.id.header_content).getPaddingTop() / 2 < scrollY);
}
});
View progressView = ViewUtils.$$(main, R.id.progress_view);
mTip = (TextView) ViewUtils.$$(main, R.id.tip);
mViewTransition = new ViewTransition(mainView, progressView, mTip);
@@ -662,6 +668,11 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
return view;
}
@Override
public boolean needWhiteStatusBar() {
return false;
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -830,16 +841,19 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
switch (state) {
case STATE_NORMAL:
setLightStatusBar(false);
// Show mMainView
mViewTransition.showView(0, animation);
// Show mBelowHeader
mViewTransition2.showView(0, animation);
break;
case STATE_REFRESH:
setLightStatusBar(true);
// Show mProgressView
mViewTransition.showView(1, animation);
break;
case STATE_REFRESH_HEADER:
setLightStatusBar(false);
// Show mMainView
mViewTransition.showView(0, animation);
// Show mProgress
@@ -848,6 +862,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
default:
case STATE_INIT:
case STATE_FAILED:
setLightStatusBar(true);
// Show mFailedView
mViewTransition.showView(2, animation);
break;
@@ -18,10 +18,13 @@ package com.hippo.ehviewer.ui.scene;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -30,8 +33,10 @@ import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
@@ -45,17 +50,25 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.draggable.DraggableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.draggable.ItemDraggableRange;
import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractDraggableItemViewHolder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
@@ -98,6 +111,7 @@ import com.hippo.widget.FabLayout;
import com.hippo.widget.SearchBarMover;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.StringUtils;
@@ -620,9 +634,40 @@ public final class GalleryListScene extends BaseScene
mHelper.firstRefresh();
}
guideQuickSearch();
return view;
}
private void guideQuickSearch() {
Activity activity = getMainActivity();
if (null == activity || !Settings.getGuideQuickSearch()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_quick_search_title),
getString(R.string.guide_quick_search_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.putGuideQuickSearch(false);
openDrawer(Gravity.RIGHT);
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -741,13 +786,19 @@ public final class GalleryListScene extends BaseScene
Context context = getContext();
AssertUtils.assertNotNull(context);
RecyclerViewDragDropManager dragDropManager = new RecyclerViewDragDropManager();
dragDropManager.setInitiateOnLongPress(true);
dragDropManager.setInitiateOnTouch(false);
dragDropManager.setDraggingItemAlpha(0.8f);
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
final QsDrawerAdapter qsDrawerAdapter = new QsDrawerAdapter(inflater);
qsDrawerAdapter.setHasStableIds(true);
recyclerView.setAdapter(qsDrawerAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new GalleryListQSItemTouchHelperCallback(qsDrawerAdapter));
itemTouchHelper.attachToRecyclerView(recyclerView);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
recyclerView.setAdapter(dragDropManager.createWrappedAdapter(qsDrawerAdapter));
dragDropManager.attachRecyclerView(recyclerView);
mQuickSearchList = EhDB.getAllQuickSearch();
tip.setText(R.string.quick_search_tip);
if (mIsTopList) {
@@ -1371,7 +1422,7 @@ public final class GalleryListScene extends BaseScene
? R.string.gallery_list_empty_hit_subscription
: R.string.gallery_list_empty_hit);
mHelper.setEmptyString(emptyString);
mHelper.onGetPageData(taskId, CommonOperations.getPagesForFounds(result.founds, 25), mHelper.pgCounter + 1, result.galleryInfoList);
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
}
}
@@ -1474,7 +1525,7 @@ public final class GalleryListScene extends BaseScene
}
}
private static class QsDrawerHolder extends RecyclerView.ViewHolder {
private static class QsDrawerHolder extends AbstractDraggableItemViewHolder {
private final TextView key;
private final ImageView option;
@@ -1519,7 +1570,7 @@ public final class GalleryListScene extends BaseScene
}
}
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> {
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> implements DraggableItemAdapter<QsDrawerHolder> {
private final LayoutInflater mInflater;
@@ -1544,12 +1595,40 @@ public final class GalleryListScene extends BaseScene
}
mUrlBuilder.set(mQuickSearchList.get(position));
mUrlBuilder.setNextGid(0);
mUrlBuilder.setPageIndex(0);
onUpdateUrlBuilder();
mHelper.refresh();
setState(STATE_NORMAL);
closeDrawer(Gravity.RIGHT);
});
holder.itemView.setOnLongClickListener(v -> {
PopupMenu popupMenu = new PopupMenu(requireContext(), holder.option);
popupMenu.inflate(R.menu.quicksearch_option);
popupMenu.show();
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
final QuickSearch quickSearch = mQuickSearchList.get(position);
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.menu_qs_remove) {
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_quick_search_title))
.setMessage(getString(R.string.delete_quick_search_message, quickSearch.name))
.setPositiveButton(R.string.delete, (dialog, which) -> {
EhDB.deleteQuickSearch(quickSearch);
mQuickSearchList.remove(position);
notifyDataSetChanged();
})
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
}
return false;
}
});
return true;
});
} else {
int[] keywords = {11, 12, 13, 15};
int[] toplists = {R.string.toplist_alltime, R.string.toplist_pastyear, R.string.toplist_pastmonth, R.string.toplist_yesterday};
@@ -1561,7 +1640,7 @@ public final class GalleryListScene extends BaseScene
}
mUrlBuilder.setKeyword(String.valueOf(keywords[position]));
mUrlBuilder.setNextGid(0);
mUrlBuilder.setPageIndex(0);
onUpdateUrlBuilder();
mHelper.refresh();
setState(STATE_NORMAL);
@@ -1585,6 +1664,44 @@ public final class GalleryListScene extends BaseScene
public int getItemCount() {
return !mIsTopList ? mQuickSearchList != null ? mQuickSearchList.size() : 0 : 4;
}
@Override
public boolean onCheckCanStartDrag(@NonNull QsDrawerHolder holder, int position, int x, int y) {
return !mIsTopList && x > holder.option.getX() && y > holder.option.getY();
}
@Override
public ItemDraggableRange onGetItemDraggableRange(@NonNull QsDrawerHolder holder, int position) {
return null;
}
@Override
public void onMoveItem(int fromPosition, int toPosition) {
if (fromPosition == toPosition) {
return;
}
if (null == mQuickSearchList) {
return;
}
EhDB.moveQuickSearch(fromPosition, toPosition);
final QuickSearch item = mQuickSearchList.remove(fromPosition);
mQuickSearchList.add(toPosition, item);
}
@Override
public boolean onCheckCanDrop(int draggingPosition, int dropPosition) {
return true;
}
@Override
public void onItemDragStarted(int position) {
notifyDataSetChanged();
}
@Override
public void onItemDragFinished(int fromPosition, int toPosition, boolean result) {
notifyDataSetChanged();
}
}
private abstract class UrlSuggestion extends SearchBar.Suggestion {
@@ -1691,20 +1808,15 @@ public final class GalleryListScene extends BaseScene
}
private class GalleryListHelper extends GalleryInfoContentHelper {
public int pgCounter = 0;
@Override
protected void getPageData(int taskId, int type, int page) {
pgCounter = page;
MainActivity activity = getMainActivity();
if (null == activity || null == mClient || null == mUrlBuilder) {
return;
}
if (page != 0)
mUrlBuilder.setNextGid(minGid);
else
mUrlBuilder.setNextGid(0);
mUrlBuilder.setPageIndex(page);
if (ListUrlBuilder.MODE_IMAGE_SEARCH == mUrlBuilder.getMode()) {
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_IMAGE_SEARCH);
@@ -1774,47 +1886,4 @@ public final class GalleryListScene extends BaseScene
}
}
}
private class GalleryListQSItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final QsDrawerAdapter mAdapter;
public GalleryListQSItemTouchHelperCallback(QsDrawerAdapter adapter) {
mAdapter = adapter;
}
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT);
}
@SuppressLint("NotifyDataSetChanged")
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getBindingAdapterPosition();
int toPosition = target.getBindingAdapterPosition();
if (fromPosition == toPosition) {
return false;
}
if (null == mQuickSearchList) {
return false;
}
EhDB.moveQuickSearch(fromPosition, toPosition);
final QuickSearch item = mQuickSearchList.remove(fromPosition);
mQuickSearchList.add(toPosition, item);
mAdapter.notifyDataSetChanged();
return true;
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
if (mQuickSearchList == null)
return;
int position = viewHolder.getBindingAdapterPosition();
final QuickSearch quickSearch = mQuickSearchList.get(position);
EhDB.deleteQuickSearch(quickSearch);
mQuickSearchList.remove(position);
mAdapter.notifyDataSetChanged();
}
}
}
@@ -36,7 +36,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.slider.Slider;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
@@ -54,6 +53,7 @@ import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.widget.ContentLayout;
import com.hippo.widget.LoadImageView;
import com.hippo.widget.Slider;
import com.hippo.widget.recyclerview.AutoGridLayoutManager;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
@@ -209,7 +209,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return true;
}
int pages = mHelper.getPages();
if (pages > 1 && mHelper.canGoTo()) {
if (pages > 0 && mHelper.canGoTo()) {
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setTitle(R.string.go_to)
.setView(R.layout.dialog_go_to)
@@ -418,8 +418,8 @@ public class GalleryPreviewsScene extends ToolbarScene {
((TextView) ViewUtils.$$(dialog, R.id.start)).setText(String.format(Locale.US, "%d", 1));
((TextView) ViewUtils.$$(dialog, R.id.end)).setText(String.format(Locale.US, "%d", mPages));
mSlider = (Slider) ViewUtils.$$(dialog, R.id.slider);
mSlider.setValueTo(mPages);
mSlider.setValue(mCurrentPage + 1);
mSlider.setRange(1, mPages);
mSlider.setProgress(mCurrentPage + 1);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
dialog.setOnDismissListener(this);
@@ -431,7 +431,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return;
}
int page = (int) (mSlider.getValue() - 1);
int page = mSlider.getProgress() - 1;
if (page >= 0 && page < mPages && mHelper != null) {
mHelper.goTo(page);
if (mDialog != null) {
@@ -36,11 +36,20 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem;
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager;
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.HandlerDrawable;
@@ -197,9 +206,17 @@ public class HistoryScene extends ToolbarScene {
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
mTip.setCompoundDrawables(null, drawable, null, null);
RecyclerViewTouchActionGuardManager guardManager = new RecyclerViewTouchActionGuardManager();
guardManager.setInterceptVerticalScrollingWhileAnimationRunning(true);
guardManager.setEnabled(true);
RecyclerViewSwipeManager swipeManager = new RecyclerViewSwipeManager();
mAdapter = new HistoryAdapter();
mAdapter.setHasStableIds(true);
mAdapter = swipeManager.createWrappedAdapter(mAdapter);
mRecyclerView.setAdapter(mAdapter);
final GeneralItemAnimator animator = new SwipeDismissItemAnimator();
animator.setSupportsChangeAnimations(false);
mRecyclerView.setItemAnimator(animator);
AutoStaggeredGridLayoutManager layoutManager = new AutoStaggeredGridLayoutManager(
0, StaggeredGridLayoutManager.VERTICAL);
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()));
@@ -212,8 +229,9 @@ public class HistoryScene extends ToolbarScene {
int paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v);
MarginItemDecoration decoration = new MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV);
mRecyclerView.addItemDecoration(decoration);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new HistoryItemTouchHelperCallback());
itemTouchHelper.attachToRecyclerView(mRecyclerView);
guardManager.attachRecyclerView(mRecyclerView);
swipeManager.attachRecyclerView(mRecyclerView);
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable handlerDrawable = new HandlerDrawable();
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorPrimary));
@@ -484,7 +502,7 @@ public class HistoryScene extends ToolbarScene {
}
}
private static class HistoryHolder extends RecyclerView.ViewHolder {
private static class HistoryHolder extends AbstractSwipeableItemViewHolder {
public final View card;
public final LoadImageView thumb;
@@ -511,6 +529,12 @@ public class HistoryScene extends ToolbarScene {
pages = itemView.findViewById(R.id.pages);
downloaded = itemView.findViewById(R.id.downloaded);
}
@NonNull
@Override
public View getSwipeableContainerView() {
return card;
}
}
private class MoveDialogHelper implements DialogInterface.OnClickListener {
@@ -546,7 +570,8 @@ public class HistoryScene extends ToolbarScene {
}
}
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder> {
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder>
implements SwipeableItemAdapter<HistoryHolder> {
private final LayoutInflater mInflater;
private final int mListThumbWidth;
@@ -624,30 +649,55 @@ public class HistoryScene extends ToolbarScene {
public int getItemCount() {
return null != mLazyList ? mLazyList.size() : 0;
}
@Override
public int onGetSwipeReactionType(@NonNull HistoryHolder holder, int position, int x, int y) {
return SwipeableItemConstants.REACTION_CAN_SWIPE_LEFT;
}
@Override
public void onSwipeItemStarted(@NonNull HistoryHolder holder, int position) {
}
@Override
public void onSetSwipeBackground(@NonNull HistoryHolder holder, int position, int type) {
}
@Override
public SwipeResultAction onSwipeItem(@NonNull HistoryHolder holder, int position, int result) {
switch (result) {
case SwipeableItemConstants.RESULT_SWIPED_LEFT:
return new SwipeResultActionClear(position);
case SwipeableItemConstants.RESULT_SWIPED_RIGHT:
case SwipeableItemConstants.RESULT_CANCELED:
case SwipeableItemConstants.RESULT_NONE:
case SwipeableItemConstants.RESULT_SWIPED_DOWN:
case SwipeableItemConstants.RESULT_SWIPED_UP:
default:
return new SwipeResultActionDefault();
}
}
}
private class HistoryItemTouchHelperCallback extends ItemTouchHelper.Callback {
private class SwipeResultActionClear extends SwipeResultActionRemoveItem {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(0, ItemTouchHelper.LEFT);
private final int mPosition;
protected SwipeResultActionClear(int position) {
mPosition = position;
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int mPosition = viewHolder.getBindingAdapterPosition();
if (null == mLazyList || mAdapter == null) {
protected void onPerformAction() {
super.onPerformAction();
if (null == mLazyList || null == mAdapter) {
return;
}
HistoryInfo info = mLazyList.get(mPosition);
EhDB.deleteHistoryInfo(info);
updateLazyList();
mAdapter.notifyItemRemoved(mPosition);
mAdapter.notifyDataSetChanged();
updateView(true);
}
}
@@ -87,6 +87,12 @@ public abstract class ToolbarScene extends BaseScene {
}
}
@Override
public boolean needWhiteStatusBar() {
return false;
}
public int getMenuResId() {
return 0;
}
@@ -0,0 +1,229 @@
/*
* 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.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.NumberUtils;
public class AdvanceSearchTable extends LinearLayout {
public static final int SNAME = 0x1;
public static final int STAGS = 0x2;
public static final int SDESC = 0x4;
public static final int STORR = 0x8;
public static final int STO = 0x10;
public static final int SDT1 = 0x20;
public static final int SDT2 = 0x40;
public static final int SH = 0x80;
public static final int SFL = 0x100;
public static final int SFU = 0x200;
public static final int SFT = 0x400;
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_ADVANCE_SEARCH = "advance_search";
private static final String STATE_KEY_MIN_RATING = "min_rating";
private static final String STATE_KEY_PAGE_FROM = "page_from";
private static final String STATE_KEY_PAGE_TO = "page_to";
private CheckBox mSname;
private CheckBox mStags;
private CheckBox mSdesc;
private CheckBox mStorr;
private CheckBox mSto;
private CheckBox mSdt1;
private CheckBox mSdt2;
private CheckBox mSh;
private CheckBox mSr;
private Spinner mMinRating;
private CheckBox mSp;
private EditText mSpf;
private EditText mSpt;
private CheckBox mSfl;
private CheckBox mSfu;
private CheckBox mSft;
public AdvanceSearchTable(Context context) {
super(context);
init(context);
}
public AdvanceSearchTable(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public void init(Context context) {
setOrientation(LinearLayout.VERTICAL);
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_advance_search_table, this);
ViewGroup row0 = (ViewGroup) getChildAt(0);
mSname = (CheckBox) row0.getChildAt(0);
mStags = (CheckBox) row0.getChildAt(1);
ViewGroup row1 = (ViewGroup) getChildAt(1);
mSdesc = (CheckBox) row1.getChildAt(0);
mStorr = (CheckBox) row1.getChildAt(1);
ViewGroup row2 = (ViewGroup) getChildAt(2);
mSto = (CheckBox) row2.getChildAt(0);
mSdt1 = (CheckBox) row2.getChildAt(1);
ViewGroup row3 = (ViewGroup) getChildAt(3);
mSdt2 = (CheckBox) row3.getChildAt(0);
mSh = (CheckBox) row3.getChildAt(1);
ViewGroup row4 = (ViewGroup) getChildAt(4);
mSr = (CheckBox) row4.getChildAt(0);
mMinRating = (Spinner) row4.getChildAt(1);
ViewGroup row5 = (ViewGroup) getChildAt(5);
mSp = (CheckBox) row5.getChildAt(0);
mSpf = (EditText) row5.getChildAt(1);
mSpt = (EditText) row5.getChildAt(3);
ViewGroup row7 = (ViewGroup) getChildAt(7);
mSfl = (CheckBox) row7.getChildAt(0);
mSfu = (CheckBox) row7.getChildAt(1);
mSft = (CheckBox) row7.getChildAt(2);
// Avoid java.lang.IllegalStateException: focus search returned a view that wasn't able to take focus!
mSpt.setOnEditorActionListener((v, actionId, event) -> {
View nextView = v.focusSearch(View.FOCUS_DOWN);
if (nextView != null) {
nextView.requestFocus(View.FOCUS_DOWN);
}
return true;
});
}
public int getAdvanceSearch() {
int advanceSearch = 0;
if (mSname.isChecked()) advanceSearch |= SNAME;
if (mStags.isChecked()) advanceSearch |= STAGS;
if (mSdesc.isChecked()) advanceSearch |= SDESC;
if (mStorr.isChecked()) advanceSearch |= STORR;
if (mSto.isChecked()) advanceSearch |= STO;
if (mSdt1.isChecked()) advanceSearch |= SDT1;
if (mSdt2.isChecked()) advanceSearch |= SDT2;
if (mSh.isChecked()) advanceSearch |= SH;
if (mSfl.isChecked()) advanceSearch |= SFL;
if (mSfu.isChecked()) advanceSearch |= SFU;
if (mSft.isChecked()) advanceSearch |= SFT;
return advanceSearch;
}
public void setAdvanceSearch(int advanceSearch) {
mSname.setChecked(NumberUtils.int2boolean(advanceSearch & SNAME));
mStags.setChecked(NumberUtils.int2boolean(advanceSearch & STAGS));
mSdesc.setChecked(NumberUtils.int2boolean(advanceSearch & SDESC));
mStorr.setChecked(NumberUtils.int2boolean(advanceSearch & STORR));
mSto.setChecked(NumberUtils.int2boolean(advanceSearch & STO));
mSdt1.setChecked(NumberUtils.int2boolean(advanceSearch & SDT1));
mSdt2.setChecked(NumberUtils.int2boolean(advanceSearch & SDT2));
mSh.setChecked(NumberUtils.int2boolean(advanceSearch & SH));
mSfl.setChecked(NumberUtils.int2boolean(advanceSearch & SFL));
mSfu.setChecked(NumberUtils.int2boolean(advanceSearch & SFU));
mSft.setChecked(NumberUtils.int2boolean(advanceSearch & SFT));
}
public int getMinRating() {
int position = mMinRating.getSelectedItemPosition();
if (mSr.isChecked() && position >= 0) {
return position + 2;
} else {
return -1;
}
}
public void setMinRating(int minRating) {
if (minRating >= 2 && minRating <= 5) {
mSr.setChecked(true);
mMinRating.setSelection(minRating - 2);
} else {
mSr.setChecked(false);
}
}
public int getPageFrom() {
if (mSp.isChecked()) {
return NumberUtils.parseIntSafely(mSpf.getText().toString(), -1);
}
return -1;
}
public void setPageFrom(int pageFrom) {
if (pageFrom > 0) {
mSpf.setText(Integer.toString(pageFrom));
mSp.setChecked(true);
} else {
mSp.setChecked(false);
mSpf.setText(null);
}
}
public int getPageTo() {
if (mSp.isChecked()) {
return NumberUtils.parseIntSafely(mSpt.getText().toString(), -1);
}
return -1;
}
public void setPageTo(int pageTo) {
if (pageTo > 0) {
mSpt.setText(Integer.toString(pageTo));
mSp.setChecked(true);
} else {
mSp.setChecked(false);
mSpt.setText(null);
}
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_ADVANCE_SEARCH, getAdvanceSearch());
state.putInt(STATE_KEY_MIN_RATING, getMinRating());
state.putInt(STATE_KEY_PAGE_FROM, getPageFrom());
state.putInt(STATE_KEY_PAGE_TO, getPageTo());
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
setAdvanceSearch(savedState.getInt(STATE_KEY_ADVANCE_SEARCH));
setMinRating(savedState.getInt(STATE_KEY_MIN_RATING));
setPageFrom(savedState.getInt(STATE_KEY_PAGE_FROM));
setPageTo(savedState.getInt(STATE_KEY_PAGE_TO));
}
}
}
@@ -1,189 +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.util.AttributeSet
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.*
import com.google.android.material.textfield.TextInputLayout
import com.hippo.ehviewer.R
import com.hippo.yorozuya.NumberUtils
class AdvanceSearchTable @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
private var mSname: CheckBox
private var mStags: CheckBox
private var mSdesc: CheckBox
private var mStorr: CheckBox
private var mSto: CheckBox
private var mSdt1: CheckBox
private var mSdt2: CheckBox
private var mSh: CheckBox
private var mSr: CheckBox
private var mMinRating: TextInputLayout
private var mSp: CheckBox
private var mSpf: EditText
private var mSpt: EditText
private var mSfl: CheckBox
private var mSfu: CheckBox
private var mSft: CheckBox
private val mArray: Array<String> = context.resources.getStringArray(R.array.search_min_rating)
init {
orientation = VERTICAL
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.widget_advance_search_table, this)
val row0 = getChildAt(0) as ViewGroup
mSname = row0.getChildAt(0) as CheckBox
mStags = row0.getChildAt(1) as CheckBox
val row1 = getChildAt(1) as ViewGroup
mSdesc = row1.getChildAt(0) as CheckBox
mStorr = row1.getChildAt(1) as CheckBox
val row2 = getChildAt(2) as ViewGroup
mSto = row2.getChildAt(0) as CheckBox
mSdt1 = row2.getChildAt(1) as CheckBox
val row3 = getChildAt(3) as ViewGroup
mSdt2 = row3.getChildAt(0) as CheckBox
mSh = row3.getChildAt(1) as CheckBox
val row4 = getChildAt(4) as ViewGroup
mSr = row4.getChildAt(0) as CheckBox
mMinRating = row4.getChildAt(1) as TextInputLayout
val row5 = getChildAt(5) as ViewGroup
mSp = row5.getChildAt(0) as CheckBox
mSpf = row5.getChildAt(1) as EditText
mSpt = row5.getChildAt(3) as EditText
val row7 = getChildAt(7) as ViewGroup
mSfl = row7.getChildAt(0) as CheckBox
mSfu = row7.getChildAt(1) as CheckBox
mSft = row7.getChildAt(2) as CheckBox
mSpt.setOnEditorActionListener { v: TextView, _: Int, _: KeyEvent? ->
val nextView = v.focusSearch(
FOCUS_DOWN
)
nextView?.requestFocus(FOCUS_DOWN)
true
}
mMinRating.isEnabled = mSr.isChecked
mSr.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
mMinRating.isEnabled = isChecked
}
mSpt.isEnabled = mSp.isChecked
mSpf.isEnabled = mSp.isChecked
mSp.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
mSpt.isEnabled = isChecked
mSpf.isEnabled = isChecked
}
}
var advanceSearch: Int
get() {
var advanceSearch = 0
if (mSname.isChecked) advanceSearch = advanceSearch or SNAME
if (mStags.isChecked) advanceSearch = advanceSearch or STAGS
if (mSdesc.isChecked) advanceSearch = advanceSearch or SDESC
if (mStorr.isChecked) advanceSearch = advanceSearch or STORR
if (mSto.isChecked) advanceSearch = advanceSearch or STO
if (mSdt1.isChecked) advanceSearch = advanceSearch or SDT1
if (mSdt2.isChecked) advanceSearch = advanceSearch or SDT2
if (mSh.isChecked) advanceSearch = advanceSearch or SH
if (mSfl.isChecked) advanceSearch = advanceSearch or SFL
if (mSfu.isChecked) advanceSearch = advanceSearch or SFU
if (mSft.isChecked) advanceSearch = advanceSearch or SFT
return advanceSearch
}
set(advanceSearch) {
mSname.isChecked =
NumberUtils.int2boolean(advanceSearch and SNAME)
mStags.isChecked =
NumberUtils.int2boolean(advanceSearch and STAGS)
mSdesc.isChecked =
NumberUtils.int2boolean(advanceSearch and SDESC)
mStorr.isChecked = NumberUtils.int2boolean(advanceSearch and STORR)
mSto.isChecked = NumberUtils.int2boolean(advanceSearch and STO)
mSdt1.isChecked = NumberUtils.int2boolean(advanceSearch and SDT1)
mSdt2.isChecked = NumberUtils.int2boolean(advanceSearch and SDT2)
mSh.isChecked = NumberUtils.int2boolean(advanceSearch and SH)
mSfl.isChecked = NumberUtils.int2boolean(advanceSearch and SFL)
mSfu.isChecked = NumberUtils.int2boolean(advanceSearch and SFU)
mSft.isChecked = NumberUtils.int2boolean(advanceSearch and SFT)
}
var minRating: Int
get() {
val position = mArray.indexOf(mMinRating.editText!!.text.toString())
return if (mSr.isChecked && position >= 0) {
position + 2
} else {
-1
}
}
set(minRating) {
if (minRating in 2..5) {
mSr.isChecked = true
(mMinRating.editText!! as AutoCompleteTextView).setText(
mArray[minRating - 2],
false
)
} else {
mSr.isChecked = false
}
}
var pageFrom: Int
get() = if (mSp.isChecked) {
NumberUtils.parseIntSafely(mSpf.text.toString(), -1)
} else -1
set(pageFrom) {
if (pageFrom > 0) {
mSpf.setText(pageFrom.toString())
mSp.isChecked = true
} else {
mSp.isChecked = false
mSpf.text = null
}
}
var pageTo: Int
get() = if (mSp.isChecked) {
NumberUtils.parseIntSafely(mSpt.text.toString(), -1)
} else -1
set(pageTo) {
if (pageTo > 0) {
mSpt.setText(pageTo.toString())
mSp.isChecked = true
} else {
mSp.isChecked = false
}
}
companion object {
const val SNAME = 0x1
const val STAGS = 0x2
const val SDESC = 0x4
const val STORR = 0x8
const val STO = 0x10
const val SDT1 = 0x20
const val SDT2 = 0x40
const val SH = 0x80
const val SFL = 0x100
const val SFU = 0x200
const val SFT = 0x400
}
}
@@ -53,6 +53,11 @@ public class FixedThumb extends LoadImageView {
a.recycle();
}
public void setFix(float minAspect, float maxAspect) {
this.minAspect = minAspect;
this.maxAspect = maxAspect;
}
@Override
public void onPreSetImageDrawable(Drawable drawable, boolean isTarget) {
if (alwaysCutAndScale) {
@@ -31,14 +31,8 @@ import java.util.List;
import java.util.Map;
public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelper<GalleryInfo> {
public int maxGid = -1;
public int minGid = -1;
private static final String KEY_DATA_MAP = "data_map";
private static final String KEY_MAX_GID = "max_gid";
private static final String KEY_MIN_GID = "min_gid";
public static final String KEY_NEXT_PAGE = "next_page";
private final FavouriteStatusRouter.Listener listener;
@SuppressLint("UseSparseArrays")
private Map<Long, GalleryInfo> map = new HashMap<>();
@@ -65,12 +59,6 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
@Override
protected void onAddData(List<GalleryInfo> data) {
for (GalleryInfo info : data) {
if (maxGid == -1)
maxGid = (int) info.gid;
if (minGid == -1)
minGid = (int) info.gid;
maxGid = (int) Math.max(maxGid, info.gid);
minGid = (int) Math.min(minGid, info.gid);
map.put(info.gid, info);
}
}
@@ -90,8 +78,6 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
@Override
protected void onClearData() {
map.clear();
maxGid = -1;
minGid = -1;
}
@Override
@@ -102,8 +88,6 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
FavouriteStatusRouter router = EhApplication.getFavouriteStatusRouter();
int id = router.saveDataMap(map);
bundle.putInt(KEY_DATA_MAP, id);
bundle.putInt(KEY_MIN_GID, minGid);
bundle.putInt(KEY_MAX_GID, maxGid);
return bundle;
}
@@ -120,8 +104,6 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
this.map = map;
}
}
minGid = bundle.getInt(KEY_MIN_GID);
maxGid = bundle.getInt(KEY_MAX_GID);
return super.restoreInstanceState(state);
}
@@ -1,13 +0,0 @@
package com.hippo.ehviewer.widget
import android.content.Context
import android.util.AttributeSet
import com.hippo.ehviewer.Settings
class ResizeableFixedThumb @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : FixedThumb(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(Settings.getListThumbSize() * 2, Settings.getListThumbSize() * 3)
}
}
@@ -0,0 +1,27 @@
/*
* 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.scene;
import android.content.Context;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
public interface TransitionHelper {
boolean onTransition(Context context, FragmentTransaction transaction, Fragment exit, Fragment enter);
}
@@ -102,11 +102,27 @@ public class ContentLayout extends FrameLayout {
mFastScroller = mContentView.findViewById(R.id.fast_scroller);
mRecyclerView = mRefreshLayout.findViewById(R.id.recycler_view);
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(context.getTheme(), androidx.appcompat.R.attr.colorPrimary));
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable drawable = new HandlerDrawable();
drawable.setColor(ResourcesKt.resolveColor(context.getTheme(), androidx.appcompat.R.attr.colorPrimary));
mFastScroller.setHandlerDrawable(drawable);
mRefreshLayout.setColorSchemeResources(
R.color.loading_indicator_red,
R.color.loading_indicator_purple,
R.color.loading_indicator_blue,
R.color.loading_indicator_cyan,
R.color.loading_indicator_green,
R.color.loading_indicator_yellow);
Resources resources = getResources();
mBottomProgress.setIndicatorColor(
resources.getColor(R.color.loading_indicator_red),
resources.getColor(R.color.loading_indicator_blue),
resources.getColor(R.color.loading_indicator_green),
resources.getColor(R.color.loading_indicator_orange));
mBottomProgress.setIndeterminateAnimationType(LinearProgressIndicator.INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS);
mRecyclerViewOriginBottom = mRecyclerView.getPaddingBottom();
mFastScrollerOriginBottom = mFastScroller.getPaddingBottom();
}
@@ -451,22 +467,48 @@ public class ContentLayout extends FrameLayout {
switch (mCurrentTaskType) {
case TYPE_REFRESH:
mStartPage = 0;
mEndPage = 1;
mPages = pages;
mNextPage = nextPage;
mPageDivider.clear();
mPageDivider.add(data.size());
if (data.isEmpty()) {
mData.clear();
onClearData();
notifyDataSetChanged();
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showEmptyString();
if (true || mEndPage >= mPages) { // Not found
// Ui change, show empty string
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showEmptyString();
} else {
// Ui change, show progress bar
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showProgressBar();
// Get next page
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS;
mCurrentTaskPage = mEndPage;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
}
} else {
mData.clear();
onClearData();
mData.addAll(data);
onAddData(data);
notifyDataSetChanged();
// Ui change, show content
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showContent();
// RecyclerView scroll
if (mRecyclerView.isAttachedToWindow()) {
mRecyclerView.stopScroll();
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), 0, 0);
@@ -487,24 +529,33 @@ public class ContentLayout extends FrameLayout {
// assert mStartPage >= 0
if (data.isEmpty()) {
// OK, that's all
if (mData.isEmpty()) {
// Ui change, show empty string
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showEmptyString();
} else {
// Ui change, show content
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showContent();
if (true || mStartPage <= 0) { // OK, that's all
if (mData.isEmpty()) {
// Ui change, show empty string
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showEmptyString();
} else {
// Ui change, show content
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showContent();
if (mCurrentTaskType == TYPE_PRE_PAGE && mRecyclerView.isAttachedToWindow()) {
// RecyclerView scroll, to top
mRecyclerView.stopScroll();
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), 0, 0);
onScrollToPosition(0);
if (mCurrentTaskType == TYPE_PRE_PAGE && mRecyclerView.isAttachedToWindow()) {
// RecyclerView scroll, to top
mRecyclerView.stopScroll();
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), 0, 0);
onScrollToPosition(0);
}
}
} else {
// Keep UI
// Get previous
mCurrentTaskId = mIdGenerator.nextId();
// Keep mCurrentTaskType
mCurrentTaskPage = mStartPage - 1;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
}
} else {
mData.addAll(0, data);
@@ -541,24 +592,33 @@ public class ContentLayout extends FrameLayout {
mPages = Math.max(mEndPage, pages);
if (data.isEmpty()) {
// OK, that's all
if (mData.isEmpty()) {
// Ui change, show empty string
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showEmptyString();
} else {
// Ui change, show content
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showContent();
if (true || mEndPage >= mPages) { // OK, that's all
if (mData.isEmpty()) {
// Ui change, show empty string
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showEmptyString();
} else {
// Ui change, show content
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showContent();
if (mCurrentTaskType == TYPE_NEXT_PAGE && mRecyclerView.isAttachedToWindow()) {
// RecyclerView scroll
mRecyclerView.stopScroll();
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), oldDataSize, 0);
onScrollToPosition(oldDataSize);
if (mCurrentTaskType == TYPE_NEXT_PAGE && mRecyclerView.isAttachedToWindow()) {
// RecyclerView scroll
mRecyclerView.stopScroll();
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), oldDataSize, 0);
onScrollToPosition(oldDataSize);
}
}
} else {
// Keep UI
// Get next page
mCurrentTaskId = mIdGenerator.nextId();
// Keep mCurrentTaskType
mCurrentTaskPage = mEndPage;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
}
} else {
mData.addAll(data);
@@ -595,11 +655,23 @@ public class ContentLayout extends FrameLayout {
onClearData();
notifyDataSetChanged();
// Not found
// Ui change, show empty string
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showEmptyString();
if (true || mEndPage >= mPages) { // Not found
// Ui change, show empty string
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showEmptyString();
} else {
// Ui change, show progress bar
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
showProgressBar();
// Get next page
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS;
mCurrentTaskPage = mEndPage;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
}
} else {
mData.clear();
onClearData();
@@ -748,12 +820,6 @@ public class ContentLayout extends FrameLayout {
}
private void doRefresh() {
mStartPage = 0;
mEndPage = 1;
mPages = 0;
mPageDivider.clear();
mData.clear();
onClearData();
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_REFRESH;
mCurrentTaskPage = 0;
@@ -34,6 +34,7 @@ import androidx.core.content.ContextCompat;
import com.hippo.conaco.Conaco;
import com.hippo.conaco.ConacoTask;
import com.hippo.conaco.DataContainer;
import com.hippo.conaco.Unikery;
import com.hippo.drawable.PreciselyClipDrawable;
import com.hippo.ehviewer.EhApplication;
@@ -55,6 +56,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
private Conaco<ImageBitmap> mConaco;
private String mKey;
private String mUrl;
private DataContainer mContainer;
private boolean mUseNetwork;
private int mOffsetX = Integer.MIN_VALUE;
private int mOffsetY = Integer.MIN_VALUE;
@@ -107,7 +109,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
if (mFailed) {
onFailure();
} else if (mConaco.isLoading(this)) {
load(mKey, mUrl, mUseNetwork);
load(mKey, mUrl, mContainer, mUseNetwork);
}
}
}
@@ -207,11 +209,15 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
}
public void load(String key, String url) {
load(key, url, true);
load(key, url, null, true);
}
public void load(String key, String url, boolean useNetwork) {
if (url == null || (key == null)) {
load(key, url, null, useNetwork);
}
public void load(String key, String url, DataContainer container, boolean useNetwork) {
if (url == null || (key == null && container == null)) {
return;
}
@@ -220,12 +226,14 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
mKey = key;
mUrl = url;
mContainer = container;
mUseNetwork = useNetwork;
ConacoTask.Builder<ImageBitmap> builder = new ConacoTask.Builder<ImageBitmap>()
.setUnikery(this)
.setKey(key)
.setUrl(url)
.setDataContainer(container)
.setUseNetwork(useNetwork);
mConaco.load(builder);
}
@@ -247,6 +255,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
mConaco.cancel(this);
mKey = null;
mUrl = null;
mContainer = null;
clearDrawable();
}
@@ -317,6 +326,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
// Can't retry, so release
mKey = null;
mUrl = null;
mContainer = null;
}
}
@@ -353,12 +363,12 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
@Override
public void onClick(@NonNull View v) {
load(mKey, mUrl, true);
load(mKey, mUrl, mContainer, true);
}
@Override
public boolean onLongClick(@NonNull View v) {
load(mKey, mUrl, true);
load(mKey, mUrl, mContainer, true);
return true;
}
@@ -0,0 +1,515 @@
/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.widget;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.graphics.drawable.DrawableCompat;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.SimpleHandler;
public class Slider extends View {
private static final char[] CHARACTERS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
private static final int BUBBLE_WIDTH = 26;
private static final int BUBBLE_HEIGHT = 32;
private final RectF mLeftRectF = new RectF();
private final RectF mRightRectF = new RectF();
private final int[] mTemp = new int[2];
private Context mContext;
private Paint mPaint;
private Paint mBgPaint;
private PopupWindow mPopup;
private BubbleView mBubble;
private int mStart;
private int mEnd;
private int mProgress;
private float mPercent;
private int mDrawProgress;
private float mDrawPercent;
private int mTargetProgress;
private float mThickness;
private float mRadius;
private float mCharWidth;
private float mCharHeight;
private int mBubbleWidth;
private int mBubbleHeight;
private int mBubbleMinWidth;
private int mBubbleMinHeight;
private int mPopupX;
private int mPopupY;
private int mPopupWidth;
private boolean mReverse = false;
private boolean mShowBubble;
private float mDrawBubbleScale = 0f;
private ValueAnimator mProgressAnimation;
private ValueAnimator mBubbleScaleAnimation;
private OnSetProgressListener mListener;
private CheckForShowBubble mCheckForShowBubble;
public Slider(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public Slider(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@SuppressWarnings("deprecation")
private void init(Context context, AttributeSet attrs) {
mContext = context;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextAlign(Paint.Align.CENTER);
Resources resources = context.getResources();
mBubbleMinWidth = resources.getDimensionPixelOffset(R.dimen.slider_bubble_width);
mBubbleMinHeight = resources.getDimensionPixelOffset(R.dimen.slider_bubble_height);
mBubble = new BubbleView(context, textPaint);
mBubble.setScaleX(0.0f);
mBubble.setScaleY(0.0f);
RelativeLayout relativeLayout = new RelativeLayout(context);
relativeLayout.addView(mBubble);
relativeLayout.setBackgroundDrawable(null);
mPopup = new PopupWindow(relativeLayout);
mPopup.setOutsideTouchable(false);
mPopup.setTouchable(false);
mPopup.setFocusable(false);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Slider);
textPaint.setColor(a.getColor(R.styleable.Slider_textColor, Color.WHITE));
textPaint.setTextSize(a.getDimensionPixelSize(R.styleable.Slider_textSize, 12));
updateTextSize();
setRange(a.getInteger(R.styleable.Slider_start, 0), a.getInteger(R.styleable.Slider_end, 0));
setProgress(a.getInteger(R.styleable.Slider_slider_progress, 0));
mThickness = a.getDimension(R.styleable.Slider_thickness, 2);
mRadius = a.getDimension(R.styleable.Slider_radius, 6);
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
a.recycle();
mProgressAnimation = new ValueAnimator();
mProgressAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
mProgressAnimation.addUpdateListener(animation -> {
float value = (Float) animation.getAnimatedValue();
mDrawPercent = value;
mDrawProgress = Math.round(MathUtils.lerp((float) mStart, mEnd, value));
updateBubblePosition();
mBubble.setProgress(mDrawProgress);
invalidate();
});
mBubbleScaleAnimation = new ValueAnimator();
mBubbleScaleAnimation.addUpdateListener(animation -> {
float value = (Float) animation.getAnimatedValue();
mDrawBubbleScale = value;
mBubble.setScaleX(value);
mBubble.setScaleY(value);
invalidate();
});
}
private void updateTextSize() {
int length = CHARACTERS.length;
float[] widths = new float[length];
mPaint.getTextWidths(CHARACTERS, 0, length, widths);
mCharWidth = 0.0f;
for (float f : widths) {
mCharWidth = Math.max(mCharWidth, f);
}
Paint.FontMetrics fm = mPaint.getFontMetrics();
mCharHeight = fm.bottom - fm.top;
}
private void updateBubbleSize() {
int oldWidth = mBubbleWidth;
int oldHeight = mBubbleHeight;
mBubbleWidth = (int) Math.max(mBubbleMinWidth,
Integer.toString(mEnd).length() * mCharWidth + LayoutUtils.dp2pix(mContext, 8));
mBubbleHeight = (int) Math.max(mBubbleMinHeight,
mCharHeight + LayoutUtils.dp2pix(mContext, 8));
if (oldWidth != mBubbleWidth && oldHeight != mBubbleHeight) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mBubble.getLayoutParams();
lp.width = mBubbleWidth;
lp.height = mBubbleHeight;
mBubble.setLayoutParams(lp);
}
}
private void updatePopup() {
int width = getWidth();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
getLocationInWindow(mTemp);
mPopupWidth = (int) (width - mRadius - mRadius + mBubbleWidth);
int popupHeight = mBubbleHeight;
mPopupX = (int) (mTemp[0] + mRadius - (mBubbleWidth / 2));
mPopupY = (int) (mTemp[1] - popupHeight + paddingTop +
((getHeight() - paddingTop - paddingBottom) / 2) -
mRadius - LayoutUtils.dp2pix(mContext, 2));
mPopup.update(mPopupX, mPopupY, mPopupWidth, popupHeight, false);
}
private void updateBubblePosition() {
float x = ((mPopupWidth - mBubbleWidth) * (mReverse ? (1.0f - mDrawPercent) : mDrawPercent));
mBubble.setX(x);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updatePopup();
updateBubblePosition();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPopup.showAtLocation(this, Gravity.TOP | Gravity.LEFT, mPopupX, mPopupY);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mPopup.dismiss();
}
private void startProgressAnimation(float percent) {
float currentValue;
if (mProgressAnimation.isRunning()) {
mProgressAnimation.setCurrentPlayTime(mProgressAnimation.getCurrentPlayTime());
Object value = mProgressAnimation.getAnimatedValue();
if (value instanceof Float) {
currentValue = (float) value;
} else {
currentValue = mDrawPercent;
}
} else {
currentValue = mDrawPercent;
}
mProgressAnimation.cancel();
mProgressAnimation.setFloatValues(currentValue, percent);
mProgressAnimation.setDuration(Math.min(500, (long) (50 * getWidth() * Math.abs(currentValue - percent))));
mProgressAnimation.start();
}
private void startShowBubbleAnimation() {
mBubbleScaleAnimation.cancel();
mBubbleScaleAnimation.setFloatValues(mDrawBubbleScale, 1.0f);
mBubbleScaleAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
mBubbleScaleAnimation.setDuration((long) (300 * Math.abs(mDrawBubbleScale - 1.0f)));
mBubbleScaleAnimation.start();
}
private void startHideBubbleAnimation() {
mBubbleScaleAnimation.cancel();
mBubbleScaleAnimation.setFloatValues(mDrawBubbleScale, 0.0f);
mBubbleScaleAnimation.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR);
mBubbleScaleAnimation.setDuration((long) (300 * Math.abs(mDrawBubbleScale - 0.0f)));
mBubbleScaleAnimation.start();
}
public void setColor(int color) {
mPaint.setColor(color);
mBubble.setColor(color);
invalidate();
}
public void setRange(int start, int end) {
mStart = start;
mEnd = end;
setProgress(mProgress);
updateBubbleSize();
}
public int getProgress() {
return mProgress;
}
public void setProgress(int progress) {
progress = MathUtils.clamp(progress, mStart, mEnd);
int oldProgress = mProgress;
if (mProgress != progress) {
mProgress = progress;
mPercent = MathUtils.delerp(mStart, mEnd, mProgress);
mTargetProgress = progress;
if (mProgressAnimation == null) {
// For init
mDrawPercent = mPercent;
mDrawProgress = mProgress;
updateBubblePosition();
mBubble.setProgress(mDrawProgress);
} else {
startProgressAnimation(mPercent);
}
invalidate();
}
if (mListener != null) {
mListener.onSetProgress(this, progress, oldProgress, false, true);
}
}
public void setReverse(boolean reverse) {
if (mReverse != reverse) {
mReverse = reverse;
invalidate();
}
}
public void setOnSetProgressListener(OnSetProgressListener listener) {
mListener = listener;
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
int width = getWidth();
int height = getHeight();
if (width < LayoutUtils.dp2pix(mContext, 24)) {
canvas.drawRect(0, 0, width, getHeight(), mPaint);
} else {
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
float thickness = mThickness;
float radius = mRadius;
float halfThickness = thickness / 2;
int saved = canvas.save();
canvas.translate(0, paddingTop + ((height - paddingTop - paddingBottom) / 2));
float currentX = paddingLeft + radius + (width - radius - radius - paddingLeft - paddingRight) *
(mReverse ? (1.0f - mDrawPercent) : mDrawPercent);
mLeftRectF.set(paddingLeft + radius, -halfThickness, currentX, halfThickness);
mRightRectF.set(currentX, -halfThickness, width - paddingRight - radius, halfThickness);
// Draw bar
if (mReverse) {
canvas.drawRect(mRightRectF, mPaint);
canvas.drawRect(mLeftRectF, mBgPaint);
} else {
canvas.drawRect(mLeftRectF, mPaint);
canvas.drawRect(mRightRectF, mBgPaint);
}
// Draw controller
float scale = 1.0f - mDrawBubbleScale;
if (scale != 0.0f) {
canvas.scale(scale, scale, currentX, 0);
canvas.drawCircle(currentX, 0, radius, mPaint);
}
canvas.restoreToCount(saved);
}
}
private void setShowBubble(boolean showBubble) {
if (mShowBubble != showBubble) {
mShowBubble = showBubble;
if (showBubble) {
startShowBubbleAnimation();
} else {
startHideBubbleAnimation();
}
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mListener != null) {
if (action == MotionEvent.ACTION_DOWN) {
mListener.onFingerDown();
} else if (action == MotionEvent.ACTION_UP) {
mListener.onFingerUp();
}
}
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
float radius = mRadius;
float x = event.getX();
int progress = Math.round(MathUtils.lerp((float) mStart, (float) mEnd,
MathUtils.clamp((mReverse ? (getWidth() - paddingLeft - radius - x) : (x - radius - paddingLeft)) /
(getWidth() - radius - radius - paddingLeft - paddingRight), 0.0f, 1.0f)));
float percent = MathUtils.delerp(mStart, mEnd, progress);
// ACTION_CANCEL not changed
if (action == MotionEvent.ACTION_CANCEL) {
progress = mProgress;
percent = mPercent;
}
if (mTargetProgress != progress) {
mTargetProgress = progress;
startProgressAnimation(percent);
if (mListener != null) {
mListener.onSetProgress(this, mProgress, progress, true, false);
}
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
SimpleHandler.getInstance().removeCallbacks(mCheckForShowBubble);
setShowBubble(false);
} else if (action == MotionEvent.ACTION_DOWN) {
if (mCheckForShowBubble == null) {
mCheckForShowBubble = new CheckForShowBubble();
}
SimpleHandler.getInstance().postDelayed(mCheckForShowBubble, ViewConfiguration.getTapTimeout());
}
if (action == MotionEvent.ACTION_UP) {
int oldProgress = mProgress;
if (mProgress != progress) {
mProgress = progress;
mPercent = mDrawPercent;
}
if (mListener != null) {
mListener.onSetProgress(this, progress, oldProgress, true, true);
}
}
break;
}
return true;
}
public interface OnSetProgressListener {
void onSetProgress(Slider slider, int newProgress, int oldProgress, boolean byUser, boolean confirm);
void onFingerDown();
void onFingerUp();
}
@SuppressLint("ViewConstructor")
private static class BubbleView extends AppCompatImageView {
private static final float TEXT_CENTER = (float) BUBBLE_WIDTH / 2.0f / BUBBLE_HEIGHT;
private final Drawable mDrawable;
private final Paint mTextPaint;
private final Rect mRect = new Rect();
private String mProgressStr = "";
public BubbleView(Context context, Paint paint) {
super(context);
setImageResource(R.drawable.v_slider_bubble);
mDrawable = DrawableCompat.wrap(getDrawable());
setImageDrawable(mDrawable);
mTextPaint = paint;
}
public void setColor(int color) {
DrawableCompat.setTint(mDrawable, color);
}
public void setProgress(int progress) {
String str = Integer.toString(progress);
if (!str.equals(mProgressStr)) {
mProgressStr = str;
mTextPaint.getTextBounds(str, 0, str.length(), mRect);
invalidate();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setPivotX(w / 2);
setPivotY(h);
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int x = width / 2;
int y = (int) ((height * TEXT_CENTER) + (mRect.height() / 2));
canvas.drawText(mProgressStr, x, y, mTextPaint);
}
}
private final class CheckForShowBubble implements Runnable {
@Override
public void run() {
setShowBubble(true);
}
}
}
@@ -21,49 +21,18 @@
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.7425"
android:scaleY="0.7425"
android:translateX="30.68"
android:translateY="30.68">
<path
android:pathData="M11,9H34"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#FBBD06"
android:strokeLineCap="round"/>
<path
android:pathData="M11,54H23"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#FBBD06"
android:strokeLineCap="round"/>
<path
android:pathData="M11,9L11,54"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#35AA53"
android:strokeLineCap="round"/>
<path
android:pathData="M11,31L52,31"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#FBBD06"
android:strokeLineCap="round"/>
<path
android:pathData="M52,31L52,54"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#4586F3"
android:strokeLineCap="round"/>
<path
android:pathData="M34,21L34,54"
android:strokeWidth="8"
android:fillColor="#00000000"
android:strokeColor="#EB4334"
android:strokeLineCap="round"/>
<path
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z"
android:fillColor="#FBBD06"
android:fillType="evenOdd"/>
</group>
<group
android:scaleX="0.37947997"
android:scaleY="0.37947997"
android:translateX="35.1"
android:translateY="39.584126">
<path
android:fillColor="@color/ic_launcher_foreground"
android:fillType="evenOdd"
android:pathData="M39.893,68.066L39.893,75.977L0,75.977L0,4.59L39.893,4.59L39.893,12.5L8.984,12.5L8.984,34.766L38.086,34.766L38.086,42.578L8.984,42.578L8.984,68.066L39.893,68.066Z" />
<path
android:fillColor="@color/ic_launcher_foreground"
android:fillType="evenOdd"
android:pathData="M99.609,75.977L90.918,75.977L90.918,41.699A23.436,23.436 0,0 0,90.662 38.109Q90.055,34.207 88.013,31.958Q85.629,29.334 81.028,28.863A20.84,20.84 0,0 0,78.906 28.76A23.134,23.134 0,0 0,74.45 29.16Q71.926,29.656 69.994,30.767A11.122,11.122 0,0 0,66.968 33.301A13.32,13.32 0,0 0,64.756 37.276Q63.399,40.971 63.213,46.495A53.517,53.517 0,0 0,63.184 48.291L63.184,75.977L54.395,75.977L54.395,0L63.184,0L63.184,22.266A66.935,66.935 0,0 1,63.115 25.383Q63.043,26.914 62.897,28.251A33.547,33.547 0,0 1,62.695 29.785L63.281,29.785A16.11,16.11 0,0 1,69.394 24.035A19.448,19.448 0,0 1,70.068 23.682A20.88,20.88 0,0 1,77.292 21.622A25.763,25.763 0,0 1,79.98 21.484A32.643,32.643 0,0 1,85.483 21.917Q91.249,22.905 94.678,26.147Q98.472,29.735 99.347,36.631A35.274,35.274 0,0 1,99.609 41.064L99.609,75.977Z" />
</group>
</vector>
@@ -1,191 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or
~ modify it under the terms of the GNU General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License, or (at your option) any later version.
~
~ EhViewer is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/actions"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="@dimen/keyline_margin">
<TextView
android:id="@+id/newerVersion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center"
android:padding="8dp"
android:text="@string/newer_version_avaliable"
android:visibility="gone" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/keyline_margin"
android:paddingRight="@dimen/keyline_margin">
<LinearLayout
android:id="@+id/rate"
android:layout_width="484dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<androidx.appcompat.widget.AppCompatRatingBar
android:id="@+id/rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:isIndicator="true"
android:theme="@style/RatingBarTheme" />
<TextView
android:id="@+id/rating_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="0dp"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<HorizontalScrollView
android:id="@+id/actions_scroll_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clipToPadding="false"
android:paddingLeft="36dp"
android:paddingRight="@dimen/keyline_margin"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:divider="@drawable/spacer_keyline"
android:orientation="horizontal"
android:showDividers="middle">
<FrameLayout
android:id="@+id/heart_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:nextFocusUp="@+id/download">
<TextView
android:id="@+id/heart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="4dp"
android:gravity="center_horizontal"
android:text="@string/favorited"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/heart_outline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="4dp"
android:gravity="center_horizontal"
android:text="@string/not_favorited"
android:textColor="?android:attr/textColorPrimary" />
</FrameLayout>
<TextView
android:id="@+id/share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusUp="@+id/download"
android:text="@string/share"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/torrent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusUp="@+id/download"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/archive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusUp="@+id/download"
android:text="@string/archive"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/similar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusUp="@+id/download"
android:text="@string/similar_gallery"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/search_cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:drawablePadding="4dp"
android:focusable="true"
android:gravity="center_horizontal"
android:nextFocusRight="@+id/search_cover"
android:nextFocusUp="@+id/download"
android:text="@string/search_cover"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</LinearLayout>
@@ -1,263 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or
~ modify it under the terms of the GNU General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License, or (at your option) any later version.
~
~ EhViewer is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<View
android:id="@+id/color_bg"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:elevation="4dp"
app:fitsSystemWindowsInsets="top" />
<Space
android:layout_width="match_parent"
android:layout_height="28dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/title"
style="@style/TextAppearance.Material3.HeadlineSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
android:ellipsize="end"
android:maxLines="5" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="540dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginTop="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
android:layout_marginBottom="@dimen/keyline_margin"
app:fitsSystemWindowsInsets="top">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/header_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.hippo.widget.LoadImageView
android:id="@+id/thumb"
android:layout_width="256dp"
android:layout_height="@dimen/gallery_detail_thumb_height" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/info"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/keyline_margin"
android:clickable="true"
android:focusable="true"
android:nextFocusRight="@+id/read"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="@+id/language"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/size"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="right"
android:singleLine="true"
app:layout_constraintBaseline_toBaselineOf="@id/language"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/language" />
<TextView
android:id="@+id/favoredTimes"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="left"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/language" />
<TextView
android:id="@+id/pages"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="right"
android:singleLine="true"
app:layout_constraintBaseline_toBaselineOf="@id/favoredTimes"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/favoredTimes" />
<TextView
android:id="@+id/posted"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="left"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/favoredTimes" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.chip.Chip
android:id="@+id/uploader"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/keyline_margin"
android:ellipsize="end"
android:nextFocusRight="@+id/read"
android:nextFocusDown="@+id/rate"
android:singleLine="true"
android:textAllCaps="true"
app:chipIcon="@drawable/file_upload_black_24dp"
app:ensureMinTouchTargetSize="true"
app:layout_constraintBottom_toBottomOf="@id/thumb_card"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/category"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/keyline_margin"
android:ellipsize="end"
android:nextFocusRight="@+id/read"
android:singleLine="true"
android:textAllCaps="true"
app:chipIcon="@drawable/ic_baseline_label_24"
app:ensureMinTouchTargetSize="true"
app:layout_constraintBottom_toTopOf="@id/uploader"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/action_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/keyline_margin"
android:layout_marginRight="@dimen/keyline_margin"
android:orientation="vertical">
<Button
android:id="@+id/read"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:insetLeft="40dp"
android:insetRight="40dp"
android:insetTop="16dp"
android:text="@string/read">
<requestFocus />
</Button>
<Button
android:id="@+id/download"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:nextFocusDown="@+id/torrent"
android:focusable="true"
android:insetLeft="40dp"
android:insetTop="16dp"
android:insetRight="40dp" />
<android.widget.Space
android:layout_width="match_parent"
android:layout_height="80dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
@@ -1,20 +1,17 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~ 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
~
~ EhViewer is free software: you can redistribute it and/or
~ modify it under the terms of the GNU General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License, or (at your option) any later version.
~
~ EhViewer is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -1,20 +1,17 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~ 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
~
~ EhViewer is free software: you can redistribute it and/or
~ modify it under the terms of the GNU General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License, or (at your option) any later version.
~
~ EhViewer is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -39,10 +36,10 @@
<ImageView
android:layout_width="257dp"
android:layout_height="match_parent"
android:layout_height="518dp"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@drawable/ic_launcher_foreground" />
app:srcCompat="@mipmap/ic_launcher" />
</LinearLayout>
@@ -1,20 +1,17 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~ 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
~
~ EhViewer is free software: you can redistribute it and/or
~ modify it under the terms of the GNU General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License, or (at your option) any later version.
~
~ EhViewer is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
@@ -1,20 +1,17 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~ 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
~
~ EhViewer is free software: you can redistribute it and/or
~ modify it under the terms of the GNU General Public License as
~ published by the Free Software Foundation, either version 3 of the
~ License, or (at your option) any later version.
~
~ EhViewer is distributed in the hope that it will be useful, but
~ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
~ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+3 -4
View File
@@ -28,14 +28,13 @@
android:layout_gravity="center_vertical"
android:gravity="center_horizontal" />
<com.google.android.material.slider.Slider
<com.hippo.widget.Slider
android:id="@+id/slider"
style="@style/Slider"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
android:stepSize="1"
android:valueFrom="1" />
android:layout_weight="1" />
<TextView
android:id="@+id/end"
@@ -64,24 +64,21 @@
android:layout_marginBottom="@dimen/keyline_margin"
app:fitsSystemWindowsInsets="top">
<androidx.constraintlayout.widget.ConstraintLayout
<RelativeLayout
android:id="@+id/header_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.FixedThumb
<com.hippo.widget.LoadImageView
android:id="@+id/thumb"
android:layout_width="@dimen/gallery_detail_thumb_width"
android:layout_height="@dimen/gallery_detail_thumb_height"
app:alwaysCutAndScale="true"/>
android:layout_height="@dimen/gallery_detail_thumb_height" />
</com.google.android.material.card.MaterialCardView>
@@ -90,82 +87,69 @@
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/keyline_margin"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
<TableLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="@+id/language"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TableRow>
<TextView
android:id="@+id/size"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="end"
android:singleLine="true"
app:layout_constraintBaseline_toBaselineOf="@id/language"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/language" />
<TextView
android:id="@+id/language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:singleLine="true" />
<TextView
android:id="@+id/size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:singleLine="true" />
<TextView
android:id="@+id/favoredTimes"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="start"
android:singleLine="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/language" />
</TableRow>
<TextView
android:id="@+id/pages"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:gravity="end"
android:singleLine="true"
app:layout_constraintBaseline_toBaselineOf="@id/favoredTimes"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@id/favoredTimes" />
<TableRow android:paddingTop="8dp">
<TextView
android:id="@+id/favoredTimes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:singleLine="true" />
<TextView
android:id="@+id/posted"
style="@style/TextAppearance.Material3.LabelMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/favoredTimes" />
<TextView
android:id="@+id/pages"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:singleLine="true" />
</TableRow>
</androidx.constraintlayout.widget.ConstraintLayout>
<TableRow android:paddingTop="8dp">
<TextView
android:id="@+id/posted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:singleLine="true" />
</TableRow>
</TableLayout>
</com.google.android.material.card.MaterialCardView>
@@ -174,31 +158,30 @@
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/thumb_card"
android:layout_alignParentEnd="true"
android:layout_marginEnd="@dimen/keyline_margin"
android:ellipsize="end"
android:singleLine="true"
android:textAllCaps="true"
app:chipIcon="@drawable/file_upload_black_24dp"
app:ensureMinTouchTargetSize="true"
app:layout_constraintBottom_toBottomOf="@id/thumb_card"
app:layout_constraintEnd_toEndOf="parent" />
app:ensureMinTouchTargetSize="true" />
<com.google.android.material.chip.Chip
android:id="@+id/category"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/uploader"
android:layout_alignParentEnd="true"
android:layout_marginEnd="@dimen/keyline_margin"
app:chipSpacingHorizontal="8dp"
android:ellipsize="end"
android:singleLine="true"
android:textAllCaps="true"
app:chipIcon="@drawable/ic_baseline_label_24"
app:ensureMinTouchTargetSize="true"
app:layout_constraintBottom_toTopOf="@id/uploader"
app:layout_constraintEnd_toEndOf="parent" />
app:ensureMinTouchTargetSize="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
@@ -215,17 +198,19 @@
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:clickable="true"
android:focusable="true" />
<android.widget.Space
android:layout_width="8dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/read"
style="@style/Widget.Material3.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
+9 -17
View File
@@ -29,20 +29,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.hippo.ehviewer.widget.ResizeableFixedThumb
android:id="@+id/thumb"
android:layout_width="0dp"
android:layout_height="0dp"
app:alwaysCutAndScale="true"
app:retryType="click"/>
</com.google.android.material.card.MaterialCardView>
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height"
app:alwaysCutAndScale="true"
app:retryType="longClick" />
<TextView
android:id="@+id/title"
@@ -53,7 +45,7 @@
android:layout_marginLeft="8dp"
android:layout_marginTop="4dp"
android:layout_marginRight="8dp"
android:layout_toEndOf="@id/thumb_card" />
android:layout_toEndOf="@id/thumb" />
<TextView
android:id="@+id/uploader"
@@ -78,7 +70,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@id/title"
android:layout_alignBottom="@id/thumb_card"
android:layout_alignBottom="@id/thumb"
android:layout_marginBottom="8dp"
android:paddingLeft="8dp"
android:paddingTop="2dp"
@@ -91,7 +83,7 @@
android:id="@+id/actions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/thumb_card"
android:layout_alignBottom="@+id/thumb"
android:layout_alignParentEnd="true"
android:paddingEnd="8dp">
@@ -35,14 +35,12 @@
<TextView
android:id="@+id/user"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackground" />
<TextView
android:id="@+id/time"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true" />
@@ -15,7 +15,6 @@
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
@@ -32,15 +31,15 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/thumb_card"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_width="@dimen/gallery_list_thumb_width"
android:layout_height="@dimen/gallery_list_thumb_height">
<com.hippo.ehviewer.widget.ResizeableFixedThumb
<com.hippo.ehviewer.widget.FixedThumb
android:id="@+id/thumb"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:alwaysCutAndScale="true"
app:retryType="click"/>
app:retryType="click" />
</com.google.android.material.card.MaterialCardView>
+9 -12
View File
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@drawable/ic_launcher_foreground" />
app:srcCompat="@mipmap/ic_launcher" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_layout"
@@ -95,30 +95,27 @@
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="horizontal"
android:orientation="vertical"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="4dp">
<Button
android:id="@+id/register"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:text="@string/register" />
<Button
android:id="@+id/sign_in"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:text="@string/sign_in" />
<Button
android:id="@+id/register"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/register" />
</LinearLayout>
<LinearLayout
@@ -12,8 +12,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
@@ -140,22 +139,13 @@
android:minHeight="0dp"
android:text="@string/search_sr" />
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
<Spinner
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/advance_search_table_item_margin"
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
android:layout_weight="1"
android:hint="@string/search_sr">
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
app:simpleItems="@array/search_min_rating" />
</com.google.android.material.textfield.TextInputLayout>
android:entries="@array/search_min_rating" />
</LinearLayout>
<LinearLayout
@@ -60,7 +60,6 @@
android:layout_gravity="bottom"
android:indeterminate="true"
android:visibility="gone"
app:fitsSystemWindowsInsets="end"
app:hideAnimationBehavior="inward"
app:showAnimationBehavior="outward" />
@@ -15,8 +15,7 @@
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/white"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -15,8 +15,7 @@
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/white"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 B

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

+4
View File
@@ -15,6 +15,10 @@
-->
<resources>
<!-- divider -->
<color name="divider">#20ffffff</color>
<color name="navigation_bar_divider">#1fffffff</color>
<!-- Lock pattern view -->
</resources>
+3 -13
View File
@@ -106,6 +106,7 @@
<string name="content_layout_restore_tip">点击重试</string>
<string name="write_rationale">EhViewer 将申请设备存储空间访问权限以便下载画廊。您愿意授权吗?</string>
<string name="you_rejected_me">好痛</string>
<string name="analytics_explain">您同意让开发者收集您的设备信息与使用情况吗?这将对开发有很大帮助并将为您启用应用内更新。&lt;br&gt;&lt;br&gt;数据统计服务由 Microsoft AppCenter 提供,其隐私政策可在 &lt;a href=&quot;https://privacy.microsoft.com/zh-cn/privacystatement&quot;&gt;https://privacy.microsoft.com/zh-cn/privacystatement&lt;/a&gt; 查看。&lt;br&gt;&lt;br&gt;若您同意参与数据统计,您的设备信息与应用使用情况将会被记录,其包括但不限于:Android API 版本、设备型号、语言所在地、本应用版本号、会话时长、应用功能使用次数。开发者承诺以下信息并不会被记录:电话号码、电子邮件地址、IMEI。&lt;br&gt;&lt;br&gt;在您同意参与数据统计之前,您的信息不会被记录。&lt;br&gt;&lt;br&gt;被记录的数据会由 Microsoft AppCenter 进行分析,分析报表仅可被本应用开发者访问。</string>
<string name="get_it">知道了</string>
<string name="accept">接受</string>
<string name="reject">拒绝</string>
@@ -755,23 +756,12 @@
<string name="pick_new_download_location">选择新路径</string>
<string name="dont_show_again">不再显示</string>
<string name="open_settings">打开设置</string>
<string name="app_link_not_verified_message">对于您的设备,我们强烈建议您使用 1.8 版本。1.7 版本仅为 Android 11 及以下版本设计</string>
<string name="app_link_not_verified_title">升级 App 版本</string>
<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>
<string name="settings_privacy_require_unlock">需要解锁</string>
<string name="proxy_type_name">代理类型</string>
<string name="filter_label">屏蔽项类型</string>
<string name="archive_passwd">归档文件密码</string>
<string name="archive_need_passwd">归档文件需要密码</string>
<string name="passwd_wrong">密码错误</string>
<string name="passwd_cannot_be_empty">密码不能为空</string>
<string name="list_tile_thumb_size">详情模式下缩略图大小</string>
<string name="backup_favorite">备份收藏列表</string>
<string name="backup_favorite_summary">备份云端收藏列表到本地</string>
<string name="backup_favorite_failed">备份收藏列表失败</string>
<string name="backup_favorite_success">备份收藏列表成功</string>
<string name="backup_favorite_start">正在备份收藏列表</string>
</resources>
+8 -78
View File
@@ -19,8 +19,8 @@
<string name="app_name">EhViewer</string>
<!-- Theme -->
<string name="theme_light">淺色主題</string>
<string name="theme_dark">深色主題</string>
<string name="theme_light">亮色主題</string>
<string name="theme_dark">暗色主題</string>
<string name="theme_black">黑色主題</string>
<string name="homepage">首頁</string>
@@ -144,7 +144,7 @@
<string name="search_specify_tag">指定標籤</string>
<string name="search_enable_advance">啟用進階選項</string>
<string name="search_tip">一般搜尋:就是搜尋。\n\n訂閱搜尋:在訂閱中搜尋。\n\n指定上傳者:列出該上傳者上傳的圖庫。其他選項會被忽略。\n\n指定標籤:列出含有該標籤的圖庫。其他選項會被忽略。</string>
<string name="search_tip">一般搜尋:就是搜尋。\n\n訂閱搜素:在訂閱中搜索。\n\n指定上傳者:列出該上傳者上傳的圖庫。其他選項會被忽略。\n\n指定標籤:列出含有該標籤的圖庫。其他選項會被忽略。</string>
<string name="search_sname">搜尋圖庫</string>
<string name="search_stags">搜尋圖庫的標籤</string>
@@ -183,7 +183,7 @@
<string name="delete_quick_search_message">刪除“%s”嗎?</string>
<string name="no_quick_search">快速搜尋會顯示在這裡</string>
<string name="add_quick_search_tip">圖庫列表的狀態將被儲存為快速搜尋。如果你想儲存搜尋面板的狀態,請先進行搜尋。</string>
<string name="readme">ReadMe</string>
<string name="readme">讀我檔</string>
<string name="image_search_not_quick_search">無法將圖片搜尋加入快速搜尋</string>
<string name="duplicate_quick_search">已存在相同的快速搜尋,名稱為“%s”。</string>
<string name="duplicate_name">已經由重複的名稱了</string>
@@ -234,10 +234,7 @@
<string name="add_to_favorite_failure">無法新增至收藏</string>
<string name="remove_from_favorite_failure">無法從收藏中移除</string>
<string name="filter_the_uploader">把上傳者“%s”加入濾除列表?</string>
<string name="filter_the_tag">隱藏標籤“%s”?</string>
<string name="filter_added">已經新增篩選條件</string>
<string name="newer_version_avaliable">此圖庫有新版本可以使用。</string>
<string name="newer_version_title">%1$s, 增加於 %2$s</string>
<!-- Rating -->
<string name="rating10">太神啦!</string>
@@ -352,7 +349,6 @@
<string name="rename_label_title">重新命名標籤</string>
<string name="delete_label_title">刪除標籤</string>
<string name="delete_label_message">將 “%s”刪除?</string>
<string name="rename_label">重新命名</string>
<!-- History -->
<string name="no_history">歷史紀錄將會顯示在這裡</string>
@@ -377,7 +373,6 @@
<string name="set_pattern_protection">設定圖形解鎖</string>
<string name="set_pattern_protection_tip">繪製圖案來設定繪圖解鎖\n留白以取消圖案保護</string>
<string name="set">設定</string>
<string name="enable_biometric">允許使用生物識別解鎖</string>
<!-- UConfig Activity -->
<string name="u_config">EHentai 設定</string>
@@ -405,7 +400,6 @@
<string name="page_menu_refresh">@string/refresh</string>
<string name="page_menu_share">@string/share</string>
<string name="page_menu_save">儲存</string>
<string name="page_menu_save_to">儲存到…</string>
<string name="page_menu_add_bookmark">新增書籤</string>
<string name="gallery_menu_title">選單</string>
<string name="share_image">分享圖片</string>
@@ -428,8 +422,8 @@
<string name="settings_my_tags">我的標籤</string>
<string name="settings_my_tags_summary">EHentai 網站上的我的標籤</string>
<string name="settings_eh_theme">主題</string>
<string name="settings_eh_theme_light">淺色</string>
<string name="settings_eh_theme_dark">深色</string>
<string name="settings_eh_theme_light">亮色</string>
<string name="settings_eh_theme_dark">暗色</string>
<string name="settings_eh_theme_black">黑色</string>
<string name="settings_eh_apply_nav_bar_theme_color">導航欄應用主題配色</string>
<string name="settings_eh_gallery_site">圖庫站台</string>
@@ -471,7 +465,6 @@
<string name="settings_read_screen_rotation_default">系統預設</string>
<string name="settings_read_screen_rotation_portrait">直向</string>
<string name="settings_read_screen_rotation_landscape">橫向</string>
<string name="settings_read_screen_rotation_sensor">自動旋轉</string>
<string name="settings_read_reading_direction">閱讀方向</string>
<string name="settings_read_reading_direction_left_to_right">由左到右</string>
<string name="settings_read_reading_direction_right_to_Left">由右到左</string>
@@ -497,7 +490,6 @@
<string name="settings_read_reading_fullscreen">全螢幕</string>
<string name="settings_read_custom_screen_lightness">自訂螢幕亮度</string>
<string name="settings_read_screen_lightness">螢幕亮度</string>
<string name="settings_read_reverse_volume">翻轉音量鍵</string>
<string name="settings_download">下載</string>
<string name="settings_download_download_location">下載路徑</string>
@@ -533,7 +525,7 @@
<string name="settings_advanced">進階</string>
<string name="settings_advanced_save_parse_error_body">解析失敗時儲存網頁的內容</string>
<string name="settings_advanced_save_parse_error_body_summary">網頁內容可能含有敏感的隱私資料</string>
<string name="settings_advanced_save_crash_log">應用程式崩潰時儲存錯誤日誌</string>
<string name="settings_advanced_save_crash_log">應用崩潰時儲存錯誤日誌</string>
<string name="settings_advanced_save_crash_log_summary">錯誤日誌可以幫助開發者修正問題</string>
<string name="settings_advanced_dump_logcat">傾印log</string>
<string name="settings_advanced_dump_logcat_summary">儲存log紀錄至外接儲存裝置中</string>
@@ -599,7 +591,7 @@
<string name="rewrite">改寫</string>
<string name="language_japanese">日語</string>
<string name="language_english">英語</string>
<string name="language_chinese">中文</string>
<string name="language_chinese">漢語</string>
<string name="language_dutch">荷蘭語</string>
<string name="language_french">法語</string>
<string name="language_german">德語</string>
@@ -710,68 +702,6 @@
<string name="settings_privacy">隱私</string>
<string name="settings_privacy_secure">不允許螢幕擷取</string>
<string name="settings_privacy_secure_summary">啟用後,將無法對本程式進行螢幕擷取,同時,將不會在系統任務切換器中顯示該程式的預覽內容,重新啟動本程式以套用此變更</string>
<string name="download_service">下載服務</string>
<string name="favorite_name">收藏圖庫</string>
<string name="black_dark_theme">純黑深色主題</string>
<string name="download_filter">過濾</string>
<string name="download_all">全部</string>
<string name="download_start_all_reversed">全部開始(倒序)</string>
<string name="settings_download_download_delay">下載延時</string>
<string name="settings_download_download_delay_summary">每次下載延時 %s 毫秒</string>
<string name="no_browser_installed">請安裝一個瀏覽器。</string>
<string name="toplist_alltime">從始至終</string>
<string name="toplist_pastyear">過去一年</string>
<string name="toplist_pastmonth">過去一個月</string>
<string name="toplist_yesterday">昨日</string>
<string name="toplist">排行</string>
<string name="tag_vote_down">不,這不是</string>
<string name="tag_vote_up">是的,這很正確</string>
<string name="tag_vote_successfully">投票成功</string>
<string name="from_clipboard">從剪貼板匯入</string>
<string name="from_clipboard_error">未在剪貼板中發現 Cookies。</string>
<string name="delete_search_history">從搜索記錄中刪除“%s”?</string>
<string name="settings_advanced_dns_over_http_title">安全 DNS(DoH)</string>
<string name="settings_advanced_dns_over_http_summary">解決某些地區的 DNS 汙染問題</string>
<string name="settings_advanced_domain_fronting_title">域名前綴</string>
<string name="settings_advanced_domain_fronting_summary">繞過 SNI 封鎖</string>
<string name="read_theme">色彩主題</string>
<string name="follow_app">跟隨應用</string>
<string name="settings_e_ink_mode_title">墨水屏模式</string>
<string name="settings_e_ink_mode_summary">為墨水屏做一些最佳化</string>
<string name="settings_advanced_clear_memory_cache_done">完成記憶體快取清理</string>
<string name="action_add_tag">增加標籤</string>
<string name="action_add_tag_tip">增加新標籤,用逗號「,」分割</string>
<string name="error_no_webview">WebView 不可用</string>
<string name="comment_user_uploader">%s (上傳者)</string>
<string name="settings_eh_metered_network_warning">流量計費網路警告</string>
<string name="metered_network_warning">正在使用流量計費網路</string>
<string name="read_from">從第 %d 頁閱讀</string>
<string name="settings_request_news_title">啟動時請求新聞頁面</string>
<string name="copy_trans">複製翻譯</string>
<string name="reset_download_location">恢復預設路徑</string>
<string name="pick_new_download_location">選擇新路徑</string>
<string name="dont_show_again">不再顯示</string>
<string name="open_settings">打開設定</string>
<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>
<string name="settings_privacy_require_unlock">需要解鎖</string>
<string name="proxy_type_name">代理類型</string>
<string name="filter_label">隱藏項類型</string>
<string name="archive_passwd">封存文件密碼</string>
<string name="archive_need_passwd">封存文件需要密碼</string>
<string name="passwd_wrong">密碼錯誤</string>
<string name="passwd_cannot_be_empty">密碼不能為空</string>
<string name="list_tile_thumb_size">詳情模式下縮圖大小</string>
<string name="backup_favorite">備份收藏列表</string>
<string name="backup_favorite_summary">備份雲端收藏列表到本機</string>
<string name="backup_favorite_failed">備份收藏列表失敗</string>
<string name="backup_favorite_success">備份收藏列表成功</string>
<string name="backup_favorite_start">正在備份收藏列表</string>
</resources>
+4
View File
@@ -134,6 +134,10 @@
<attr name="url" format="string" />
</declare-styleable>
<declare-styleable name="SeekBarPreference">
<attr name="max" format="integer" />
</declare-styleable>
<declare-styleable name="FastScroller">
<attr name="handler" format="reference" />
<attr name="draggable" format="boolean" />
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Tarsin Norbin
~
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ (at your option) any later version.
~
~ EhViewer is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License along with EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
-->
<resources>
<color name="ic_launcher_background">#E2E0D0</color>
<color name="ic_launcher_foreground">#5C0D11</color>
</resources>
+33
View File
@@ -18,10 +18,43 @@
<color name="primary_drawable_black">#ccffffff</color>
<color name="secondary_drawable_black">#3dffffff</color>
<color name="red_500">#fff44336</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="brown_500">#ff795548</color>
<color name="loading_indicator_red">#ffe53935</color>
<color name="loading_indicator_purple">@color/purple_a700</color>
<color name="loading_indicator_blue">#ff1d87e4</color>
<color name="loading_indicator_cyan">@color/cyan_600</color>
<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="navigation_bar_divider">#1f000000</color>
<!-- Red -->
<!-- Orange -->
<!-- Yellow 700 -->
<!-- Green -->
<!-- Light Green -->
<!-- Blue -->
<!-- Indigo -->
<!-- Purple -->
<!-- Deep Purple 300 -->
<!-- Pink 300 -->
<!-- divider -->
<color name="divider">#20000000</color>
<color name="divider_black">#20ffffff</color>
<!-- shadow -->
<!-- Gallery Activity -->
<!-- Lock pattern view -->
<!--Shortcut background-->
<color name="shortcut_bg">#F5F5F5</color>
</resources>
+2
View File
@@ -31,6 +31,8 @@
<dimen name="rating_size">16dp</dimen>
<dimen name="rating_interval">1dp</dimen>
<dimen name="gallery_list_thumb_height">120dp</dimen>
<dimen name="gallery_list_thumb_width">80dp</dimen>
<!-- 4dp - 4dp, 4dp for card padding -->
<dimen name="gallery_list_interval">0dp</dimen>
<!-- 8dp - 4dp, 4dp for card padding -->
+3
View File
@@ -60,10 +60,13 @@
<string name="pd_file_image" translatable="false">M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6,20H15L18,20V12L14,16L12,14L6,20M8,9A2,2 0 0,0 6,11A2,2 0 0,0 8,13A2,2 0 0,0 10,11A2,2 0 0,0 8,9Z</string>
<string name="pd_eh_subscription" translatable="false">M20,8H4V6h16V8 M18,2H6v2h12V2 M20,10H4c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2v-8C22,10.9,21.1,10,20,10zM11,14H7v1h4v2H7v1h4v2H5v-8h6V14z M19,20h-2v-3h-2v3h-2v-8h2v3h2v-3h2V20z</string>
<string name="pd_pencil" translatable="false">M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z</string>
<string name="pd_big_history" translatable="false">M5.5,2H22V0H5C3.3,0,2,1.3,2,3V21C2,22.7,3.3,24,5,24H22V4H5.5C4.1,4,4.1,2,5.5,2ZM20,6V22H6V6H20M12.5,11.8V14.4L14.9,15.8L15.3,15.2L13.3,14V11.9M13.3,9.3C11.5,9.3,10,10.3,9.2,11.7L8,10.5V13.8H11.3L9.9,12.4C10.5,11.1,11.8,10.3,13.3,10.3C15.4,10.3,17.1,12,17.1,14.1S15.4,17.9,13.3,17.9C11.7,17.9,10.3,16.9,9.8,15.4H8.7C9.3,17.4,11.1,18.9,13.3,18.9C15.9,18.9,18.1,16.8,18.1,14.1C18,11.4,15.9,9.3,13.3,9.3Z</string>
<string name="pd_big_download" translatable="false">M0,19V24H24V19H0ZM16,22H15V21H16V22ZM19,22H18V21H19V22ZM22,22H21V21H22V22M15,11L15,0L9,0L9,11L5,11L12,18L19,11Z</string>
<string name="pd_big_filter" translatable="false">M22.9,2.84C24,0.44,15.08,0,12,0C9.06,0,0,0.43,1.1,2.84C1.6,3.94,10,15.76,10,15.76V21L14,24V15.76S22.4,3.96,22.9,2.84ZM12,1.94C16.25,1.94,20.15,2.51,20.15,3.17S16.25,4.34,12,4.34S3.97,3.8,3.97,3.17S7.76,1.94,12,1.94Z</string>
<string name="pd_slider_bubble" translatable="false">M13,0C20.2,0,26,5.9,26,13.2,26,16.9,24,20.8,18.7,26.1L13,32,7.2,26.2C2,20.8,0,16.9,0,13.3,0,5.9,5.8,0,13,0Z</string>
<string name="pd_sad_panda" translatable="false">M21.1,7.8C22.2,7.4,23,6.3,23,5C23,3.3,21.7,2,20,2C18.9,2,17.9,2.6,17.4,3.6L21.1,7.8M6.6,3.6C6.1,2.6,5.1,2,4,2C2.3,2,1,3.3,1,5C1,6.3,1.8,7.4,2.9,7.8L6.6,3.6M10.1,13L8,15.1C7.4,15.7,6.5,15.7,5.9,15.1L5.9,15.1C5.3,14.5,5.3,13.6,5.9,13L8,10.9C8.6,10.3,9.5,10.3,10.1,10.9L10.1,10.9C10.7,11.5,10.7,12.4,10.1,13M16,15.1L13.9,13C13.3,12.4,13.3,11.5,13.9,10.9L13.9,10.9C14.5,10.3,15.4,10.3,16,10.9L18.1,13C18.7,13.6,18.7,14.5,18.1,15.1L18.1,15.1C17.5,15.7,16.6,15.7,16,15.1M12,17C12.8,17,13.5,17.4,13.5,18S12.8,19,12,19S10.5,18.6,10.5,18S11.2,17,12,17Z</string>
<string name="pd_archive" translatable="false">M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM10,17l-3.5,-3.5 1.41,-1.41L10,14.17 15.18,9l1.41,1.41L10,17z</string>
+3 -13
View File
@@ -148,6 +148,7 @@
<string name="content_layout_restore_tip">Tap to retry</string>
<string name="write_rationale">EhViewer will request WRITE_EXTERNAL_STORAGE permission for downloading gallery. Do you want to grant the permission?</string>
<string name="you_rejected_me">It hurts</string>
<string name="analytics_explain">Would you mind if the developer collected your device information and your usage? It will help the developer a lot.&lt;br&gt;&lt;br&gt;Data analysis service is provided by Microsoft AppCenter. Its privacy policy can be found at &lt;a href=&quot;https://privacy.microsoft.com/zh-cn/privacystatement&quot;&gt;https://privacy.microsoft.com/zh-cn/privacystatement&lt;/a&gt;.&lt;br&gt;&lt;br&gt;If you agree to join data analysis, your device information and your usage will be collected, including but not limited to Android API version, equipment model, language residence, the application version, conversion time, function times. The developer promise that these information will NOT be collected, including phone number, e-mail address, IMEI.&lt;br&gt;&lt;br&gt;Your information will not be collected until you agree to join data analysis.&lt;br&gt;&lt;br&gt;The collected information will be analyzed by Microsoft AppCenter. The analysis report can only be viewed by the developer of this application.</string>
<string name="get_it">Got it</string>
<string name="accept">Accept</string>
<string name="reject">Reject</string>
@@ -819,23 +820,12 @@
<string name="pick_new_download_location">Pick a new location</string>
<string name="dont_show_again">Don\'t show again</string>
<string name="open_settings">Open settings</string>
<string name="app_link_not_verified_message">For you device, We strongly recommend you to use version 1.8. version 1.7 is only made for Android 11 and below .</string>
<string name="app_link_not_verified_title">Update App Version</string>
<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>
<string name="settings_privacy_require_unlock">Require Unlock</string>
<string name="proxy_type_name">Proxy Type</string>
<string name="filter_label">Blocker Type</string>
<string name="archive_passwd">password</string>
<string name="archive_need_passwd">Archive need password</string>
<string name="passwd_wrong">Password Wrong</string>
<string name="passwd_cannot_be_empty">Password can\'t be empty</string>
<string name="list_tile_thumb_size">Thumb size in thumb mode</string>
<string name="backup_favorite">Backup favorite list</string>
<string name="backup_favorite_summary">Backup remote favorite list to local</string>
<string name="backup_favorite_failed">Backup favorite list failed</string>
<string name="backup_favorite_success">backup favorite list success</string>
<string name="backup_favorite_start">Now starting backup favorite list</string>
</resources>
+9 -1
View File
@@ -18,6 +18,8 @@
<style name="AppTheme" parent="Base.AppTheme" />
<!-- TextAppearance -->
<!-- Progress View -->
<style name="ProgressView">
<item name="android:indeterminate">true</item>
@@ -32,7 +34,7 @@
</style>
<style name="CardMessage">
<item name="android:textAppearance">@style/TextAppearance.Material3.LabelLarge</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.BodyMedium</item>
<item name="android:ellipsize">end</item>
<item name="android:singleLine">true</item>
<item name="android:maxLines">1</item>
@@ -49,6 +51,12 @@
<item name="android:gravity">center_vertical</item>
</style>
<style name="Slider">
<item name="thickness">2dp</item>
<item name="radius">6dp</item>
<item name="textSize">12sp</item>
</style>
<style name="Indicating">
<item name="indicatorHeight">1dp</item>
<item name="indicatorColor">?attr/dividerColor</item>
@@ -109,10 +109,4 @@
android:title="@string/open_by_default"
app:iconSpaceReserved="false" />
<Preference
android:key="backup_favorite"
android:summary="@string/backup_favorite_summary"
android:title="@string/backup_favorite"
app:iconSpaceReserved="false" />
</PreferenceScreen>
-9
View File
@@ -82,15 +82,6 @@
app:entryValues="@array/list_mode_entry_values"
app:iconSpaceReserved="false" />
<SeekBarPreference
android:defaultValue="40"
android:key="list_tile_size"
android:title="@string/list_tile_thumb_size"
android:max="60"
app:iconSpaceReserved="false"
app:min="20"
app:showSeekBarValue="true" />
<rikka.preference.SimpleMenuPreference
android:defaultValue="0"
android:key="detail_size"
+1 -1
View File
@@ -122,8 +122,8 @@
android:dependency="custom_screen_lightness"
android:key="screen_lightness"
android:title="@string/settings_read_screen_lightness"
android:max="200"
app:iconSpaceReserved="false"
app:max="200"
app:showSeekBarValue="true" />
</PreferenceScreen>
-55
View File
@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="646" height="250" id="svg8502" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="get-it-on-github.svg" inkscape:export-filename="/home/nixdorf/Lokaler Speicher/Build/andOTP/assets/badges/get-it-on-github.png" inkscape:export-xdpi="90" inkscape:export-ydpi="90">
<defs id="defs8504" />
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.50000001" inkscape:cx="-223.13537" inkscape:cy="546.37524" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:window-width="1920" inkscape:window-height="1021" inkscape:window-x="0" inkscape:window-y="31" inkscape:window-maximized="1" />
<metadata id="metadata8507">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Andrew Nayenko</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>https://f-droid.org</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
<dc:contributor>
<cc:Agent>
<dc:title />
</cc:Agent>
</dc:contributor>
</cc:Work>
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-289,-312.36218)">
<rect style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#a6a6a6;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" id="rect3007" width="560" height="164" x="332" y="355.36218" rx="20" ry="20" />
<text sodipodi:linespacing="100%" id="text3013" y="402.36697" x="508.95129" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.39519119px;line-height:100%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;display:inline;overflow:visible;visibility:visible;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate" xml:space="preserve"><tspan y="402.36697" x="508.95129" id="tspan3015" sodipodi:role="line" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:34.125px;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans'">GET IT ON</tspan></text>
<text xml:space="preserve" style="color:black;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.70882034px;line-height:100%;font-family:Rokkitt;-inkscape-font-specification:'Rokkitt Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;enable-background:accumulate" x="508.21259" y="489.36108" id="text5902" sodipodi:linespacing="100%"><tspan sodipodi:role="line" id="tspan5904" x="508.21259" y="489.36108" style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:87.5px;line-height:100%;font-family:'Roboto Slab';-inkscape-font-specification:'Roboto Slab Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:white;fill-opacity:1">GitHub</tspan></text>
<g transform="matrix(1.0017681,0,0,1.0017681,68.770756,92.660249)" id="g5136">
<path style="fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none" d="m 350.60937,282.4375 c -33.34765,0 -60.38671,27.03516 -60.38671,60.38672 0,26.67969 17.30078,49.3164 41.29687,57.30078 3.01563,0.55859 4.125,-1.30859 4.125,-2.90625 0,-1.4375 -0.0547,-6.19531 -0.082,-11.24219 -16.80078,3.65235 -20.34375,-7.125 -20.34375,-7.125 -2.75,-6.98047 -6.70703,-8.83594 -6.70703,-8.83594 -5.48047,-3.74609 0.41406,-3.67187 0.41406,-3.67187 6.0625,0.42578 9.25781,6.22266 9.25781,6.22266 5.38282,9.23437 14.125,6.5664 17.57032,5.02343 0.54296,-3.90234 2.10937,-6.57031 3.83593,-8.07812 -13.41406,-1.52735 -27.51562,-6.70313 -27.51562,-29.84375 0,-6.59375 2.35937,-11.98047 6.22265,-16.20703 -0.625,-1.52344 -2.6914,-7.66407 0.58594,-15.98047 0,0 5.07031,-1.625 16.60938,6.1875 4.82031,-1.33594 9.98437,-2.00781 15.11718,-2.03125 5.13282,0.0234 10.30079,0.69531 15.12891,2.03125 11.52344,-7.8125 16.58984,-6.1875 16.58984,-6.1875 3.28516,8.3164 1.21875,14.45703 0.58985,15.98047 3.87109,4.22656 6.21484,9.61328 6.21484,16.20703 0,23.19531 -14.125,28.30078 -27.57422,29.79687 2.16797,1.875 4.09766,5.55078 4.09766,11.1836 0,8.07812 -0.0703,14.58203 -0.0703,16.57031 0,1.60937 1.08593,3.49219 4.14843,2.89844 23.98047,-7.9961 41.26172,-30.6211 41.26172,-57.29297 0,-33.35156 -27.03515,-60.38672 -60.38672,-60.38672" id="path4937" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 313.09375,369.14062 c -0.13281,0.30079 -0.60547,0.38672 -1.03516,0.1836 -0.4375,-0.19922 -0.68359,-0.60547 -0.54297,-0.91016 0.12891,-0.30859 0.60157,-0.39062 1.03907,-0.1875 0.4414,0.19922 0.6914,0.61328 0.53906,0.91406" id="path4939" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 315.53906,371.86719 c -0.28906,0.26562 -0.85156,0.14453 -1.23437,-0.27735 -0.39453,-0.42187 -0.46875,-0.98437 -0.17578,-1.25781 0.29687,-0.26562 0.84375,-0.14062 1.23828,0.28125 0.39453,0.42578 0.47265,0.98438 0.17187,1.25391" id="path4941" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 317.92187,375.34375 c -0.37109,0.25781 -0.97656,0.0156 -1.35156,-0.51953 -0.37109,-0.53906 -0.37109,-1.17969 0.008,-1.4375 0.375,-0.25781 0.97266,-0.0274 1.35157,0.5039 0.36718,0.54688 0.36718,1.19141 -0.008,1.45313" id="path4943" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 321.18359,378.70312 c -0.33203,0.36719 -1.03906,0.26954 -1.55468,-0.23046 -0.52735,-0.48438 -0.67579,-1.17579 -0.34375,-1.54297 0.33593,-0.36328 1.04687,-0.26172 1.5664,0.23047 0.52344,0.48828 0.6836,1.18359 0.33203,1.54296" id="path4945-3" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 325.68359,380.65625 c -0.14843,0.47266 -0.82812,0.6875 -1.51172,0.48828 -0.68359,-0.20703 -1.1289,-0.76172 -0.99218,-1.24219 0.14453,-0.47265 0.82422,-0.69922 1.51562,-0.48437 0.67969,0.20703 1.12891,0.75781 0.98828,1.23828" id="path4947-6" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 330.625,381.01953 c 0.0156,0.49609 -0.5625,0.91016 -1.28125,0.91797 -0.72266,0.0156 -1.30859,-0.38672 -1.31641,-0.875 0,-0.50391 0.57032,-0.91406 1.28907,-0.92578 0.71875,-0.0156 1.30859,0.38672 1.30859,0.88281" id="path4949" inkscape:connector-curvature="0" />
<path style="fill:white;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 335.22266,380.23437 c 0.0859,0.48829 -0.41407,0.98438 -1.125,1.11719 -0.70313,0.12891 -1.35157,-0.17187 -1.44141,-0.65234 -0.0859,-0.5 0.42187,-0.9961 1.12109,-1.125 0.71485,-0.125 1.35547,0.16797 1.44532,0.66015" id="path4951-7" inkscape:connector-curvature="0" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

-10
View File
@@ -1,10 +0,0 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="108" height="108" rx="10" fill="white"/>
<path d="M32 31H55" stroke="#FBBD06" stroke-width="8" stroke-linecap="round"/>
<path d="M32 76H44" stroke="#FBBD06" stroke-width="8" stroke-linecap="round"/>
<line x1="32" y1="31" x2="32" y2="76" stroke="#35AA53" stroke-width="8" stroke-linecap="round"/>
<line x1="32" y1="53" x2="73" y2="53" stroke="#FBBD06" stroke-width="8" stroke-linecap="round"/>
<line x1="73" y1="53" x2="73" y2="76" stroke="#4586F3" stroke-width="8" stroke-linecap="round"/>
<path d="M55 43L55 76" stroke="#EB4334" stroke-width="8" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36 57H32C29.7909 57 28 55.2091 28 53C28 50.7909 29.7909 49 32 49H36V57Z" fill="#FBBD06"/>
</svg>

Before

Width:  |  Height:  |  Size: 832 B

+2 -2
View File
@@ -1,6 +1,6 @@
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
id 'com.android.application' version '7.2.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
}
task clean(type: Delete) {
-110
View File
@@ -1,110 +0,0 @@
<p align="right">
<strong>English<strong>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md">
简体中文
</a>
<span> | <span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md">
正體中文
</a>
</p>
<h1 align="center">
<img src="../../art/launcher_icon-web.svg" width="200">
<br>EhViewer<br>
</h1>
<p align="center">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
</a>
</p>
<div align="center">
<h3>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#description">
Description
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#download">
Download
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#screenshot">
Screenshot
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#thanks">
Thanks
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md#license">
License
</a>
</h3>
</div>
# Description
EhViewer fork dedicated to lightweight and high-performance
with Material Design 3 and Dynamic Color Support
# Download
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="../../art/get-it-on-github.svg" width="200px"/></a>
App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 and above, also the develop mainline. Version 1.7 support Android 11 and below, drop down some spicetify functions for Android 12 to make app useable on lower Android System
# Screenshot
![screenshot-01](../../art/screenshot-01.png)
![screenshot-02](../../art/screenshot-02.png)
# Thanks
Here is the libraries
- [AOSP & AndroidX](http://source.android.com/)
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
- [GnuTLS](https://gnutls.org/)
- [material-components-android](https://github.com/material-components/material-components-android)
- [material-design-icons](https://github.com/google/material-design-icons)
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
- [apng](http://apng.sourceforge.net/)
- [giflib](http://giflib.sourceforge.net)
- [greenDAO](https://github.com/greenrobot/greenDAO)
- [jsoup](https://github.com/jhy/jsoup)
- [libjpeg-turbo](https://libjpeg-turbo.org/)
- [libpng](http://www.libpng.org/pub/png/libpng.html)
- [okhttp](https://github.com/square/okhttp)
- [roaster](https://github.com/forge/roaster)
- [libarchive](http://www.libarchive.org/)
- [XZ Utils](https://tukaani.org/xz/)
# License
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
Copyright 2022 Tarsin Norbin
EhViewer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
EhViewer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with EhViewer. If not, see <https://www.gnu.org/licenses/>.
-110
View File
@@ -1,110 +0,0 @@
<p align="right">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md">
English
</a>
<span> | </span>
<strong>简体中文<strong>
<span> | <span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md">
正體中文
</a>
</p>
<h1 align="center">
<img src="../../art/launcher_icon-web.svg" width="200">
<br>EhViewer<br>
</h1>
<p align="center">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
</a>
</p>
<div align="center">
<h3>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#描述">
描述
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#下载">
下载
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#截图">
截图
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#感谢">
感谢
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md#许可证">
许可证
</a>
</h3>
</div>
# 描述
致力于轻量化与良好性能表现的 EhViewer 分支
已针对性适配 Material Design 3 及其动态取色系统
# 下载
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="../../art/get-it-on-github.svg" width="200px"/></a>
App 分为 1.8 与 1.7 两个版本。1.8 版本适配 Android 12 及以上系统,为开发主线;1.7 版本适配 Android 11 及以下版本,削减了 Android 12 及以上系统的独有功能,使其能在低版本 Android 上正常工作
# 截图
![screenshot-01](../../art/screenshot-01.png)
![screenshot-02](../../art/screenshot-02.png)
# 感谢
本项目受到了诸多开源项目的帮助
- [AOSP & AndroidX](http://source.android.com/)
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
- [GnuTLS](https://gnutls.org/)
- [material-components-android](https://github.com/material-components/material-components-android)
- [material-design-icons](https://github.com/google/material-design-icons)
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
- [apng](http://apng.sourceforge.net/)
- [giflib](http://giflib.sourceforge.net)
- [greenDAO](https://github.com/greenrobot/greenDAO)
- [jsoup](https://github.com/jhy/jsoup)
- [libjpeg-turbo](https://libjpeg-turbo.org/)
- [libpng](http://www.libpng.org/pub/png/libpng.html)
- [okhttp](https://github.com/square/okhttp)
- [roaster](https://github.com/forge/roaster)
- [libarchive](http://www.libarchive.org/)
- [XZ Utils](https://tukaani.org/xz/)
# 许可证
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
Copyright 2022 Tarsin Norbin
EhViewer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
EhViewer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with EhViewer. If not, see <https://www.gnu.org/licenses/>.
-110
View File
@@ -1,110 +0,0 @@
<p align="right">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/en.md">
English
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-cn.md">
简体中文
</a>
<span> | <span>
<strong>正體中文<strong>
</p>
<h1 align="center">
<img src="../../art/launcher_icon-web.svg" width="200">
<br>EhViewer<br>
</h1>
<p align="center">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/LICENSE">
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
</a>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
</a>
</p>
<div align="center">
<h3>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#說明">
說明
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#下載">
下載
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#截圖">
截圖
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#感謝">
感謝
</a>
<span> | </span>
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.7.x.x/docs/README/zh-tw.md#許可證書">
許可證書
</a>
</h3>
</div>
# 說明
致力於輕量化與良好性能表現的 EhViewer 分支
已針對性適配 Material Design 3 及其動態取色系統
# 下載
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on Github" src="../../art/get-it-on-github.svg" width="200px"/></a>
App 分為 1.8 與 1.7 兩個版本。1.8 版本適配 Android 12 及以上系統,為開發主線;1.7 版本適配 Android 11 及以下版本,削減了 Android 12 及以上系統的獨有功能,使其能在低版本 Android 上正常工作
# 截圖
![screenshot-01](../../art/screenshot-01.png)
![screenshot-02](../../art/screenshot-02.png)
# 感謝
本項目受到了諸多開源項目的幫助
- [AOSP & AndroidX](http://source.android.com/)
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
- [GnuTLS](https://gnutls.org/)
- [material-components-android](https://github.com/material-components/material-components-android)
- [material-design-icons](https://github.com/google/material-design-icons)
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
- [apng](http://apng.sourceforge.net/)
- [giflib](http://giflib.sourceforge.net)
- [greenDAO](https://github.com/greenrobot/greenDAO)
- [jsoup](https://github.com/jhy/jsoup)
- [libjpeg-turbo](https://libjpeg-turbo.org/)
- [libpng](http://www.libpng.org/pub/png/libpng.html)
- [okhttp](https://github.com/square/okhttp)
- [roaster](https://github.com/forge/roaster)
- [libarchive](http://www.libarchive.org/)
- [XZ Utils](https://tukaani.org/xz/)
# 許可證書
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
Copyright 2022 Tarsin Norbin
EhViewer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
EhViewer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with EhViewer. If not, see <https://www.gnu.org/licenses/>.
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+1 -1
View File
@@ -37,5 +37,5 @@ android {
}
dependencies {
compileOnly 'androidx.annotation:annotation:1.5.0'
compileOnly 'androidx.annotation:annotation:1.4.0'
}
+2 -2
View File
@@ -28,9 +28,9 @@ public class Native {
public static native String getliblzmaVersion();
public static native String getlibzstdVersion();
public static native String getlibjpeg_turboVersion();
public static native String getzlibVersion();
public static native String getnettleVersion();
}
@@ -21,44 +21,103 @@ 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 {
ParcelFileDescriptor pfd;
FileDescriptor fd;
long archiveAddr;
Context ctx;
Uri uri;
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) {
this.ctx = ctx;
this.uri = uri;
public UriArchiveAccessor(Context ctx, Uri uri) throws FileNotFoundException {
osf = new OsReadableFile(ctx, uri);
}
public int open() throws Exception {
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
fd = pfd.getFileDescriptor();
archiveAddr = Os.mmap(0, pfd.getStatSize(), OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, 0);
return openArchive(archiveAddr, pfd.getStatSize());
public int open() {
return openArchive(osf);
}
private native int openArchive(long addr, long size);
public void extractTargetIndexToOutputStream(int index, OutputStream os) {
extracttoOutputStream(osf, index, os);
}
public native void extracttoOutputStream(int index, int fd);
private native int openArchive(OsReadableFile osf);
public native boolean needPassword();
private native int extracttoOutputStream(OsReadableFile osf, int index, OutputStream os);
public native boolean providePassword(String str);
@Override
public void close() throws IOException {
osf.close();
}
private native void closeArchive();
static class OsReadableFile {
ParcelFileDescriptor pfd;
FileDescriptor fd;
public void close() throws Exception {
closeArchive();
Os.munmap(archiveAddr, pfd.getStatSize());
Os.close(fd);
pfd.close();
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);
}
}
}
}
-17
View File
@@ -13,20 +13,6 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Oz -fvisibility=hidden -fvisibility-inlines
-mllvm -polly-detect-profitability-min-per-loop-insts=40 \
-mllvm -polly-invariant-load-hoisting")
# Build GNUTLS libnettle
add_subdirectory(nettle)
# Configure libnettle support for libarchive
include_directories(nettle)
include_directories(.)
set(HAVE_LIBNETTLE 1)
set(HAVE_NETTLE_AES_H 1)
set(HAVE_NETTLE_HMAC_H 1)
set(HAVE_NETTLE_MD5_H 1)
set(HAVE_NETTLE_PBKDF2_H 1)
set(HAVE_NETTLE_RIPEMD160_H 1)
set(HAVE_NETTLE_SHA_H 1)
# Build liblzma
add_subdirectory(liblzma)
@@ -38,9 +24,6 @@ SET(HAVE_LZMA_STREAM_ENCODER_MT 1)
SET(HAVE_LZMADEC_H 1)
SET(HAVE_LIBLZMADEC 1)
# Configure libarchive link's static lib
SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
# Build libarchive
add_subdirectory(libarchive)
+113 -118
View File
@@ -23,9 +23,6 @@
#include <android/log.h>
#include <archive.h>
#include <archive_entry.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdbool.h>
#define LOG_TAG "libarchive_wrapper"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
@@ -35,13 +32,19 @@
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
static JNIEnv *env;
static int cur_index = 0; // Current entry we haven't read any data from it yet
static struct archive *arc = NULL;
static bool need_encrypt = false;
static char *passwd = NULL;
static void *archiveAddr = NULL;
static jlong archiveSize = 0;
#define BLOCK_SIZE 4096 * 16
typedef struct archive_inst_sc {
struct archive *archive;
JNIEnv *env;
jobject file;
jmethodID readID;
jmethodID seekID;
jmethodID skipID;
void *read_buffer;
void *output_buffer;
jbyteArray jbr;
} archive_inst;
static int filename_is_playable_file(const char *name) {
const char *dotptr = strrchr(name, '.');
@@ -60,132 +63,124 @@ static int filename_is_playable_file(const char *name) {
return 0;
}
static long archive_list_all_entries() {
static long archive_list_all_entries(struct archive_inst_sc *inst) {
struct archive *a = inst->archive;
struct archive_entry *entry;
long count = 0;
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
if (filename_is_playable_file(archive_entry_pathname(entry)))
count++;
}
if (!count)
LOGE("%s", archive_error_string(arc));
LOGE("%s", archive_error_string(inst->archive));
return count;
}
static void archive_release() {
if (arc) {
archive_read_close(arc);
archive_read_free(arc);
arc = NULL;
}
}
static int archive_alloc() {
archive_release();
arc = archive_read_new();
archive_read_support_format_all(arc);
archive_read_support_filter_all(arc);
if (passwd)
archive_read_add_passphrase(arc, passwd);
return archive_read_open_memory(arc, archiveAddr, archiveSize);
}
JNIEXPORT jint JNICALL
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *_, jobject thiz, jlong addr, jlong size) {
archiveAddr = (void *) addr;
archiveSize = size;
env = _;
madvise(archiveAddr, archiveSize, MADV_SEQUENTIAL | MADV_WILLNEED);
long r = archive_alloc();
if (r) {
r = 0;
LOGE("%s%s", "Archive open failed:", archive_error_string(arc));
} else {
r = archive_list_all_entries();
LOGI("%s%ld%s", "Found ", r, " image entries in archive");
// We must read through the file|vm then we can know whether it is encrypted
int encryptRet = archive_read_has_encrypted_entries(arc);
switch (encryptRet) {
case 1: // At lease 1 encrypted entry
need_encrypt = true;
break;
case 0: // format supports but no encrypted entry found
case ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW: // Sometimes we might not read through whole file|vm, but it means so far we haven't meet any encrypted entry yet
default:
need_encrypt = false;
}
LOGI("%s%d", "archive_read_has_encrypted_entries returns ", encryptRet);
}
archive_release();
ssize_t ins_read(struct archive *a, void *client_data_ptr, const void **buff) {
archive_inst *arc = client_data_ptr;
int32_t r = (*arc->env)->CallIntMethod(arc->env, arc->file, arc->readID, arc->jbr);
(*arc->env)->GetByteArrayRegion(arc->env, arc->jbr, 0, r, arc->read_buffer);
*buff = arc->read_buffer;
return r;
}
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *_, jobject thiz, jint index,
jint fd) {
struct archive_entry *entry;
size_t size;
int ret;
la_int64_t offset;
const void *buff;
if (!arc || index < cur_index) {
archive_alloc();
cur_index = 0;
int ins_close(struct archive *a, void *client_data) {
archive_inst *arc = client_data;
JNIEnv *env = arc->env;
jmethodID rewind = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, arc->file), "rewind",
"()V");
(*arc->env)->CallVoidMethod(arc->env, arc->file, rewind);
return ARCHIVE_OK;
}
la_int64_t ins_seek(struct archive *a, void *client_data, la_int64_t offset, int whence) {
archive_inst *arc = client_data;
jlong ret = (*arc->env)->CallLongMethod(arc->env, arc->file, arc->seekID, offset, whence);
return ret;
}
la_int64_t ins_skip(struct archive *a, void *client_data, la_int64_t request) {
archive_inst *arc = client_data;
jlong ret = (*arc->env)->CallLongMethod(arc->env, arc->file, arc->skipID, request);
return ret;
}
static archive_inst *archive_create_inst(JNIEnv *env, jobject file) {
archive_inst *inst = malloc(sizeof(archive_inst));
inst->archive = archive_read_new();
archive_read_set_seek_callback(inst->archive, ins_seek);
archive_read_set_skip_callback(inst->archive, ins_skip);
archive_read_support_format_all(inst->archive);
archive_read_support_filter_all(inst->archive);
inst->env = env;
inst->file = file;
inst->readID = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, file), "read", "([B)I");
inst->seekID = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, file), "seek", "(JI)J");
inst->skipID = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, file), "skip", "(J)J");
inst->jbr = (*env)->NewByteArray(env, BLOCK_SIZE);
inst->read_buffer = malloc(BLOCK_SIZE);
inst->output_buffer = malloc(BLOCK_SIZE);
return inst;
}
static void archive_destroy_inst(archive_inst *inst) {
JNIEnv *env = inst->env;
archive_read_free(inst->archive);
(*env)->DeleteLocalRef(env, inst->jbr);
free(inst->read_buffer);
free(inst->output_buffer);
free(inst);
}
JNIEXPORT jint JNICALL
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jobject osf) {
archive_inst *arc = archive_create_inst(env, osf);
long r = archive_read_open(arc->archive, arc, NULL, ins_read, ins_close);
if (r) {
r = 0;
LOGE("%s%s", "Archive open failed:", archive_error_string(arc->archive));
} else {
r = archive_list_all_entries(arc);
LOGI("%s%ld%s", "Found ", r, " image entries in archive");
}
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
archive_read_close(arc->archive);
archive_destroy_inst(arc);
return r;
}
JNIEXPORT jint JNICALL
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *env, jobject thiz, jobject osf,
jint index, jobject os) {
int count = 0;
struct archive_entry *entry;
archive_inst *arc = archive_create_inst(env, osf);
la_ssize_t size;
jmethodID writeID = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, os), "write", "([B)V");
jmethodID flushID = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, os), "flush", "()V");
jmethodID closeID = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, os), "close", "()V");
archive_read_open(arc->archive, arc, NULL, ins_read, ins_close);
while (archive_read_next_header(arc->archive, &entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname(entry)))
continue;
if (cur_index++ == index) {
if (count++ == index) {
for (;;) {
ret = archive_read_data_block(arc, &buff, &size, &offset);
if (ret == ARCHIVE_EOF)
break;
if (ret != ARCHIVE_OK) {
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
break;
size = archive_read_data(arc->archive, arc->output_buffer, BLOCK_SIZE);
if (size < 0) {
LOGE("%s%s", "Archive read failed:", archive_error_string(arc->archive));
goto free;
}
write(fd, buff, size);
(*arc->env)->SetByteArrayRegion(env, arc->jbr, 0, size, arc->output_buffer);
(*arc->env)->CallVoidMethod(env, os, writeID, arc->jbr);
(*arc->env)->CallVoidMethod(env, os, flushID);
if (size < BLOCK_SIZE)
break;
}
break;
}
}
}
JNIEXPORT void JNICALL
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *jniEnv, jobject thiz) {
archive_release();
free(passwd);
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *_, jobject thiz) {
return need_encrypt;
}
JNIEXPORT jboolean JNICALL
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *_, jobject thiz, jstring str) {
struct archive_entry *entry;
jboolean ret = true;
int len = (*env)->GetStringUTFLength(env, str);
if (passwd)
free(passwd);
passwd = calloc(len, sizeof(char));
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
archive_alloc();
while (archive_read_next_header(arc, &entry) == ARCHIVE_OK) {
if (!filename_is_playable_file(archive_entry_pathname(entry)))
continue;
if (!archive_entry_is_encrypted(entry))
continue;
void *tmpBuf = malloc(4096);
if (archive_read_data(arc, tmpBuf, 4096) < ARCHIVE_OK) {
LOGE("%s%s", "Archive read failed:", archive_error_string(arc));
ret = false;
}
free(tmpBuf);
break;
}
archive_release();
return ret;
}
free:
(*arc->env)->CallVoidMethod(env, os, closeID);
archive_read_close(arc->archive);
archive_destroy_inst(arc);
return 0;
}
Loaded 100 of 107 files, more files were not shown because too many files have changed in this diff. Show more