Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41a54b76bf | ||
|
|
a09940eb86 | ||
|
|
159b8c13b0 | ||
|
|
498d51ec22 | ||
|
|
24e50da7c9 | ||
|
|
fcc595d9a3 | ||
|
|
d2aecb5eb5 | ||
|
|
e2cfa30052 |
No files matched your search
@@ -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,19 +44,6 @@ 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:
|
||||
@@ -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,4 +1,3 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 所有其他问题 / All other questions
|
||||
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags-ignore:
|
||||
- "*"
|
||||
on: [push]
|
||||
|
||||
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,14 +16,18 @@ 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.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
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
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
|
||||
- name: Update ARM64
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -9,8 +9,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
uses: actions/setup-java@v3
|
||||
@@ -18,14 +16,18 @@ 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.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
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
|
||||
- run: |
|
||||
git submodule update --init --recursive
|
||||
gradle assembleRelease
|
||||
|
||||
- name: Update ARM64
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -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,51 +19,26 @@ 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.1.8937393" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
|
||||
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-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86-A12+.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a-A12+.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-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-arm64-v8a-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86_64-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86-A12+.apk
|
||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a-A12+.apk
|
||||
|
||||
- name: Collect Apks
|
||||
run: |
|
||||
mkdir app/build/outputs/apk/release/fdroid
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/fdroid/EhViewer-v$GITHUB_REF_NAME-armv7.apk
|
||||
|
||||
- name: Push Apks
|
||||
uses: cpina/github-action-push-to-another-repository@v1.5
|
||||
env:
|
||||
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||
with:
|
||||
source-directory: app/build/outputs/apk/release/fdroid
|
||||
destination-github-username: Ehviewer-Overhauled
|
||||
destination-repository-name: fdroid-repo
|
||||
user-name: 'github-actions[bot]'
|
||||
user-email: 'github-actions[bot]@users.noreply.github.com'
|
||||
target-branch: deploy
|
||||
commit-message: Add apks from $GITHUB_REF
|
||||
target-directory: fdroid/tmp-apks
|
||||
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
|
||||
+15
-6
@@ -1,9 +1,18 @@
|
||||
[submodule "app/src/main/cpp/libarchive"]
|
||||
path = app/src/main/cpp/libarchive
|
||||
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
|
||||
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
|
||||
url = https://github.com/Ehviewer-Overhauled/libjpeg-turbo
|
||||
[submodule "native/src/main/jni/image/giflib/giflib"]
|
||||
path = native/src/main/jni/image/giflib/giflib
|
||||
url = https://github.com/Ehviewer-Overhauled/giflib
|
||||
[submodule "native/src/main/jni/image/libpng/libpng"]
|
||||
path = native/src/main/jni/image/libpng/libpng
|
||||
url = https://github.com/Ehviewer-Overhauled/libpng
|
||||
[submodule "native/src/main/jni/libarchive"]
|
||||
path = native/src/main/jni/libarchive
|
||||
url = https://github.com/Ehviewer-Overhauled/libarchive
|
||||
[submodule "app/src/main/cpp/liblzma"]
|
||||
path = app/src/main/cpp/liblzma
|
||||
[submodule "native/src/main/jni/liblzma"]
|
||||
path = native/src/main/jni/liblzma
|
||||
url = https://github.com/Ehviewer-Overhauled/xz
|
||||
[submodule "app/src/main/cpp/nettle/nettle"]
|
||||
path = app/src/main/cpp/nettle/nettle
|
||||
[submodule "native/src/main/jni/nettle/nettle"]
|
||||
path = native/src/main/jni/nettle/nettle
|
||||
url = https://github.com/Ehviewer-Overhauled/nettle
|
||||
@@ -1,17 +1,5 @@
|
||||
<p align="right">
|
||||
<strong>English<strong>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-cn.md">
|
||||
简体中文
|
||||
</a>
|
||||
<span> | <span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.x.x/docs/README/zh-tw.md">
|
||||
正體中文
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="art/launcher_icon-web.svg" width="200">
|
||||
<img src="art/launcher_icon-web.png" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
@@ -19,7 +7,7 @@
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
|
||||
</a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/1.8.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,42 +21,22 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#description">
|
||||
Description
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#download">
|
||||
Download
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#screenshot">
|
||||
Screenshot
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#thanks">
|
||||
Thanks
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer#license">
|
||||
License
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
|
||||
# Description
|
||||
|
||||
致力于轻量化与良好性能表现的 EhViewer 分支
|
||||
|
||||
已针对性适配 Material Design 3 及其动态取色系统
|
||||
|
||||
|
||||
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> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Git it on F-Droid" src="art/get-it-on-fdroid.svg" width="200px"/></a>
|
||||
[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
|
||||
[CI 版本下载(需登录)](https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml)
|
||||
|
||||
# Screenshot
|
||||
|
||||
@@ -77,16 +45,24 @@ App is divided into two versions: 1.7 and 1.8 . Version 1.8 support Android 12 a
|
||||
|
||||
# Thanks
|
||||
|
||||
本项目受到了诸多开源项目的帮助
|
||||
|
||||
Here is the libraries
|
||||
|
||||
- [AOSP&AndroidX](http://source.android.com/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [AOSP](http://source.android.com/)
|
||||
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
|
||||
- [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)
|
||||
- [RikkaX](https://github.com/RikkaApps/RikkaX)
|
||||
- [roaster](https://github.com/forge/roaster)
|
||||
- [TapTargetView](https://github.com/KeepSafe/TapTargetView)
|
||||
- [libarchive](http://www.libarchive.org/)
|
||||
- [XZ Utils](https://tukaani.org/xz/)
|
||||
|
||||
|
||||
+1
-2
@@ -4,5 +4,4 @@ manifest-merger-release-report.txt
|
||||
/src/main/libs
|
||||
/src/main/obj
|
||||
# Intellij
|
||||
*.iml
|
||||
/.cxx
|
||||
*.iml
|
||||
+34
-62
@@ -1,20 +1,23 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'com.google.devtools.ksp'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
ndkVersion "25.1.8937393"
|
||||
|
||||
Properties localProperties = new Properties()
|
||||
if (project.rootProject.file('local.properties').exists()) {
|
||||
localProperties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable = true
|
||||
reset()
|
||||
include 'arm64-v8a', 'x86_64', 'armeabi-v7a', 'x86'
|
||||
universalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,19 +32,13 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 33
|
||||
versionCode 180011
|
||||
versionName "1.8.2.2"
|
||||
versionCode 114535
|
||||
versionName "1.7.29.3"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path 'src/main/cpp/CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
@@ -81,25 +78,27 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.0"
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += 'src/main/java-gen'
|
||||
}
|
||||
}
|
||||
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
task -> task.dependsOn ":daogenerator:executeDaoGenerator"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.0-rc02"
|
||||
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-rc01"
|
||||
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"
|
||||
@@ -112,32 +111,23 @@ dependencies {
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||
|
||||
def compose_version = "1.3.0-beta02"
|
||||
implementation "androidx.compose.animation:animation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
|
||||
implementation "androidx.compose.ui:ui:$compose_version"
|
||||
|
||||
implementation "androidx.compose.material3:material3:1.0.0-beta02"
|
||||
|
||||
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-alpha01"
|
||||
implementation "androidx.documentfile:documentfile:1.1.0-alpha01"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.6.0-alpha02"
|
||||
def fragment_version = "1.5.1"
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
def lifecycle_version = "2.6.0-alpha02"
|
||||
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"
|
||||
@@ -145,23 +135,8 @@ dependencies {
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||
|
||||
implementation "androidx.loader:loader:1.1.0"
|
||||
|
||||
def nav_version = "2.5.2"
|
||||
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
implementation "androidx.paging:paging-runtime:3.2.0-alpha02"
|
||||
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta02'
|
||||
|
||||
def room_version = "2.5.0-alpha03"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-paging:$room_version"
|
||||
|
||||
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"
|
||||
@@ -169,26 +144,23 @@ 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-alpha01'
|
||||
|
||||
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.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.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.3'
|
||||
implementation 'org.greenrobot:greendao:3.3.0'
|
||||
implementation 'org.jsoup:jsoup:1.15.2'
|
||||
implementation projects.native
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
|
||||
exclude group: 'dev.rikka.rikkax.material', module: 'material'
|
||||
exclude group: 'androidx.compose.material'
|
||||
}
|
||||
Vendored
+17
-8
@@ -7,19 +7,28 @@
|
||||
public static void throw*(...);
|
||||
}
|
||||
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
-keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
|
||||
-keepnames class com.hippo.ehviewer.ui.scene.* { }
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-keep class com.hippo.ehviewer.dao.* { *; }
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
-renamesourcefileattribute
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
-mergeinterfacesaggressively
|
||||
-overloadaggressively
|
||||
|
||||
-dontwarn net.sqlcipher.database.**
|
||||
-dontwarn rx.**
|
||||
-dontwarn org.conscrypt.ConscryptHostnameVerifier
|
||||
-dontwarn org.conscrypt.Conscrypt$Version
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn net.sqlcipher.Cursor
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.conscrypt.Conscrypt
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
@@ -23,11 +23,14 @@
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<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-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
@@ -42,7 +45,6 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/AppTheme">
|
||||
@@ -142,12 +144,6 @@
|
||||
android:label="@string/settings"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="com.hippo.ehviewer.ui.SecurityActivity"
|
||||
android:configChanges="screenSize"
|
||||
android:label="@string/settings"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
|
||||
android:excludeFromRecents="true"
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
|
||||
#define LOG_TAG "libarchive_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
|
||||
|
||||
typedef struct {
|
||||
int using;
|
||||
int next_index;
|
||||
struct archive *arc;
|
||||
struct archive_entry *entry;
|
||||
} archive_ctx;
|
||||
|
||||
pthread_mutex_t ctx_lock;
|
||||
static archive_ctx **ctx_pool;
|
||||
#define CTX_POOL_SIZE 20
|
||||
|
||||
static bool need_encrypt = false;
|
||||
static char *passwd = NULL;
|
||||
static void *archiveAddr = NULL;
|
||||
static jlong archiveSize = 0;
|
||||
|
||||
const char supportExt[9][6] = {
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"png",
|
||||
"gif",
|
||||
"webp",
|
||||
"bmp",
|
||||
"ico",
|
||||
"wbmp",
|
||||
"heif"
|
||||
};
|
||||
|
||||
static inline int filename_is_playable_file(const char *name) {
|
||||
const char *dotptr = strrchr(name, '.');
|
||||
if (!dotptr++)
|
||||
return false;
|
||||
int i;
|
||||
for (i = 0; i < 9; i++)
|
||||
if (strcmp(dotptr, supportExt[i]) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static long archive_list_all_entries(archive_ctx *ctx) {
|
||||
long count = 0;
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
|
||||
if (filename_is_playable_file(archive_entry_pathname(ctx->entry)))
|
||||
count++;
|
||||
if (!count)
|
||||
LOGE("%s", archive_error_string(ctx->arc));
|
||||
return count;
|
||||
}
|
||||
|
||||
static void archive_release_ctx(archive_ctx *ctx) {
|
||||
if (ctx) {
|
||||
archive_read_close(ctx->arc);
|
||||
archive_read_free(ctx->arc);
|
||||
free(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static int archive_alloc_ctx(archive_ctx **ctxptr) {
|
||||
archive_ctx *ctx;
|
||||
int err;
|
||||
ctx = calloc(1, sizeof(archive_ctx));
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
ctx->arc = archive_read_new();
|
||||
ctx->using = 1;
|
||||
if (!ctx->arc) {
|
||||
free(ctx);
|
||||
return -ENOMEM;
|
||||
}
|
||||
archive_read_support_format_all(ctx->arc);
|
||||
archive_read_support_filter_all(ctx->arc);
|
||||
archive_read_set_option(ctx->arc, "zip", "ignorecrc32", "1");
|
||||
if (passwd)
|
||||
archive_read_add_passphrase(ctx->arc, passwd);
|
||||
err = archive_read_open_memory(ctx->arc, archiveAddr, archiveSize);
|
||||
if (err) {
|
||||
archive_read_free(ctx->arc);
|
||||
free(ctx);
|
||||
return err;
|
||||
}
|
||||
*ctxptr = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int archive_skip_to_index(archive_ctx *ctx, int index) {
|
||||
assert(!(ctx->next_index > index));
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
||||
if (!filename_is_playable_file(archive_entry_pathname(ctx->entry)))
|
||||
continue;
|
||||
if (ctx->next_index++ == index) {
|
||||
return ctx->next_index - 1;
|
||||
}
|
||||
}
|
||||
return ARCHIVE_FATAL;
|
||||
}
|
||||
|
||||
static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
||||
int ret;
|
||||
archive_ctx *ctx = NULL;
|
||||
pthread_mutex_lock(&ctx_lock);
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++) {
|
||||
if (!ctx_pool[i])
|
||||
continue;
|
||||
if (ctx_pool[i]->using)
|
||||
continue;
|
||||
if (ctx_pool[i]->next_index > idx)
|
||||
continue;
|
||||
if (!ctx || ctx_pool[i]->next_index > ctx->next_index)
|
||||
ctx = ctx_pool[i];
|
||||
if (ctx->next_index == idx)
|
||||
break;
|
||||
}
|
||||
if (ctx)
|
||||
ctx->using = 1;
|
||||
pthread_mutex_unlock(&ctx_lock);
|
||||
|
||||
if (!ctx) {
|
||||
archive_ctx *victimCtx = NULL;
|
||||
int victimIdx = 0;
|
||||
int replace = 1;
|
||||
ret = archive_alloc_ctx(&ctx);
|
||||
if (ret)
|
||||
return ret;
|
||||
pthread_mutex_lock(&ctx_lock);
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++) {
|
||||
if (!ctx_pool[i]) {
|
||||
ctx_pool[i] = ctx;
|
||||
replace = 0;
|
||||
break;
|
||||
}
|
||||
if (ctx_pool[i]->using)
|
||||
continue;
|
||||
if (!victimCtx || ctx_pool[i]->next_index > victimCtx->next_index) {
|
||||
victimCtx = ctx_pool[i];
|
||||
victimIdx = i;
|
||||
}
|
||||
}
|
||||
if (replace) {
|
||||
archive_release_ctx(victimCtx);
|
||||
ctx_pool[victimIdx] = ctx;
|
||||
}
|
||||
pthread_mutex_unlock(&ctx_lock);
|
||||
}
|
||||
ret = archive_skip_to_index(ctx, idx);
|
||||
if (ret != idx) {
|
||||
ret = archive_errno(ctx->arc);
|
||||
LOGE("Skip to index failed:%s", archive_error_string(ctx->arc));
|
||||
archive_release_ctx(ctx);
|
||||
return ret;
|
||||
}
|
||||
*ctxptr = ctx;
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd, jlong size) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
archive_ctx *ctx = NULL;
|
||||
archiveAddr = mmap64(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (archiveAddr == MAP_FAILED) {
|
||||
LOGE("%s%d", "mmap64 failed with errno ", errno);
|
||||
return 0;
|
||||
}
|
||||
archiveSize = size;
|
||||
pthread_mutex_init(&ctx_lock, 0);
|
||||
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
||||
if (!ctx_pool) {
|
||||
LOGE("Allocate archive ctx pool failed:ENOMEM");
|
||||
return 0;
|
||||
}
|
||||
long r = archive_alloc_ctx(&ctx);
|
||||
if (r) {
|
||||
if (r == -ENOMEM)
|
||||
LOGE("%s", "Mem alloc failed");
|
||||
r = 0;
|
||||
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||
} else {
|
||||
r = archive_list_all_entries(ctx);
|
||||
LOGI("%s%ld%s", "Found ", r, " image entries in archive");
|
||||
|
||||
// We must read through the file|vm then we can know whether it is encrypted
|
||||
int encryptRet = archive_read_has_encrypted_entries(ctx->arc);
|
||||
switch (encryptRet) {
|
||||
case 1: // At lease 1 encrypted entry
|
||||
need_encrypt = true;
|
||||
break;
|
||||
case 0: // format supports but no encrypted entry found
|
||||
default:
|
||||
need_encrypt = false;
|
||||
}
|
||||
|
||||
// madvise for readahead optimization
|
||||
int format = archive_format(ctx->arc);
|
||||
switch (format) {
|
||||
case ARCHIVE_FORMAT_ZIP:
|
||||
case ARCHIVE_FORMAT_RAR:
|
||||
case ARCHIVE_FORMAT_RAR_V5:
|
||||
madvise(archiveAddr, archiveSize, MADV_SEQUENTIAL | MADV_WILLNEED);
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
archive_release_ctx(ctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
typedef struct Memarea {
|
||||
void *buffer;
|
||||
long size;
|
||||
} Memarea;
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extracttoOutputStream(JNIEnv *env, jobject thiz, jint index) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
if (ret)
|
||||
return 0;
|
||||
Memarea *memarea = malloc(sizeof(Memarea));
|
||||
if (!memarea) {
|
||||
ctx->using = 0;
|
||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||
return 0;
|
||||
}
|
||||
memarea->size = (long) archive_entry_size(ctx->entry);
|
||||
memarea->buffer = malloc(memarea->size);
|
||||
if (!memarea->buffer) {
|
||||
ctx->using = 0;
|
||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||
free(memarea);
|
||||
return 0;
|
||||
}
|
||||
ret = archive_read_data(ctx->arc, memarea->buffer, memarea->size);
|
||||
ctx->using = 0;
|
||||
if (ret == memarea->size)
|
||||
return (jlong) memarea;
|
||||
if (ret != memarea->size)
|
||||
LOGE("%s", "No enough data read, WTF?");
|
||||
if (ret < 0)
|
||||
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
||||
free(memarea->buffer);
|
||||
free(memarea);
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
||||
archive_release_ctx(ctx_pool[i]);
|
||||
free(passwd);
|
||||
free(ctx_pool);
|
||||
pthread_mutex_destroy(&ctx_lock);
|
||||
passwd = NULL;
|
||||
need_encrypt = false;
|
||||
munmap(archiveAddr, archiveSize);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *env, jobject thiz) {
|
||||
(void)env; /* UNUSED */
|
||||
(void)thiz; /* UNUSED */
|
||||
return need_encrypt;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jstring str) {
|
||||
(void)thiz; /* UNUSED */
|
||||
struct archive_entry *entry;
|
||||
archive_ctx *ctx;
|
||||
jboolean ret = true;
|
||||
int len = (*env)->GetStringUTFLength(env, str);
|
||||
passwd = realloc(passwd, len * sizeof(char));
|
||||
if (!passwd) {
|
||||
LOGE("Allocate passwd buffer failed");
|
||||
return false;
|
||||
}
|
||||
strcpy(passwd, (*env)->GetStringUTFChars(env, str, NULL));
|
||||
archive_alloc_ctx(&ctx);
|
||||
void *tmpBuf = alloca(4096);
|
||||
while (archive_read_next_header(ctx->arc, &entry) == ARCHIVE_OK) {
|
||||
if (!filename_is_playable_file(archive_entry_pathname(entry)))
|
||||
continue;
|
||||
if (!archive_entry_is_encrypted(entry))
|
||||
continue;
|
||||
if (archive_read_data(ctx->arc, tmpBuf, 4096) < ARCHIVE_OK) {
|
||||
LOGE("%s%s", "Archive read failed:", archive_error_string(ctx->arc));
|
||||
ret = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
archive_release_ctx(ctx);
|
||||
return ret;
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/data_space.h>
|
||||
#include <android/imagedecoder.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#include "image.h"
|
||||
|
||||
#define TAG "ImageDecoder_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG ,__VA_ARGS__)
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static void *tile_buffer = NULL;
|
||||
|
||||
bool image_onLoad() {
|
||||
tile_buffer = malloc(IMAGE_TILE_MAX_SIZE * 4);
|
||||
return tile_buffer != NULL;
|
||||
}
|
||||
|
||||
void image_onUnload() {
|
||||
free(tile_buffer);
|
||||
tile_buffer = NULL;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
AImageDecoder *decoder;
|
||||
void *srcBuffer;
|
||||
size_t stride;
|
||||
int fd;
|
||||
} SOURCE;
|
||||
|
||||
typedef struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
void *buffer;
|
||||
int bufferLen;
|
||||
bool isAnimated;
|
||||
bool alpha;
|
||||
int delay;
|
||||
SOURCE *src;
|
||||
} IMAGE;
|
||||
|
||||
static void recycleSource(SOURCE *src) {
|
||||
if (!src)
|
||||
return;
|
||||
AImageDecoder_delete(src->decoder);
|
||||
free(src->srcBuffer);
|
||||
free(src);
|
||||
}
|
||||
|
||||
static void recycle(IMAGE *image) {
|
||||
if (!image)
|
||||
return;
|
||||
recycleSource(image->src);
|
||||
free(image->buffer);
|
||||
free(image);
|
||||
}
|
||||
|
||||
typedef struct Memarea {
|
||||
void *buffer;
|
||||
long size;
|
||||
} Memarea;
|
||||
|
||||
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
int left;
|
||||
int line;
|
||||
size_t line_stride;
|
||||
int src_stride;
|
||||
int src_pos;
|
||||
int dst_pos;
|
||||
size_t dst_blank_length;
|
||||
|
||||
// Sanitize
|
||||
if (src_x < 0) {
|
||||
width -= src_x;
|
||||
dst_x -= src_x;
|
||||
src_x = 0;
|
||||
}
|
||||
if (dst_x < 0) {
|
||||
width -= dst_x;
|
||||
src_x -= dst_x;
|
||||
dst_x = 0;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (src_y < 0) {
|
||||
height -= src_y;
|
||||
dst_y -= src_y;
|
||||
src_y = 0;
|
||||
}
|
||||
if (dst_y < 0) {
|
||||
height -= dst_y;
|
||||
src_y -= dst_y;
|
||||
dst_y = 0;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_x + width - src_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
left = dst_x + width - dst_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_y + height - src_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
left = dst_y + height - dst_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init
|
||||
line_stride = (size_t) (width * 4);
|
||||
src_stride = src_w * 4;
|
||||
src_pos = src_y * src_stride + src_x * 4;
|
||||
dst_pos = 0;
|
||||
|
||||
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
|
||||
|
||||
// First line
|
||||
dst_pos += (int)dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int)line_stride;
|
||||
src_pos += src_stride;
|
||||
|
||||
// Other lines
|
||||
dst_blank_length = (size_t) ((dst_w - width) * 4);
|
||||
for (line = 1; line < height; line++) {
|
||||
dst_pos += (int)dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int)line_stride;
|
||||
src_pos += src_stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void decodeCommon(IMAGE *image, SOURCE *src) {
|
||||
image->src = src;
|
||||
AImageDecoder *decoder = src->decoder;
|
||||
image->isAnimated = AImageDecoder_isAnimated(decoder);
|
||||
AImageDecoder_setDataSpace(decoder, ADATASPACE_SRGB);
|
||||
AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888);
|
||||
const AImageDecoderHeaderInfo *headerInfo = AImageDecoder_getHeaderInfo(decoder);
|
||||
image->height = AImageDecoderHeaderInfo_getHeight(headerInfo);
|
||||
image->width = AImageDecoderHeaderInfo_getWidth(headerInfo);
|
||||
image->alpha =
|
||||
AImageDecoderHeaderInfo_getAlphaFlags(headerInfo) == ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
|
||||
src->stride = AImageDecoder_getMinimumStride(decoder);
|
||||
image->bufferLen = (int) (image->height * src->stride);
|
||||
image->buffer = malloc(image->bufferLen);
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_setInternallyHandleDisposePrevious(decoder, false);
|
||||
AImageDecoderFrameInfo *frameInfo = AImageDecoderFrameInfo_create();
|
||||
AImageDecoder_getFrameInfo(decoder, frameInfo);
|
||||
image->delay = (int) AImageDecoderFrameInfo_getDuration(frameInfo) / 1000000;
|
||||
AImageDecoderFrameInfo_delete(frameInfo);
|
||||
}
|
||||
AImageDecoder_decodeImage(decoder, image->buffer, src->stride, image->bufferLen);
|
||||
if (!image->isAnimated) {
|
||||
recycleSource(image->src);
|
||||
image->src = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
IMAGE *createFromFd(int fd) {
|
||||
AImageDecoder *decoder = NULL;
|
||||
if (AImageDecoder_createFromFd(fd, &decoder) != ANDROID_IMAGE_DECODER_SUCCESS)
|
||||
return NULL;
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
||||
src->decoder = decoder;
|
||||
src->fd = fd;
|
||||
decodeCommon(image, src);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *createFromAddr(void *addr, long size) {
|
||||
AImageDecoder *decoder;
|
||||
AImageDecoder_createFromBuffer((const void *) addr, size, &decoder);
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
||||
src->decoder = decoder;
|
||||
src->srcBuffer = addr;
|
||||
decodeCommon(image, src);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *create(int32_t width, int32_t height, const void *data) {
|
||||
IMAGE *plain = calloc(1, sizeof(IMAGE));
|
||||
void *buffer = NULL;
|
||||
size_t length = width * height * 4;
|
||||
|
||||
buffer = malloc(length);
|
||||
memcpy(buffer, data, length);
|
||||
|
||||
// plain->isAnimated = false; this is unnecessary since we use calloc
|
||||
plain->width = width;
|
||||
plain->height = height;
|
||||
plain->buffer = buffer;
|
||||
plain->bufferLen = (int) length;
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
void render(IMAGE *image, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
copy_pixels(image->buffer, image->width, image->height, src_x, src_y, dst, dst_w, dst_h, dst_x,
|
||||
dst_y, width, height);
|
||||
}
|
||||
|
||||
void advance(IMAGE *image) {
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_advanceFrame(image->src->decoder);
|
||||
if (AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
||||
image->bufferLen) == ANDROID_IMAGE_DECODER_FINISHED) {
|
||||
AImageDecoder_rewind(image->src->decoder);
|
||||
AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
||||
image->bufferLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int jniGetFDFromFileDescriptor(JNIEnv *env, jobject fileDescriptor) {
|
||||
jint fd = -1;
|
||||
jclass fdClass = (*env)->FindClass(env, "java/io/FileDescriptor");
|
||||
|
||||
if (fdClass != NULL) {
|
||||
jfieldID fdClassDescriptorFieldID = (*env)->GetFieldID(env, fdClass, "descriptor", "I");
|
||||
if (fdClassDescriptorFieldID != NULL && fileDescriptor != NULL) {
|
||||
fd = (*env)->GetIntField(env, fileDescriptor, fdClassDescriptorFieldID);
|
||||
}
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecodeFdInt(JNIEnv *env, jclass clazz, jint fd) {
|
||||
IMAGE *image = createFromFd(fd);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jobject fd) {
|
||||
return Java_com_hippo_image_Image_nativeDecodeFdInt(env, clazz,
|
||||
jniGetFDFromFileDescriptor(env, fd));
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeCreate(JNIEnv *env, jclass clazz, jobject bitmap) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
void *image = NULL;
|
||||
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
|
||||
image = create((int)info.width, (int)info.height, pixels);
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jlong ptr, jint src_x,
|
||||
jint src_y, jobject dst, jint dst_x, jint dst_y, jint width,
|
||||
jint height) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
|
||||
AndroidBitmap_getInfo(env, dst, &info);
|
||||
AndroidBitmap_lockPixels(env, dst, &pixels);
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, pixels, (int)info.width, (int)info.height, dst_x, dst_y, width,
|
||||
height);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, dst);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jlong ptr, jboolean init,
|
||||
jint src_x, jint src_y, jint width, jint height) {
|
||||
if (width * height > IMAGE_TILE_MAX_SIZE)
|
||||
return;
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, tile_buffer, width, height, 0, 0, width, height);
|
||||
|
||||
if (init) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeAdvance(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
advance((IMAGE *) ptr);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetDelay(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
return ((IMAGE *) ptr)->delay;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_image_Image_nativeIsOpaque(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
return ((IMAGE *) ptr)->alpha;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRecycle(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
recycle((IMAGE *) ptr);
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecodeAddr(JNIEnv *env, jclass clazz, jlong addr) {
|
||||
Memarea *memarea = (Memarea *) addr;
|
||||
IMAGE *image = createFromAddr(memarea->buffer, memarea->size);
|
||||
free(memarea);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetFormat(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->isAnimated;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetWidth(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->width;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetHeight(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->height;
|
||||
}
|
||||
Submodule app/src/main/cpp/nettle/nettle deleted from 990abad16c.
@@ -1,20 +0,0 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
open class BaseDialogBuilder constructor(
|
||||
context: Context
|
||||
) : MaterialAlertDialogBuilder(context) {
|
||||
override fun create(): AlertDialog {
|
||||
return super.create().also { dialog ->
|
||||
dialog.window?.let {
|
||||
it.addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
it.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
it.attributes.blurBehindRadius = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package com.hippo.app
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.CheckBox
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
class CheckBoxDialogBuilder constructor(
|
||||
@@ -25,7 +26,7 @@ class CheckBoxDialogBuilder constructor(
|
||||
message: String?,
|
||||
checkText: String?,
|
||||
checked: Boolean
|
||||
) : BaseDialogBuilder(
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.TextView.OnEditorActionListener
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
@@ -30,7 +31,7 @@ class EditTextDialogBuilder constructor(
|
||||
context: Context,
|
||||
text: String?,
|
||||
hint: String?
|
||||
) : BaseDialogBuilder(
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
), OnEditorActionListener {
|
||||
private val mTextInputLayout: TextInputLayout
|
||||
|
||||
@@ -23,13 +23,14 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.CheckBox
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
|
||||
class ListCheckBoxDialogBuilder constructor(
|
||||
context: Context, items: Array<CharSequence>,
|
||||
listener: OnItemClickListener?, checkText: String?, checked: Boolean
|
||||
) : BaseDialogBuilder(
|
||||
) : MaterialAlertDialogBuilder(
|
||||
context
|
||||
) {
|
||||
private val mCheckBox: CheckBox
|
||||
|
||||
@@ -167,7 +167,6 @@ public final class DiskLruCache implements Closeable {
|
||||
* its sequence number is not equal to its entry's sequence number.
|
||||
*/
|
||||
private long nextSequenceNumber = 0;
|
||||
|
||||
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
|
||||
this.directory = directory;
|
||||
this.appVersion = appVersion;
|
||||
@@ -176,7 +175,21 @@ public final class DiskLruCache implements Closeable {
|
||||
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
|
||||
this.valueCount = valueCount;
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the cache in {@code directory}, creating a cache if none exists
|
||||
@@ -231,21 +244,7 @@ public final class DiskLruCache implements Closeable {
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Debug;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
@@ -44,8 +43,8 @@ import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.Native;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
@@ -61,7 +60,6 @@ import com.hippo.ehviewer.client.parser.EventPaneParser;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.EhActivity;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.network.StatusCodeException;
|
||||
@@ -109,8 +107,7 @@ public class EhApplication extends SceneApplication {
|
||||
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
|
||||
private static final long DEBUG_PRINT_INTERVAL = 3000L;
|
||||
|
||||
public static boolean locked = true;
|
||||
public static long locked_last_leave_time = 0;
|
||||
public static boolean locked = false;
|
||||
|
||||
private static EhApplication instance;
|
||||
|
||||
@@ -340,10 +337,11 @@ public class EhApplication extends SceneApplication {
|
||||
EhEngine.initialize();
|
||||
BitmapUtils.initialize(this);
|
||||
|
||||
// Locales can be managed by system automatically above Snow Cone v2
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
|
||||
if (EhDB.needMerge()) {
|
||||
EhDB.mergeOldDB(this);
|
||||
}
|
||||
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
|
||||
|
||||
// Do io tasks in new thread
|
||||
@@ -431,7 +429,7 @@ public class EhApplication extends SceneApplication {
|
||||
Activity activity = getTopActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
AlertDialog dialog = new BaseDialogBuilder(activity)
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(activity)
|
||||
.setMessage(Html.fromHtml(html))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
@@ -534,9 +532,9 @@ public class EhApplication extends SceneApplication {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public EhActivity getTopActivity() {
|
||||
public Activity getTopActivity() {
|
||||
if (!mActivityList.isEmpty()) {
|
||||
return (EhActivity) mActivityList.get(mActivityList.size() - 1);
|
||||
return mActivityList.get(mActivityList.size() - 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -592,8 +590,6 @@ public class EhApplication extends SceneApplication {
|
||||
static class EhlifecycleObserver implements DefaultLifecycleObserver {
|
||||
@Override
|
||||
public void onPause(@NonNull LifecycleOwner owner) {
|
||||
if (!locked)
|
||||
locked_last_leave_time = System.currentTimeMillis() / 1000;
|
||||
locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,24 +17,27 @@
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.paging.PagingSource;
|
||||
import androidx.room.Room;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.dao.BasicDao;
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder;
|
||||
import com.hippo.ehviewer.dao.DaoMaster;
|
||||
import com.hippo.ehviewer.dao.DaoSession;
|
||||
import com.hippo.ehviewer.dao.DownloadDirname;
|
||||
import com.hippo.ehviewer.dao.DownloadDirnameDao;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.dao.DownloadLabelDao;
|
||||
import com.hippo.ehviewer.dao.DownloadsDao;
|
||||
import com.hippo.ehviewer.dao.EhDatabase;
|
||||
import com.hippo.ehviewer.dao.Filter;
|
||||
import com.hippo.ehviewer.dao.FilterDao;
|
||||
import com.hippo.ehviewer.dao.HistoryDao;
|
||||
import com.hippo.ehviewer.dao.HistoryInfo;
|
||||
import com.hippo.ehviewer.dao.LocalFavoriteInfo;
|
||||
@@ -43,8 +46,14 @@ import com.hippo.ehviewer.dao.QuickSearch;
|
||||
import com.hippo.ehviewer.dao.QuickSearchDao;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.SqlUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
|
||||
import org.greenrobot.greendao.AbstractDao;
|
||||
import org.greenrobot.greendao.query.CloseableListIterator;
|
||||
import org.greenrobot.greendao.query.LazyList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -56,9 +65,15 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EhDB {
|
||||
private static final int CUR_DB_VER = 4;
|
||||
|
||||
private static EhDatabase db;
|
||||
private static final String TAG = EhDB.class.getSimpleName();
|
||||
|
||||
private static final int MAX_HISTORY_COUNT = 200;
|
||||
|
||||
private static DaoSession sDaoSession;
|
||||
|
||||
private static boolean sHasOldDB;
|
||||
private static boolean sNewDB;
|
||||
|
||||
private static void upgradeDB(SQLiteDatabase db, int oldVersion) {
|
||||
switch (oldVersion) {
|
||||
@@ -100,12 +115,219 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static void initialize(Context context) {
|
||||
db = Room.databaseBuilder(context, EhDatabase.class, "eh.db").allowMainThreadQueries().build();
|
||||
sHasOldDB = context.getDatabasePath("data").exists();
|
||||
|
||||
DBOpenHelper helper = new DBOpenHelper(
|
||||
context.getApplicationContext(), "eh.db", null);
|
||||
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
|
||||
sDaoSession = daoMaster.newSession();
|
||||
}
|
||||
|
||||
public static boolean needMerge() {
|
||||
return sNewDB && sHasOldDB;
|
||||
}
|
||||
|
||||
public static void mergeOldDB(Context context) {
|
||||
sNewDB = false;
|
||||
|
||||
OldDBHelper oldDBHelper = new OldDBHelper(context);
|
||||
SQLiteDatabase oldDB;
|
||||
try {
|
||||
oldDB = oldDBHelper.getReadableDatabase();
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get GalleryInfo list
|
||||
SparseJLArray<GalleryInfo> map = new SparseJLArray<>();
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_GALLERY, null);
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
GalleryInfo gi = new GalleryInfo();
|
||||
gi.gid = cursor.getInt(0);
|
||||
gi.token = cursor.getString(1);
|
||||
gi.title = cursor.getString(2);
|
||||
gi.posted = cursor.getString(3);
|
||||
gi.category = cursor.getInt(4);
|
||||
gi.thumb = cursor.getString(5);
|
||||
gi.uploader = cursor.getString(6);
|
||||
try {
|
||||
// In 0.6.x version, NaN is stored
|
||||
gi.rating = cursor.getFloat(7);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
gi.rating = -1.0f;
|
||||
}
|
||||
|
||||
map.put(gi.gid, gi);
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Merge local favorites
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_LOCAL_FAVOURITE, null);
|
||||
if (cursor != null) {
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
long i = 0L;
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
LocalFavoriteInfo info = new LocalFavoriteInfo(gi);
|
||||
info.setTime(i);
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
|
||||
// Merge quick search
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_TAG, null);
|
||||
if (cursor != null) {
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
QuickSearch quickSearch = new QuickSearch();
|
||||
|
||||
int mode = cursor.getInt(2);
|
||||
String search = cursor.getString(4);
|
||||
String tag = cursor.getString(7);
|
||||
if (mode == ListUrlBuilder.MODE_UPLOADER && search != null &&
|
||||
search.startsWith("uploader:")) {
|
||||
search = search.substring("uploader:".length());
|
||||
}
|
||||
|
||||
quickSearch.setTime(cursor.getInt(0));
|
||||
quickSearch.setName(cursor.getString(1));
|
||||
quickSearch.setMode(mode);
|
||||
quickSearch.setCategory(cursor.getInt(3));
|
||||
quickSearch.setKeyword(mode == ListUrlBuilder.MODE_TAG ? tag : search);
|
||||
quickSearch.setAdvanceSearch(cursor.getInt(5));
|
||||
quickSearch.setMinRating(cursor.getInt(6));
|
||||
|
||||
dao.insert(quickSearch);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// Merge download info
|
||||
try {
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_DOWNLOAD, null);
|
||||
if (cursor != null) {
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
long i = 0L;
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
DownloadInfo info = new DownloadInfo(gi);
|
||||
int state = cursor.getInt(2);
|
||||
int legacy = cursor.getInt(3);
|
||||
if (state == DownloadInfo.STATE_FINISH && legacy > 0) {
|
||||
state = DownloadInfo.STATE_FAILED;
|
||||
}
|
||||
info.setState(state);
|
||||
info.setLegacy(legacy);
|
||||
if (cursor.getColumnCount() == 5) {
|
||||
info.setTime(cursor.getLong(4));
|
||||
} else {
|
||||
info.setTime(i);
|
||||
}
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
// Merge history info
|
||||
Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_HISTORY, null);
|
||||
if (cursor != null) {
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
// Get GalleryInfo first
|
||||
long gid = cursor.getInt(0);
|
||||
GalleryInfo gi = map.get(gid);
|
||||
if (gi == null) {
|
||||
Log.e(TAG, "Can't get GalleryInfo with gid: " + gid);
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
HistoryInfo info = new HistoryInfo(gi);
|
||||
info.setMode(cursor.getInt(1));
|
||||
info.setTime(cursor.getLong(2));
|
||||
dao.insert(info);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
|
||||
try {
|
||||
oldDBHelper.close();
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized List<DownloadInfo> getAllDownloadInfo() {
|
||||
DownloadsDao dao = db.downloadsDao();
|
||||
List<DownloadInfo> list = dao.list();
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
List<DownloadInfo> list = dao.queryBuilder().orderDesc(DownloadsDao.Properties.Time).list();
|
||||
// Fix state
|
||||
for (DownloadInfo info : list) {
|
||||
if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) {
|
||||
@@ -117,7 +339,7 @@ public class EhDB {
|
||||
|
||||
// Insert or update
|
||||
public static synchronized void putDownloadInfo(DownloadInfo downloadInfo) {
|
||||
DownloadsDao dao = db.downloadsDao();
|
||||
DownloadsDao dao = sDaoSession.getDownloadsDao();
|
||||
if (null != dao.load(downloadInfo.gid)) {
|
||||
// Update
|
||||
dao.update(downloadInfo);
|
||||
@@ -127,13 +349,13 @@ public class EhDB {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadInfo(DownloadInfo downloadInfo) {
|
||||
db.downloadsDao().delete(downloadInfo);
|
||||
public static synchronized void removeDownloadInfo(long gid) {
|
||||
sDaoSession.getDownloadsDao().deleteByKey(gid);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static synchronized String getDownloadDirname(long gid) {
|
||||
DownloadDirnameDao dao = db.downloadDirnameDao();
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
DownloadDirname raw = dao.load(gid);
|
||||
if (raw != null) {
|
||||
return raw.getDirname();
|
||||
@@ -146,7 +368,7 @@ public class EhDB {
|
||||
* Insert or update
|
||||
*/
|
||||
public static synchronized void putDownloadDirname(long gid, String dirname) {
|
||||
DownloadDirnameDao dao = db.downloadDirnameDao();
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
DownloadDirname raw = dao.load(gid);
|
||||
if (raw != null) { // Update
|
||||
raw.setDirname(dirname);
|
||||
@@ -160,23 +382,23 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadDirname(long gid) {
|
||||
DownloadDirnameDao dao = db.downloadDirnameDao();
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
dao.deleteByKey(gid);
|
||||
}
|
||||
|
||||
public static synchronized void clearDownloadDirname() {
|
||||
DownloadDirnameDao dao = db.downloadDirnameDao();
|
||||
DownloadDirnameDao dao = sDaoSession.getDownloadDirnameDao();
|
||||
dao.deleteAll();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static synchronized List<DownloadLabel> getAllDownloadLabelList() {
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
return dao.list();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
return dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time).list();
|
||||
}
|
||||
|
||||
public static synchronized DownloadLabel addDownloadLabel(String label) {
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
DownloadLabel raw = new DownloadLabel();
|
||||
raw.setLabel(label);
|
||||
raw.setTime(System.currentTimeMillis());
|
||||
@@ -187,13 +409,13 @@ public class EhDB {
|
||||
public static synchronized DownloadLabel addDownloadLabel(DownloadLabel raw) {
|
||||
// Reset id
|
||||
raw.setId(null);
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
raw.setId(dao.insert(raw));
|
||||
return raw;
|
||||
}
|
||||
|
||||
public static synchronized void updateDownloadLabel(DownloadLabel raw) {
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
dao.update(raw);
|
||||
}
|
||||
|
||||
@@ -206,8 +428,9 @@ public class EhDB {
|
||||
int offset = reverse ? toPosition : fromPosition;
|
||||
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
|
||||
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
List<DownloadLabel> list = dao.list(offset, limit);
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
List<DownloadLabel> list = dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time)
|
||||
.offset(offset).limit(limit).list();
|
||||
|
||||
int step = reverse ? 1 : -1;
|
||||
int start = reverse ? limit - 1 : 0;
|
||||
@@ -218,44 +441,50 @@ public class EhDB {
|
||||
}
|
||||
list.get(start).setTime(toTime);
|
||||
|
||||
dao.update(list);
|
||||
dao.updateInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized void removeDownloadLabel(DownloadLabel raw) {
|
||||
DownloadLabelDao dao = db.downloadLabelDao();
|
||||
DownloadLabelDao dao = sDaoSession.getDownloadLabelDao();
|
||||
dao.delete(raw);
|
||||
}
|
||||
|
||||
public static synchronized List<GalleryInfo> getAllLocalFavorites() {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.list();
|
||||
return new ArrayList<>(list);
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time).list();
|
||||
List<GalleryInfo> result = new ArrayList<>();
|
||||
result.addAll(list);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.list();
|
||||
return new ArrayList<>(list);
|
||||
query = SqlUtils.sqlEscapeString("%" + query + "%");
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time)
|
||||
.where(LocalFavoritesDao.Properties.Title.like(query)).list();
|
||||
List<GalleryInfo> result = new ArrayList<>();
|
||||
result.addAll(list);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static synchronized void removeLocalFavorites(long gid) {
|
||||
db.localFavoritesDao().deleteByKey(gid);
|
||||
sDaoSession.getLocalFavoritesDao().deleteByKey(gid);
|
||||
}
|
||||
|
||||
public static synchronized void removeLocalFavorites(long[] gidArray) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
for (long gid : gidArray) {
|
||||
dao.deleteByKey(gid);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized boolean containLocalFavorites(long gid) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
return null != dao.load(gid);
|
||||
}
|
||||
|
||||
public static synchronized void putLocalFavorites(GalleryInfo galleryInfo) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
|
||||
if (null == dao.load(galleryInfo.gid)) {
|
||||
LocalFavoriteInfo info;
|
||||
if (galleryInfo instanceof LocalFavoriteInfo) {
|
||||
@@ -275,31 +504,31 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static synchronized List<QuickSearch> getAllQuickSearch() {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
return dao.list();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
return dao.queryBuilder().orderAsc(QuickSearchDao.Properties.Time).list();
|
||||
}
|
||||
|
||||
public static synchronized void insertQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
quickSearch.id = null;
|
||||
quickSearch.time = System.currentTimeMillis();
|
||||
quickSearch.id = dao.insert(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void importQuickSearch(List<QuickSearch> quickSearchList) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
dao.insert(quickSearch);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
dao.update(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
dao.delete(quickSearch);
|
||||
}
|
||||
|
||||
@@ -312,8 +541,9 @@ public class EhDB {
|
||||
int offset = reverse ? toPosition : fromPosition;
|
||||
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
|
||||
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
List<QuickSearch> list = dao.list(offset, limit);
|
||||
QuickSearchDao dao = sDaoSession.getQuickSearchDao();
|
||||
List<QuickSearch> list = dao.queryBuilder().orderAsc(QuickSearchDao.Properties.Time)
|
||||
.offset(offset).limit(limit).list();
|
||||
|
||||
int step = reverse ? 1 : -1;
|
||||
int start = reverse ? limit - 1 : 0;
|
||||
@@ -324,19 +554,15 @@ public class EhDB {
|
||||
}
|
||||
list.get(start).setTime(toTime);
|
||||
|
||||
dao.update(list);
|
||||
dao.updateInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized List<HistoryInfo> getHistoryList() {
|
||||
return db.historyDao().list();
|
||||
}
|
||||
|
||||
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
|
||||
return db.historyDao().listLazy();
|
||||
public static synchronized LazyList<HistoryInfo> getHistoryLazyList() {
|
||||
return sDaoSession.getHistoryDao().queryBuilder().orderDesc(HistoryDao.Properties.Time).listLazy();
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
HistoryInfo info = dao.load(galleryInfo.gid);
|
||||
if (null != info) {
|
||||
// Update time
|
||||
@@ -347,42 +573,49 @@ public class EhDB {
|
||||
info = new HistoryInfo(galleryInfo);
|
||||
info.time = System.currentTimeMillis();
|
||||
dao.insert(info);
|
||||
List<HistoryInfo> list = dao.queryBuilder().orderDesc(HistoryDao.Properties.Time)
|
||||
.limit(-1).offset(MAX_HISTORY_COUNT).list();
|
||||
dao.deleteInTx(list);
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(List<HistoryInfo> historyInfoList) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
for (HistoryInfo info : historyInfoList) {
|
||||
if (null == dao.load(info.gid)) {
|
||||
dao.insert(info);
|
||||
}
|
||||
}
|
||||
|
||||
List<HistoryInfo> list = dao.queryBuilder().orderDesc(HistoryDao.Properties.Time)
|
||||
.limit(-1).offset(MAX_HISTORY_COUNT).list();
|
||||
dao.deleteInTx(list);
|
||||
}
|
||||
|
||||
public static synchronized void deleteHistoryInfo(HistoryInfo info) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
dao.delete(info);
|
||||
}
|
||||
|
||||
public static synchronized void clearHistoryInfo() {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryDao dao = sDaoSession.getHistoryDao();
|
||||
dao.deleteAll();
|
||||
}
|
||||
|
||||
public static synchronized List<Filter> getAllFilter() {
|
||||
return db.filterDao().list();
|
||||
return sDaoSession.getFilterDao().queryBuilder().list();
|
||||
}
|
||||
|
||||
public static synchronized boolean addFilter(Filter filter) {
|
||||
Filter existFilter;
|
||||
try {
|
||||
existFilter = db.filterDao().load(filter.text, filter.mode);
|
||||
existFilter = sDaoSession.getFilterDao().queryBuilder().where(FilterDao.Properties.Text.eq(filter.text), FilterDao.Properties.Mode.eq(filter.mode)).unique();
|
||||
} catch (Exception e) {
|
||||
existFilter = null;
|
||||
}
|
||||
if (existFilter == null) {
|
||||
filter.setId(null);
|
||||
filter.setId(db.filterDao().insert(filter));
|
||||
filter.setId(sDaoSession.getFilterDao().insert(filter));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -390,19 +623,23 @@ public class EhDB {
|
||||
}
|
||||
|
||||
public static synchronized void deleteFilter(Filter filter) {
|
||||
db.filterDao().delete(filter);
|
||||
sDaoSession.getFilterDao().delete(filter);
|
||||
}
|
||||
|
||||
public static synchronized void triggerFilter(Filter filter) {
|
||||
filter.setEnable(!filter.enable);
|
||||
db.filterDao().update(filter);
|
||||
sDaoSession.getFilterDao().update(filter);
|
||||
}
|
||||
|
||||
private static <T> boolean copyDao(BasicDao<T> from, BasicDao<T> to) {
|
||||
List<T> list = from.list();
|
||||
for (T item : list)
|
||||
to.insert(item);
|
||||
return false;
|
||||
private static <T> boolean copyDao(AbstractDao<T, ?> from, AbstractDao<T, ?> to) {
|
||||
try (CloseableListIterator<T> iterator = from.queryBuilder().listIterator()) {
|
||||
while (iterator.hasNext()) {
|
||||
to.insert(iterator.next());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static synchronized boolean exportDB(Context context, Uri uri) {
|
||||
@@ -410,24 +647,31 @@ public class EhDB {
|
||||
|
||||
// Delete old export db
|
||||
context.deleteDatabase(ehExportName);
|
||||
EhDatabase newDb = Room.databaseBuilder(context, EhDatabase.class, ehExportName).allowMainThreadQueries().build();
|
||||
|
||||
DBOpenHelper helper = new DBOpenHelper(context.getApplicationContext(), ehExportName, null);
|
||||
|
||||
try {
|
||||
// Copy data to a export db
|
||||
if (copyDao(db.downloadsDao(), newDb.downloadsDao()))
|
||||
return false;
|
||||
if (copyDao(db.downloadLabelDao(), newDb.downloadLabelDao()))
|
||||
return false;
|
||||
if (copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao()))
|
||||
return false;
|
||||
if (copyDao(db.historyDao(), newDb.historyDao()))
|
||||
return false;
|
||||
if (copyDao(db.quickSearchDao(), newDb.quickSearchDao()))
|
||||
return false;
|
||||
if (copyDao(db.localFavoritesDao(), newDb.localFavoritesDao()))
|
||||
return false;
|
||||
if (copyDao(db.filterDao(), newDb.filterDao()))
|
||||
return false;
|
||||
try (SQLiteDatabase db = helper.getWritableDatabase()) {
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
DaoSession exportSession = daoMaster.newSession();
|
||||
if (!copyDao(sDaoSession.getDownloadsDao(), exportSession.getDownloadsDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getDownloadLabelDao(), exportSession.getDownloadLabelDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getDownloadDirnameDao(), exportSession.getDownloadDirnameDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getHistoryDao(), exportSession.getHistoryDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getQuickSearchDao(), exportSession.getQuickSearchDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getLocalFavoritesDao(), exportSession.getLocalFavoritesDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getBookmarksDao(), exportSession.getBookmarksDao()))
|
||||
return false;
|
||||
if (!copyDao(sDaoSession.getFilterDao(), exportSession.getFilterDao()))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy export db to data dir
|
||||
File dbFile = context.getDatabasePath(ehExportName);
|
||||
@@ -474,89 +718,76 @@ public class EhDB {
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
SQLiteDatabase oldDB = SQLiteDatabase.openDatabase(
|
||||
SQLiteDatabase db = SQLiteDatabase.openDatabase(
|
||||
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
|
||||
int newVersion = CUR_DB_VER;
|
||||
int oldVersion = oldDB.getVersion();
|
||||
int newVersion = DaoMaster.SCHEMA_VERSION;
|
||||
int oldVersion = db.getVersion();
|
||||
if (oldVersion < newVersion) {
|
||||
upgradeDB(oldDB, oldVersion);
|
||||
oldDB.setVersion(newVersion);
|
||||
upgradeDB(db, oldVersion);
|
||||
db.setVersion(newVersion);
|
||||
} else if (oldVersion > newVersion) {
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
}
|
||||
|
||||
String tmpDBName = "tmp.db";
|
||||
context.deleteDatabase(tmpDBName);
|
||||
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
|
||||
.createFromFile(file).allowMainThreadQueries().build();
|
||||
|
||||
// Download label
|
||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
||||
try {
|
||||
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
} catch (Exception ignored) {}
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
DaoSession session = daoMaster.newSession();
|
||||
|
||||
// Downloads
|
||||
try {
|
||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
} catch (Exception ignored) {}
|
||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
||||
List<DownloadInfo> downloadInfoList = session.getDownloadsDao().queryBuilder().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
|
||||
// Download label
|
||||
List<DownloadLabel> downloadLabelList = session.getDownloadLabelDao().queryBuilder().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
|
||||
// Download dirname
|
||||
try {
|
||||
List<DownloadDirname> downloadDirnameList = oldRoomDatabase.downloadDirnameDao().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
List<DownloadDirname> downloadDirnameList = session.getDownloadDirnameDao().queryBuilder().list();
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
|
||||
// History
|
||||
try {
|
||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
} catch (Exception ignored) {}
|
||||
List<HistoryInfo> historyInfoList = session.getHistoryDao().queryBuilder().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
|
||||
// QuickSearch
|
||||
try {
|
||||
List<QuickSearch> quickSearchList = oldRoomDatabase.quickSearchDao().list();
|
||||
List<QuickSearch> currentQuickSearchList = db.quickSearchDao().list();
|
||||
List<QuickSearch> importList = new ArrayList<>();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
String name = quickSearch.name;
|
||||
for (QuickSearch q : currentQuickSearchList) {
|
||||
if (ObjectUtils.equal(q.name, name)) {
|
||||
// The same name
|
||||
name = null;
|
||||
break;
|
||||
}
|
||||
List<QuickSearch> quickSearchList = session.getQuickSearchDao().queryBuilder().list();
|
||||
List<QuickSearch> currentQuickSearchList = sDaoSession.getQuickSearchDao().queryBuilder().list();
|
||||
List<QuickSearch> importList = new ArrayList<>();
|
||||
for (QuickSearch quickSearch : quickSearchList) {
|
||||
String name = quickSearch.name;
|
||||
for (QuickSearch q : currentQuickSearchList) {
|
||||
if (ObjectUtils.equal(q.name, name)) {
|
||||
// The same name
|
||||
name = null;
|
||||
break;
|
||||
}
|
||||
if (null == name) {
|
||||
continue;
|
||||
}
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
} catch (Exception ignored) {}
|
||||
if (null == name) {
|
||||
continue;
|
||||
}
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
|
||||
// LocalFavorites
|
||||
try {
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = oldRoomDatabase.localFavoritesDao().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
List<LocalFavoriteInfo> localFavoriteInfoList = session.getLocalFavoritesDao().queryBuilder().list();
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
|
||||
// Bookmarks
|
||||
// TODO
|
||||
|
||||
// Filter
|
||||
try {
|
||||
List<Filter> filterList = oldRoomDatabase.filterDao().list();
|
||||
List<Filter> currentFilterList = db.filterDao().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
}
|
||||
List<Filter> filterList = session.getFilterDao().queryBuilder().list();
|
||||
List<Filter> currentFilterList = sDaoSession.getFilterDao().queryBuilder().list();
|
||||
for (Filter filter : filterList) {
|
||||
if (!currentFilterList.contains(filter)) {
|
||||
addFilter(filter);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
@@ -565,4 +796,46 @@ public class EhDB {
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DBOpenHelper extends DaoMaster.OpenHelper {
|
||||
|
||||
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
|
||||
super(context, name, factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
super.onCreate(db);
|
||||
sNewDB = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
upgradeDB(db, oldVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OldDBHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String DB_NAME = "data";
|
||||
private static final int VERSION = 4;
|
||||
|
||||
private static final String TABLE_GALLERY = "gallery";
|
||||
private static final String TABLE_LOCAL_FAVOURITE = "local_favourite";
|
||||
private static final String TABLE_TAG = "tag";
|
||||
private static final String TABLE_DOWNLOAD = "download";
|
||||
private static final String TABLE_HISTORY = "history";
|
||||
|
||||
public OldDBHelper(Context context) {
|
||||
super(context, DB_NAME, null, VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import com.hippo.conaco.ValueHelper;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
@@ -35,8 +34,7 @@ public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
FileInputStream is = (FileInputStream) isPipe.open();
|
||||
return ImageBitmap.decode(is.getFD());
|
||||
return ImageBitmap.decode(isPipe.open());
|
||||
} catch (OutOfMemoryError | IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
|
||||
@@ -50,8 +50,6 @@ public class Settings {
|
||||
********************/
|
||||
|
||||
public static final String KEY_THEME = "theme";
|
||||
|
||||
public static final String KEY_ACCOUNT = "account";
|
||||
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
|
||||
public static final int THEME_LIGHT = 1;
|
||||
public static final int THEME_SYSTEM = -1;
|
||||
@@ -78,8 +76,6 @@ public class Settings {
|
||||
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
|
||||
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
|
||||
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
|
||||
|
||||
public static final String KEY_NOTIFICATION_REQUIRED = "notification_required";
|
||||
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
|
||||
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
|
||||
public static final String KEY_MEDIA_SCAN = "media_scan";
|
||||
@@ -92,7 +88,6 @@ public class Settings {
|
||||
********************/
|
||||
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
|
||||
public static final String KEY_SECURITY = "require_unlock";
|
||||
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
|
||||
public static final String DEFAULT_SECURITY = "";
|
||||
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
|
||||
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
|
||||
@@ -105,11 +100,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";
|
||||
@@ -436,7 +426,6 @@ public class Settings {
|
||||
putInt(KEY_VERSION_CODE, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getDisplayName() {
|
||||
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
|
||||
}
|
||||
@@ -1067,9 +1056,6 @@ public class Settings {
|
||||
public static void putSecurity(boolean value) {
|
||||
putBoolean(KEY_SECURITY, value);
|
||||
}
|
||||
public static int getSecurityDelay() {
|
||||
return getInt(KEY_SECURITY_DELAY, 0);
|
||||
}
|
||||
|
||||
public static boolean getEnableFingerprint() {
|
||||
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
|
||||
@@ -1182,22 +1168,4 @@ public class Settings {
|
||||
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;
|
||||
}
|
||||
|
||||
public static boolean getNotificationRequired() {
|
||||
return getBoolean(KEY_NOTIFICATION_REQUIRED, false);
|
||||
}
|
||||
|
||||
public static void putNotificationRequired() {
|
||||
putBoolean(KEY_NOTIFICATION_REQUIRED, true);
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,6 @@ import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Ignore;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -111,52 +108,30 @@ public class GalleryInfo implements Parcelable {
|
||||
return new GalleryInfo[size];
|
||||
}
|
||||
};
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "GID")
|
||||
public long gid;
|
||||
@ColumnInfo(name = "TOKEN")
|
||||
public String token;
|
||||
@ColumnInfo(name = "TITLE")
|
||||
public String title;
|
||||
@ColumnInfo(name = "TITLE_JPN")
|
||||
public String titleJpn;
|
||||
@ColumnInfo(name = "THUMB")
|
||||
public String thumb;
|
||||
@ColumnInfo(name = "CATEGORY")
|
||||
public int category;
|
||||
@ColumnInfo(name = "POSTED")
|
||||
public String posted;
|
||||
@ColumnInfo(name = "UPLOADER")
|
||||
public String uploader;
|
||||
@Ignore
|
||||
public boolean disowned;
|
||||
@ColumnInfo(name = "RATING")
|
||||
public float rating;
|
||||
@Ignore
|
||||
public boolean rated;
|
||||
@Nullable
|
||||
@Ignore
|
||||
public String[] simpleTags;
|
||||
@Ignore
|
||||
public int pages;
|
||||
@Ignore
|
||||
public int thumbWidth;
|
||||
@Ignore
|
||||
public int thumbHeight;
|
||||
@Ignore
|
||||
public int spanSize;
|
||||
@Ignore
|
||||
public int spanIndex;
|
||||
@Ignore
|
||||
public int spanGroupIndex;
|
||||
/**
|
||||
* language from title
|
||||
*/
|
||||
@ColumnInfo(name = "SIMPLE_LANGUAGE")
|
||||
public String simpleLanguage;
|
||||
@Ignore
|
||||
public int favoriteSlot = -2;
|
||||
@Ignore
|
||||
public String favoriteName;
|
||||
|
||||
public GalleryInfo() {
|
||||
@@ -195,7 +170,6 @@ public class GalleryInfo implements Parcelable {
|
||||
}
|
||||
|
||||
private void generateSLangFromTags() {
|
||||
assert simpleTags != null;
|
||||
for (String tag : simpleTags) {
|
||||
for (int i = 0; i < S_LANGS.length; i++) {
|
||||
if (S_LANG_TAGS[i].equals(tag)) {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface BasicDao<T> {
|
||||
List<T> list();
|
||||
long insert(T t);
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
|
||||
@Entity(tableName = "BOOKMARKS")
|
||||
public class BookmarkInfo extends GalleryInfo {
|
||||
public static final Creator<BookmarkInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public BookmarkInfo createFromParcel(Parcel source) {
|
||||
return new BookmarkInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BookmarkInfo[] newArray(int size) {
|
||||
return new BookmarkInfo[size];
|
||||
}
|
||||
};
|
||||
@ColumnInfo(name = "PAGE")
|
||||
public int page;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
|
||||
protected BookmarkInfo(Parcel in) {
|
||||
super(in);
|
||||
this.page = in.readInt();
|
||||
this.time = in.readLong();
|
||||
}
|
||||
|
||||
public BookmarkInfo(GalleryInfo galleryInfo) {
|
||||
this.gid = galleryInfo.gid;
|
||||
this.token = galleryInfo.token;
|
||||
this.title = galleryInfo.title;
|
||||
this.titleJpn = galleryInfo.titleJpn;
|
||||
this.thumb = galleryInfo.thumb;
|
||||
this.category = galleryInfo.category;
|
||||
this.posted = galleryInfo.posted;
|
||||
this.uploader = galleryInfo.uploader;
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
public BookmarkInfo(int page, long time) {
|
||||
this.page = page;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitleJpn() {
|
||||
return titleJpn;
|
||||
}
|
||||
|
||||
public void setTitleJpn(String titleJpn) {
|
||||
this.titleJpn = titleJpn;
|
||||
}
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getPosted() {
|
||||
return posted;
|
||||
}
|
||||
|
||||
public void setPosted(String posted) {
|
||||
this.posted = posted;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public float getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public String getSimpleLanguage() {
|
||||
return simpleLanguage;
|
||||
}
|
||||
|
||||
public void setSimpleLanguage(String simpleLanguage) {
|
||||
this.simpleLanguage = simpleLanguage;
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(this.page);
|
||||
dest.writeLong(this.time);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface BookmarksDao extends BasicDao<BookmarkInfo> {
|
||||
@Insert
|
||||
long insert(BookmarkInfo bookmark);
|
||||
|
||||
@Delete
|
||||
void delete(BookmarkInfo bookmark);
|
||||
|
||||
@Query("SELECT * FROM BOOKMARKS ORDER BY TIME DESC")
|
||||
List<BookmarkInfo> list();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "DOWNLOAD_DIRNAME")
|
||||
public class DownloadDirname {
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "GID")
|
||||
private long gid;
|
||||
@ColumnInfo(name = "DIRNAME")
|
||||
private String dirname;
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getDirname() {
|
||||
return dirname;
|
||||
}
|
||||
|
||||
public void setDirname(String dirname) {
|
||||
this.dirname = dirname;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DownloadDirnameDao extends BasicDao<DownloadDirname> {
|
||||
@Query("SELECT * FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
|
||||
DownloadDirname load(long gid);
|
||||
|
||||
@Update
|
||||
void update(DownloadDirname downloadDirname);
|
||||
|
||||
@Insert
|
||||
long insert(DownloadDirname downloadDirname);
|
||||
|
||||
@Query("DELETE FROM DOWNLOAD_DIRNAME WHERE GID = :gid")
|
||||
void deleteByKey(long gid);
|
||||
|
||||
@Query("DELETE FROM DOWNLOAD_DIRNAME")
|
||||
void deleteAll();
|
||||
|
||||
@Query("SELECT * FROM DOWNLOAD_DIRNAME")
|
||||
List<DownloadDirname> list();
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Ignore;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
|
||||
@Entity(tableName = "DOWNLOADS")
|
||||
public class DownloadInfo extends GalleryInfo {
|
||||
public static final Creator<DownloadInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public DownloadInfo createFromParcel(Parcel source) {
|
||||
return new DownloadInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadInfo[] newArray(int size) {
|
||||
return new DownloadInfo[size];
|
||||
}
|
||||
};
|
||||
public static final int STATE_INVALID = -1;
|
||||
public static final int STATE_NONE = 0;
|
||||
public static final int STATE_WAIT = 1;
|
||||
public static final int STATE_DOWNLOAD = 2;
|
||||
public static final int STATE_FINISH = 3;
|
||||
public static final int STATE_FAILED = 4;
|
||||
@ColumnInfo(name = "STATE")
|
||||
public int state;
|
||||
@ColumnInfo(name = "LEGACY")
|
||||
public int legacy;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
@ColumnInfo(name = "LABEL")
|
||||
public String label;
|
||||
@Ignore
|
||||
public long speed;
|
||||
@Ignore
|
||||
public long remaining;
|
||||
@Ignore
|
||||
public int finished;
|
||||
@Ignore
|
||||
public int downloaded;
|
||||
@Ignore
|
||||
public int total;
|
||||
|
||||
DownloadInfo() {
|
||||
}
|
||||
|
||||
protected DownloadInfo(Parcel in) {
|
||||
super(in);
|
||||
this.state = in.readInt();
|
||||
this.legacy = in.readInt();
|
||||
this.time = in.readLong();
|
||||
this.label = in.readString();
|
||||
}
|
||||
|
||||
public DownloadInfo(GalleryInfo galleryInfo) {
|
||||
this.gid = galleryInfo.gid;
|
||||
this.token = galleryInfo.token;
|
||||
this.title = galleryInfo.title;
|
||||
this.titleJpn = galleryInfo.titleJpn;
|
||||
this.thumb = galleryInfo.thumb;
|
||||
this.category = galleryInfo.category;
|
||||
this.posted = galleryInfo.posted;
|
||||
this.uploader = galleryInfo.uploader;
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
public DownloadInfo(int state, int legacy, long time, String label, long speed, long remaining,
|
||||
int finished, int downloaded, int total) {
|
||||
this.state = state;
|
||||
this.legacy = legacy;
|
||||
this.time = time;
|
||||
this.label = label;
|
||||
this.speed = speed;
|
||||
this.remaining = remaining;
|
||||
this.finished = finished;
|
||||
this.downloaded = downloaded;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitleJpn() {
|
||||
return titleJpn;
|
||||
}
|
||||
|
||||
public void setTitleJpn(String titleJpn) {
|
||||
this.titleJpn = titleJpn;
|
||||
}
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getPosted() {
|
||||
return posted;
|
||||
}
|
||||
|
||||
public void setPosted(String posted) {
|
||||
this.posted = posted;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public float getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public String getSimpleLanguage() {
|
||||
return simpleLanguage;
|
||||
}
|
||||
|
||||
public void setSimpleLanguage(String simpleLanguage) {
|
||||
this.simpleLanguage = simpleLanguage;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public int getLegacy() {
|
||||
return legacy;
|
||||
}
|
||||
|
||||
public void setLegacy(int legacy) {
|
||||
this.legacy = legacy;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(this.state);
|
||||
dest.writeInt(this.legacy);
|
||||
dest.writeLong(this.time);
|
||||
dest.writeString(this.label);
|
||||
}
|
||||
|
||||
public long getSpeed() {
|
||||
return this.speed;
|
||||
}
|
||||
|
||||
public void setSpeed(long speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public long getRemaining() {
|
||||
return this.remaining;
|
||||
}
|
||||
|
||||
public void setRemaining(long remaining) {
|
||||
this.remaining = remaining;
|
||||
}
|
||||
|
||||
public int getFinished() {
|
||||
return this.finished;
|
||||
}
|
||||
|
||||
public void setFinished(int finished) {
|
||||
this.finished = finished;
|
||||
}
|
||||
|
||||
public int getDownloaded() {
|
||||
return this.downloaded;
|
||||
}
|
||||
|
||||
public void setDownloaded(int downloaded) {
|
||||
this.downloaded = downloaded;
|
||||
}
|
||||
|
||||
public int getTotal() {
|
||||
return this.total;
|
||||
}
|
||||
|
||||
public void setTotal(int total) {
|
||||
this.total = total;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "DOWNLOAD_LABELS")
|
||||
public class DownloadLabel {
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "_id")
|
||||
private Long id;
|
||||
@ColumnInfo(name = "LABEL")
|
||||
private String label;
|
||||
@ColumnInfo(name = "TIME")
|
||||
private long time;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DownloadLabelDao extends BasicDao<DownloadLabel> {
|
||||
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC")
|
||||
List<DownloadLabel> list();
|
||||
|
||||
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
|
||||
List<DownloadLabel> list(int offset, int limit);
|
||||
|
||||
@Update
|
||||
void update(List<DownloadLabel> downloadLabels);
|
||||
|
||||
@Update
|
||||
void update(DownloadLabel downloadLabel);
|
||||
|
||||
@Insert
|
||||
long insert(DownloadLabel downloadLabel);
|
||||
|
||||
@Delete
|
||||
void delete(DownloadLabel downloadLabel);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DownloadsDao extends BasicDao<DownloadInfo> {
|
||||
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC")
|
||||
List<DownloadInfo> list();
|
||||
|
||||
@Query("SELECT * FROM DOWNLOADS WHERE GID = :gid")
|
||||
DownloadInfo load(long gid);
|
||||
|
||||
@Update
|
||||
void update(DownloadInfo downloadInfo);
|
||||
|
||||
@Insert
|
||||
long insert(DownloadInfo downloadInfo);
|
||||
|
||||
@Delete
|
||||
void delete(DownloadInfo downloadInfo);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
@Database(entities = {BookmarkInfo.class, DownloadInfo.class, DownloadLabel.class, DownloadDirname.class, Filter.class, HistoryInfo.class, LocalFavoriteInfo.class, QuickSearch.class}, version = 4, exportSchema = false)
|
||||
public abstract class EhDatabase extends RoomDatabase {
|
||||
public abstract BookmarksDao bookmarksBao();
|
||||
|
||||
public abstract DownloadDirnameDao downloadDirnameDao();
|
||||
|
||||
public abstract DownloadLabelDao downloadLabelDao();
|
||||
|
||||
public abstract DownloadsDao downloadsDao();
|
||||
|
||||
public abstract FilterDao filterDao();
|
||||
|
||||
public abstract HistoryDao historyDao();
|
||||
|
||||
public abstract LocalFavoritesDao localFavoritesDao();
|
||||
|
||||
public abstract QuickSearchDao quickSearchDao();
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.hippo.util.HashCodeUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
|
||||
@Entity(tableName = "FILTER")
|
||||
public class Filter {
|
||||
@ColumnInfo(name = "MODE")
|
||||
public int mode;
|
||||
@ColumnInfo(name = "TEXT")
|
||||
public String text;
|
||||
@ColumnInfo(name = "ENABLE")
|
||||
public Boolean enable;
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "_id")
|
||||
private Long id;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public Boolean getEnable() {
|
||||
return enable;
|
||||
}
|
||||
|
||||
public void setEnable(Boolean enable) {
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HashCodeUtils.hashCode(mode, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Filter)) {
|
||||
return false;
|
||||
}
|
||||
Filter filter = (Filter) o;
|
||||
return filter.mode == mode && ObjectUtils.equal(filter.text, text);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface FilterDao extends BasicDao<Filter> {
|
||||
@Query("SELECT * FROM FILTER")
|
||||
List<Filter> list();
|
||||
|
||||
@Update
|
||||
void update(Filter filter);
|
||||
|
||||
@Insert
|
||||
long insert(Filter filter);
|
||||
|
||||
@Delete
|
||||
void delete(Filter filter);
|
||||
|
||||
@Query("SELECT * FROM FILTER WHERE TEXT = :text AND MODE = :mode")
|
||||
Filter load(String text, int mode);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.paging.PagingSource;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface HistoryDao extends BasicDao<HistoryInfo> {
|
||||
@Query("SELECT * FROM HISTORY WHERE GID = :gid")
|
||||
HistoryInfo load(long gid);
|
||||
|
||||
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC")
|
||||
List<HistoryInfo> list();
|
||||
|
||||
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC LIMIT :limit OFFSET :offset")
|
||||
List<HistoryInfo> list(int offset, int limit);
|
||||
|
||||
@Query("SELECT * FROM HISTORY ORDER BY TIME DESC")
|
||||
PagingSource<Integer, HistoryInfo> listLazy();
|
||||
|
||||
@Update
|
||||
void update(HistoryInfo historyInfo);
|
||||
|
||||
@Insert
|
||||
long insert(HistoryInfo historyInfo);
|
||||
|
||||
@Delete
|
||||
void delete(HistoryInfo historyInfos);
|
||||
|
||||
@Delete
|
||||
void delete(List<HistoryInfo> historyInfos);
|
||||
|
||||
@Query("DELETE FROM HISTORY")
|
||||
void deleteAll();
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
|
||||
@Entity(tableName = "HISTORY")
|
||||
public class HistoryInfo extends GalleryInfo {
|
||||
public static final Creator<HistoryInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public HistoryInfo createFromParcel(Parcel source) {
|
||||
return new HistoryInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HistoryInfo[] newArray(int size) {
|
||||
return new HistoryInfo[size];
|
||||
}
|
||||
};
|
||||
@ColumnInfo(name = "MODE")
|
||||
public int mode;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
|
||||
protected HistoryInfo(Parcel in) {
|
||||
super(in);
|
||||
this.mode = in.readInt();
|
||||
this.time = in.readLong();
|
||||
}
|
||||
|
||||
public HistoryInfo(GalleryInfo galleryInfo) {
|
||||
this.gid = galleryInfo.gid;
|
||||
this.token = galleryInfo.token;
|
||||
this.title = galleryInfo.title;
|
||||
this.titleJpn = galleryInfo.titleJpn;
|
||||
this.thumb = galleryInfo.thumb;
|
||||
this.category = galleryInfo.category;
|
||||
this.posted = galleryInfo.posted;
|
||||
this.uploader = galleryInfo.uploader;
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
public HistoryInfo(int mode, long time) {
|
||||
this.mode = mode;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitleJpn() {
|
||||
return titleJpn;
|
||||
}
|
||||
|
||||
public void setTitleJpn(String titleJpn) {
|
||||
this.titleJpn = titleJpn;
|
||||
}
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getPosted() {
|
||||
return posted;
|
||||
}
|
||||
|
||||
public void setPosted(String posted) {
|
||||
this.posted = posted;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public float getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public String getSimpleLanguage() {
|
||||
return simpleLanguage;
|
||||
}
|
||||
|
||||
public void setSimpleLanguage(String simpleLanguage) {
|
||||
this.simpleLanguage = simpleLanguage;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(this.mode);
|
||||
dest.writeLong(this.time);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
|
||||
@Entity(tableName = "LOCAL_FAVORITES")
|
||||
public class LocalFavoriteInfo extends GalleryInfo {
|
||||
|
||||
public static final Creator<LocalFavoriteInfo> CREATOR = new Creator<>() {
|
||||
@Override
|
||||
public LocalFavoriteInfo createFromParcel(Parcel source) {
|
||||
return new LocalFavoriteInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalFavoriteInfo[] newArray(int size) {
|
||||
return new LocalFavoriteInfo[size];
|
||||
}
|
||||
};
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
|
||||
protected LocalFavoriteInfo(Parcel in) {
|
||||
super(in);
|
||||
this.time = in.readLong();
|
||||
}
|
||||
|
||||
public LocalFavoriteInfo(GalleryInfo galleryInfo) {
|
||||
this.gid = galleryInfo.gid;
|
||||
this.token = galleryInfo.token;
|
||||
this.title = galleryInfo.title;
|
||||
this.titleJpn = galleryInfo.titleJpn;
|
||||
this.thumb = galleryInfo.thumb;
|
||||
this.category = galleryInfo.category;
|
||||
this.posted = galleryInfo.posted;
|
||||
this.uploader = galleryInfo.uploader;
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
public LocalFavoriteInfo() {
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
public void setGid(long gid) {
|
||||
this.gid = gid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getTitleJpn() {
|
||||
return titleJpn;
|
||||
}
|
||||
|
||||
public void setTitleJpn(String titleJpn) {
|
||||
this.titleJpn = titleJpn;
|
||||
}
|
||||
|
||||
public String getThumb() {
|
||||
return thumb;
|
||||
}
|
||||
|
||||
public void setThumb(String thumb) {
|
||||
this.thumb = thumb;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getPosted() {
|
||||
return posted;
|
||||
}
|
||||
|
||||
public void setPosted(String posted) {
|
||||
this.posted = posted;
|
||||
}
|
||||
|
||||
public String getUploader() {
|
||||
return uploader;
|
||||
}
|
||||
|
||||
public void setUploader(String uploader) {
|
||||
this.uploader = uploader;
|
||||
}
|
||||
|
||||
public float getRating() {
|
||||
return rating;
|
||||
}
|
||||
|
||||
public void setRating(float rating) {
|
||||
this.rating = rating;
|
||||
}
|
||||
|
||||
public String getSimpleLanguage() {
|
||||
return simpleLanguage;
|
||||
}
|
||||
|
||||
public void setSimpleLanguage(String simpleLanguage) {
|
||||
this.simpleLanguage = simpleLanguage;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(this.time);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface LocalFavoritesDao extends BasicDao<LocalFavoriteInfo> {
|
||||
@Query("SELECT * FROM LOCAL_FAVORITES ORDER BY TIME DESC")
|
||||
List<LocalFavoriteInfo> list();
|
||||
|
||||
@Query("SELECT * FROM LOCAL_FAVORITES WHERE TITLE LIKE :title ORDER BY TIME DESC")
|
||||
List<LocalFavoriteInfo> list(String title);
|
||||
|
||||
@Query("SELECT * FROM LOCAL_FAVORITES WHERE GID = :gid")
|
||||
LocalFavoriteInfo load(long gid);
|
||||
|
||||
@Insert
|
||||
long insert(LocalFavoriteInfo localFavoriteInfo);
|
||||
|
||||
@Delete
|
||||
void delete(LocalFavoriteInfo localFavoriteInfo);
|
||||
|
||||
@Query("DELETE FROM LOCAL_FAVORITES WHERE GID = :gid")
|
||||
void deleteByKey(long gid);
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(tableName = "QUICK_SEARCH")
|
||||
public class QuickSearch {
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "_id")
|
||||
public Long id;
|
||||
@ColumnInfo(name = "NAME")
|
||||
public String name;
|
||||
@ColumnInfo(name = "MODE")
|
||||
public int mode;
|
||||
@ColumnInfo(name = "CATEGORY")
|
||||
public int category;
|
||||
@ColumnInfo(name = "KEYWORD")
|
||||
public String keyword;
|
||||
@ColumnInfo(name = "ADVANCE_SEARCH")
|
||||
public int advanceSearch;
|
||||
@ColumnInfo(name = "MIN_RATING")
|
||||
public int minRating;
|
||||
@ColumnInfo(name = "PAGE_FROM")
|
||||
public int pageFrom;
|
||||
@ColumnInfo(name = "PAGE_TO")
|
||||
public int pageTo;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public void setKeyword(String keyword) {
|
||||
this.keyword = keyword;
|
||||
}
|
||||
|
||||
public int getAdvanceSearch() {
|
||||
return advanceSearch;
|
||||
}
|
||||
|
||||
public void setAdvanceSearch(int advanceSearch) {
|
||||
this.advanceSearch = advanceSearch;
|
||||
}
|
||||
|
||||
public int getMinRating() {
|
||||
return minRating;
|
||||
}
|
||||
|
||||
public void setMinRating(int minRating) {
|
||||
this.minRating = minRating;
|
||||
}
|
||||
|
||||
public int getPageFrom() {
|
||||
return pageFrom;
|
||||
}
|
||||
|
||||
public void setPageFrom(int pageFrom) {
|
||||
this.pageFrom = pageFrom;
|
||||
}
|
||||
|
||||
public int getPageTo() {
|
||||
return pageTo;
|
||||
}
|
||||
|
||||
public void setPageTo(int pageTo) {
|
||||
this.pageTo = pageTo;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(long time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.hippo.ehviewer.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.Query;
|
||||
import androidx.room.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface QuickSearchDao extends BasicDao<QuickSearch> {
|
||||
@Query("SELECT * FROM QUICK_SEARCH ORDER BY TIME ASC")
|
||||
List<QuickSearch> list();
|
||||
|
||||
@Query("SELECT * FROM QUICK_SEARCH ORDER BY TIME ASC LIMIT :limit OFFSET :offset")
|
||||
List<QuickSearch> list(int offset, int limit);
|
||||
|
||||
@Update
|
||||
void update(List<QuickSearch> downloadLabels);
|
||||
|
||||
@Update
|
||||
void update(QuickSearch quickSearch);
|
||||
|
||||
@Insert
|
||||
long insert(QuickSearch quickSearch);
|
||||
|
||||
@Delete
|
||||
void delete(QuickSearch quickSearch);
|
||||
}
|
||||
@@ -516,7 +516,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
|
||||
DownloadInfo info = mAllInfoMap.get(gid);
|
||||
if (info != null) {
|
||||
// Remove from DB
|
||||
EhDB.removeDownloadInfo(info);
|
||||
EhDB.removeDownloadInfo(info.gid);
|
||||
|
||||
// Remove all list and map
|
||||
mAllInfoList.remove(info);
|
||||
@@ -552,7 +552,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
|
||||
}
|
||||
|
||||
// Remove from DB
|
||||
EhDB.removeDownloadInfo(info);
|
||||
EhDB.removeDownloadInfo(info.gid);
|
||||
|
||||
// Remove from all info map
|
||||
mAllInfoList.remove(info);
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
/*
|
||||
* 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.download;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationChannelCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.scene.DownloadsScene;
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.collect.LongList;
|
||||
import com.hippo.yorozuya.collect.SparseJBArray;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class DownloadService extends Service implements DownloadManager.DownloadListener {
|
||||
|
||||
public static final String ACTION_START = "start";
|
||||
public static final String ACTION_START_RANGE = "start_range";
|
||||
public static final String ACTION_START_ALL = "start_all";
|
||||
public static final String ACTION_STOP = "stop";
|
||||
public static final String ACTION_STOP_RANGE = "stop_range";
|
||||
public static final String ACTION_STOP_CURRENT = "stop_current";
|
||||
public static final String ACTION_STOP_ALL = "stop_all";
|
||||
public static final String ACTION_DELETE = "delete";
|
||||
public static final String ACTION_DELETE_RANGE = "delete_range";
|
||||
|
||||
public static final String ACTION_CLEAR = "clear";
|
||||
|
||||
public static final String KEY_GALLERY_INFO = "gallery_info";
|
||||
public static final String KEY_LABEL = "label";
|
||||
public static final String KEY_GID = "gid";
|
||||
public static final String KEY_GID_LIST = "gid_list";
|
||||
|
||||
private static final int ID_DOWNLOADING = 1;
|
||||
private static final int ID_DOWNLOADED = 2;
|
||||
private static final int ID_509 = 3;
|
||||
private final static SparseJBArray sItemStateArray = new SparseJBArray();
|
||||
private final static SparseJLArray<String> sItemTitleArray = new SparseJLArray<>();
|
||||
private static int sFailedCount;
|
||||
private static int sFinishedCount;
|
||||
private static int sDownloadedCount;
|
||||
@Nullable
|
||||
private NotificationManagerCompat mNotifyManager;
|
||||
@Nullable
|
||||
private DownloadManager mDownloadManager;
|
||||
private NotificationCompat.Builder mDownloadingBuilder;
|
||||
private NotificationCompat.Builder mDownloadedBuilder;
|
||||
private NotificationCompat.Builder m509dBuilder;
|
||||
private NotificationDelay mDownloadingDelay;
|
||||
private NotificationDelay mDownloadedDelay;
|
||||
private NotificationDelay m509Delay;
|
||||
private String CHANNEL_ID;
|
||||
|
||||
public static void clear() {
|
||||
sFailedCount = 0;
|
||||
sFinishedCount = 0;
|
||||
sDownloadedCount = 0;
|
||||
sItemStateArray.clear();
|
||||
sItemTitleArray.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
CHANNEL_ID = getPackageName() + ".download";
|
||||
mNotifyManager = NotificationManagerCompat.from(this);
|
||||
mNotifyManager.createNotificationChannel(
|
||||
new NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
|
||||
.setName(getString(R.string.download_service))
|
||||
.build());
|
||||
mDownloadManager = EhApplication.getDownloadManager(getApplicationContext());
|
||||
mDownloadManager.setDownloadListener(this);
|
||||
|
||||
ensureDownloadingBuilder();
|
||||
|
||||
mDownloadingBuilder.setContentTitle(getString(R.string.download_service))
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setProgress(0, 0, true);
|
||||
|
||||
startForeground(ID_DOWNLOADING, mDownloadingBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
mNotifyManager = null;
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.setDownloadListener(null);
|
||||
mDownloadManager = null;
|
||||
}
|
||||
mDownloadingBuilder = null;
|
||||
mDownloadedBuilder = null;
|
||||
m509dBuilder = null;
|
||||
if (mDownloadingDelay != null) {
|
||||
mDownloadingDelay.release();
|
||||
}
|
||||
if (mDownloadedDelay != null) {
|
||||
mDownloadedDelay.release();
|
||||
}
|
||||
if (m509Delay != null) {
|
||||
m509Delay.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
handleIntent(intent);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
String action = null;
|
||||
if (intent != null) {
|
||||
action = intent.getAction();
|
||||
}
|
||||
|
||||
if (ACTION_START.equals(action)) {
|
||||
GalleryInfo gi = intent.getParcelableExtra(KEY_GALLERY_INFO);
|
||||
String label = intent.getStringExtra(KEY_LABEL);
|
||||
if (gi != null && mDownloadManager != null) {
|
||||
mDownloadManager.startDownload(gi, label);
|
||||
}
|
||||
} else if (ACTION_START_RANGE.equals(action)) {
|
||||
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager.startRangeDownload(gidList);
|
||||
}
|
||||
} else if (ACTION_START_ALL.equals(action)) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.startAllDownload();
|
||||
}
|
||||
} else if (ACTION_STOP.equals(action)) {
|
||||
long gid = intent.getLongExtra(KEY_GID, -1);
|
||||
if (gid != -1 && mDownloadManager != null) {
|
||||
mDownloadManager.stopDownload(gid);
|
||||
}
|
||||
} else if (ACTION_STOP_CURRENT.equals(action)) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.stopCurrentDownload();
|
||||
}
|
||||
} else if (ACTION_STOP_RANGE.equals(action)) {
|
||||
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager.stopRangeDownload(gidList);
|
||||
}
|
||||
} else if (ACTION_STOP_ALL.equals(action)) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.stopAllDownload();
|
||||
}
|
||||
} else if (ACTION_DELETE.equals(action)) {
|
||||
long gid = intent.getLongExtra(KEY_GID, -1);
|
||||
if (gid != -1 && mDownloadManager != null) {
|
||||
mDownloadManager.deleteDownload(gid);
|
||||
}
|
||||
} else if (ACTION_DELETE_RANGE.equals(action)) {
|
||||
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager.deleteRangeDownload(gidList);
|
||||
}
|
||||
} else if (ACTION_CLEAR.equals(action)) {
|
||||
clear();
|
||||
}
|
||||
|
||||
checkStopSelf();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
throw new IllegalStateException("No bindService");
|
||||
}
|
||||
|
||||
private void ensureDownloadingBuilder() {
|
||||
if (mDownloadingBuilder != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent stopAllIntent = new Intent(this, DownloadService.class);
|
||||
stopAllIntent.setAction(ACTION_STOP_ALL);
|
||||
PendingIntent piStopAll = PendingIntent.getService(this, 0, stopAllIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
mDownloadingBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.addAction(R.drawable.v_pause_x24, getString(R.string.stat_download_action_stop_all), piStopAll)
|
||||
.setShowWhen(false)
|
||||
.setChannelId(CHANNEL_ID);
|
||||
|
||||
mDownloadingDelay = new NotificationDelay(this, mNotifyManager, mDownloadingBuilder, ID_DOWNLOADING);
|
||||
}
|
||||
|
||||
private void ensureDownloadedBuilder() {
|
||||
if (mDownloadedBuilder != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent clearIntent = new Intent(this, DownloadService.class);
|
||||
clearIntent.setAction(ACTION_CLEAR);
|
||||
PendingIntent piClear = PendingIntent.getService(this, 0, clearIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(DownloadsScene.KEY_ACTION, DownloadsScene.ACTION_CLEAR_DOWNLOAD_SERVICE);
|
||||
Intent activityIntent = new Intent(this, MainActivity.class);
|
||||
activityIntent.setAction(StageActivity.ACTION_START_SCENE);
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene.class.getName());
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle);
|
||||
PendingIntent piActivity = PendingIntent.getActivity(DownloadService.this, 0,
|
||||
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
mDownloadedBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentTitle(getString(R.string.stat_download_done_title))
|
||||
.setDeleteIntent(piClear)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(piActivity);
|
||||
|
||||
mDownloadedDelay = new NotificationDelay(this, mNotifyManager, mDownloadedBuilder, ID_DOWNLOADED);
|
||||
}
|
||||
|
||||
private void ensure509Builder() {
|
||||
if (m509dBuilder != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m509dBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_baseline_warning_24)
|
||||
.setContentText(getString(R.string.stat_509_alert_title))
|
||||
.setContentText(getString(R.string.stat_509_alert_text))
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.stat_509_alert_text)))
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR);
|
||||
|
||||
m509Delay = new NotificationDelay(this, mNotifyManager, m509dBuilder, ID_509);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGet509() {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.stopAllDownload();
|
||||
}
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensure509Builder();
|
||||
m509dBuilder.setWhen(System.currentTimeMillis());
|
||||
m509Delay.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(DownloadInfo info) {
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureDownloadingBuilder();
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putLong(DownloadsScene.KEY_GID, info.gid);
|
||||
Intent activityIntent = new Intent(this, MainActivity.class);
|
||||
activityIntent.setAction(StageActivity.ACTION_START_SCENE);
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene.class.getName());
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle);
|
||||
PendingIntent piActivity = PendingIntent.getActivity(DownloadService.this, 0,
|
||||
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
mDownloadingBuilder.setContentTitle(EhUtils.getSuitableTitle(info))
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setProgress(0, 0, true)
|
||||
.setContentIntent(piActivity);
|
||||
|
||||
mDownloadingDelay.startForeground();
|
||||
}
|
||||
|
||||
private void onUpdate(DownloadInfo info) {
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
ensureDownloadingBuilder();
|
||||
|
||||
long speed = info.speed;
|
||||
if (speed < 0) {
|
||||
speed = 0;
|
||||
}
|
||||
String text = FileUtils.humanReadableByteCount(speed, false) + "/s";
|
||||
long remaining = info.remaining;
|
||||
if (remaining >= 0) {
|
||||
text = getString(R.string.download_speed_text_2, text, ReadableTime.getShortTimeInterval(remaining));
|
||||
} else {
|
||||
text = getString(R.string.download_speed_text, text);
|
||||
}
|
||||
mDownloadingBuilder.setContentTitle(EhUtils.getSuitableTitle(info))
|
||||
.setContentText(text)
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(text))
|
||||
.setSubText(info.total == -1 || info.finished == -1 ? null : info.finished + "/" + info.total)
|
||||
.setProgress(info.total, info.finished, false);
|
||||
|
||||
mDownloadingDelay.startForeground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownload(DownloadInfo info) {
|
||||
onUpdate(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetPage(DownloadInfo info) {
|
||||
onUpdate(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(DownloadInfo info) {
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null != mDownloadingDelay) {
|
||||
mDownloadingDelay.cancel();
|
||||
}
|
||||
|
||||
ensureDownloadedBuilder();
|
||||
|
||||
boolean finish = info.state == DownloadInfo.STATE_FINISH;
|
||||
long gid = info.gid;
|
||||
int index = sItemStateArray.indexOfKey(gid);
|
||||
if (index < 0) { // Not contain
|
||||
sItemStateArray.put(gid, finish);
|
||||
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info));
|
||||
sDownloadedCount++;
|
||||
if (finish) {
|
||||
sFinishedCount++;
|
||||
} else {
|
||||
sFailedCount++;
|
||||
}
|
||||
} else { // Contain
|
||||
boolean oldFinish = sItemStateArray.valueAt(index);
|
||||
sItemStateArray.put(gid, finish);
|
||||
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info));
|
||||
if (oldFinish && !finish) {
|
||||
sFinishedCount--;
|
||||
sFailedCount++;
|
||||
} else if (!oldFinish && finish) {
|
||||
sFinishedCount++;
|
||||
sFailedCount--;
|
||||
}
|
||||
}
|
||||
|
||||
String text;
|
||||
boolean needStyle;
|
||||
if (sFinishedCount != 0 && sFailedCount == 0) {
|
||||
if (sFinishedCount == 1) {
|
||||
if (sItemTitleArray.size() >= 1) {
|
||||
text = getString(R.string.stat_download_done_line_succeeded, sItemTitleArray.valueAt(0));
|
||||
} else {
|
||||
Log.d("TAG", "WTF, sItemTitleArray is null");
|
||||
text = getString(R.string.error_unknown);
|
||||
}
|
||||
needStyle = false;
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_succeeded, sFinishedCount);
|
||||
needStyle = true;
|
||||
}
|
||||
} else if (sFinishedCount == 0 && sFailedCount != 0) {
|
||||
if (sFailedCount == 1) {
|
||||
if (sItemTitleArray.size() >= 1) {
|
||||
text = getString(R.string.stat_download_done_line_failed, sItemTitleArray.valueAt(0));
|
||||
} else {
|
||||
Log.d("TAG", "WTF, sItemTitleArray is null");
|
||||
text = getString(R.string.error_unknown);
|
||||
}
|
||||
needStyle = false;
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_failed, sFailedCount);
|
||||
needStyle = true;
|
||||
}
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_mix, sFinishedCount, sFailedCount);
|
||||
needStyle = true;
|
||||
}
|
||||
|
||||
NotificationCompat.InboxStyle style;
|
||||
if (needStyle) {
|
||||
style = new NotificationCompat.InboxStyle();
|
||||
style.setBigContentTitle(getString(R.string.stat_download_done_title));
|
||||
SparseJBArray stateArray = sItemStateArray;
|
||||
for (int i = 0, n = stateArray.size(); i < n; i++) {
|
||||
long id = stateArray.keyAt(i);
|
||||
boolean fin = stateArray.valueAt(i);
|
||||
String title = sItemTitleArray.get(id);
|
||||
if (title == null) {
|
||||
continue;
|
||||
}
|
||||
style.addLine(getString(fin ? R.string.stat_download_done_line_succeeded :
|
||||
R.string.stat_download_done_line_failed, title));
|
||||
}
|
||||
} else {
|
||||
style = null;
|
||||
}
|
||||
|
||||
mDownloadedBuilder.setContentText(text)
|
||||
.setStyle(style)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setNumber(sDownloadedCount);
|
||||
|
||||
mDownloadedDelay.show();
|
||||
|
||||
checkStopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DownloadInfo info) {
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null != mDownloadingDelay) {
|
||||
mDownloadingDelay.cancel();
|
||||
}
|
||||
|
||||
checkStopSelf();
|
||||
}
|
||||
|
||||
private void checkStopSelf() {
|
||||
if (mDownloadManager == null || mDownloadManager.isIdle()) {
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Include all notification in one delay
|
||||
// Avoid frequent notification
|
||||
private static class NotificationDelay implements Runnable {
|
||||
|
||||
private static final int OPS_NOTIFY = 0;
|
||||
private static final int OPS_CANCEL = 1;
|
||||
private static final int OPS_START_FOREGROUND = 2;
|
||||
private static final long DELAY = 1000; // 1s
|
||||
private final NotificationManagerCompat mNotifyManager;
|
||||
private final NotificationCompat.Builder mBuilder;
|
||||
private final int mId;
|
||||
private Service mService;
|
||||
private long mLastTime;
|
||||
private boolean mPosted;
|
||||
// false for show, true for cancel
|
||||
@Ops
|
||||
private int mOps;
|
||||
|
||||
public NotificationDelay(Service service, NotificationManagerCompat notifyManager,
|
||||
NotificationCompat.Builder builder, int id) {
|
||||
mService = service;
|
||||
mNotifyManager = notifyManager;
|
||||
mBuilder = builder;
|
||||
mId = id;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
mService = null;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_NOTIFY;
|
||||
} else {
|
||||
long now = SystemClock.currentThreadTimeMillis();
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
mNotifyManager.notify(mId, mBuilder.build());
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_NOTIFY;
|
||||
mPosted = true;
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY);
|
||||
}
|
||||
mLastTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_CANCEL;
|
||||
} else {
|
||||
long now = SystemClock.currentThreadTimeMillis();
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
mNotifyManager.cancel(mId);
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_CANCEL;
|
||||
mPosted = true;
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startForeground() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_START_FOREGROUND;
|
||||
} else {
|
||||
long now = SystemClock.currentThreadTimeMillis();
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
if (mService != null) {
|
||||
mService.startForeground(mId, mBuilder.build());
|
||||
}
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_START_FOREGROUND;
|
||||
mPosted = true;
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mPosted = false;
|
||||
switch (mOps) {
|
||||
case OPS_NOTIFY:
|
||||
mNotifyManager.notify(mId, mBuilder.build());
|
||||
break;
|
||||
case OPS_CANCEL:
|
||||
mNotifyManager.cancel(mId);
|
||||
break;
|
||||
case OPS_START_FOREGROUND:
|
||||
if (mService != null) {
|
||||
mService.startForeground(mId, mBuilder.build());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef({OPS_NOTIFY, OPS_CANCEL, OPS_START_FOREGROUND})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Ops {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,550 +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.download
|
||||
|
||||
import android.Manifest
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.scene.DownloadsScene
|
||||
import com.hippo.scene.StageActivity
|
||||
import com.hippo.util.ReadableTime
|
||||
import com.hippo.yorozuya.FileUtils
|
||||
import com.hippo.yorozuya.SimpleHandler
|
||||
import com.hippo.yorozuya.collect.LongList
|
||||
import com.hippo.yorozuya.collect.SparseJBArray
|
||||
import com.hippo.yorozuya.collect.SparseJLArray
|
||||
|
||||
class DownloadService : Service(), DownloadManager.DownloadListener {
|
||||
private var mNotifyManager: NotificationManagerCompat? = null
|
||||
private var mDownloadManager: DownloadManager? = null
|
||||
private var mDownloadingBuilder: NotificationCompat.Builder? = null
|
||||
private var mDownloadedBuilder: NotificationCompat.Builder? = null
|
||||
private var m509dBuilder: NotificationCompat.Builder? = null
|
||||
private var mDownloadingDelay: NotificationDelay? = null
|
||||
private var mDownloadedDelay: NotificationDelay? = null
|
||||
private var m509Delay: NotificationDelay? = null
|
||||
private var mChannelID: String? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
mChannelID = "$packageName.download"
|
||||
mNotifyManager = NotificationManagerCompat.from(this)
|
||||
mNotifyManager!!.createNotificationChannel(
|
||||
NotificationChannelCompat.Builder(
|
||||
mChannelID!!,
|
||||
NotificationManagerCompat.IMPORTANCE_LOW
|
||||
)
|
||||
.setName(getString(R.string.download_service))
|
||||
.build()
|
||||
)
|
||||
mDownloadManager = EhApplication.getDownloadManager(applicationContext)
|
||||
mDownloadManager!!.setDownloadListener(this)
|
||||
ensureDownloadingBuilder()
|
||||
mDownloadingBuilder!!.setContentTitle(getString(R.string.download_service))
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setProgress(0, 0, true)
|
||||
startForeground(ID_DOWNLOADING, mDownloadingBuilder!!.build())
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mNotifyManager = null
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.setDownloadListener(null)
|
||||
mDownloadManager = null
|
||||
}
|
||||
mDownloadingBuilder = null
|
||||
mDownloadedBuilder = null
|
||||
m509dBuilder = null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
handleIntent(intent)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun handleIntent(intent: Intent?) {
|
||||
var action: String? = null
|
||||
if (intent != null) {
|
||||
action = intent.action
|
||||
}
|
||||
if (ACTION_START == action) {
|
||||
val gi = intent!!.getParcelableExtra<GalleryInfo>(KEY_GALLERY_INFO)
|
||||
val label = intent.getStringExtra(KEY_LABEL)
|
||||
if (gi != null && mDownloadManager != null) {
|
||||
mDownloadManager!!.startDownload(gi, label)
|
||||
}
|
||||
} else if (ACTION_START_RANGE == action) {
|
||||
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager!!.startRangeDownload(gidList)
|
||||
}
|
||||
} else if (ACTION_START_ALL == action) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.startAllDownload()
|
||||
}
|
||||
} else if (ACTION_STOP == action) {
|
||||
val gid = intent!!.getLongExtra(KEY_GID, -1)
|
||||
if (gid != -1L && mDownloadManager != null) {
|
||||
mDownloadManager!!.stopDownload(gid)
|
||||
}
|
||||
} else if (ACTION_STOP_CURRENT == action) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.stopCurrentDownload()
|
||||
}
|
||||
} else if (ACTION_STOP_RANGE == action) {
|
||||
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager!!.stopRangeDownload(gidList)
|
||||
}
|
||||
} else if (ACTION_STOP_ALL == action) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.stopAllDownload()
|
||||
}
|
||||
} else if (ACTION_DELETE == action) {
|
||||
val gid = intent!!.getLongExtra(KEY_GID, -1)
|
||||
if (gid != -1L && mDownloadManager != null) {
|
||||
mDownloadManager!!.deleteDownload(gid)
|
||||
}
|
||||
} else if (ACTION_DELETE_RANGE == action) {
|
||||
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager!!.deleteRangeDownload(gidList)
|
||||
}
|
||||
} else if (ACTION_CLEAR == action) {
|
||||
clear()
|
||||
}
|
||||
checkStopSelf()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
throw IllegalStateException("No bindService")
|
||||
}
|
||||
|
||||
private fun ensureDownloadingBuilder() {
|
||||
if (mDownloadingBuilder != null) {
|
||||
return
|
||||
}
|
||||
val stopAllIntent = Intent(this, DownloadService::class.java)
|
||||
stopAllIntent.action = ACTION_STOP_ALL
|
||||
val piStopAll =
|
||||
PendingIntent.getService(this, 0, stopAllIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
mDownloadingBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.addAction(
|
||||
R.drawable.v_pause_x24,
|
||||
getString(R.string.stat_download_action_stop_all),
|
||||
piStopAll
|
||||
)
|
||||
.setShowWhen(false)
|
||||
.setChannelId(mChannelID!!)
|
||||
mDownloadingDelay =
|
||||
NotificationDelay(this, mNotifyManager!!, mDownloadingBuilder!!, ID_DOWNLOADING)
|
||||
}
|
||||
|
||||
private fun ensureDownloadedBuilder() {
|
||||
if (mDownloadedBuilder != null) {
|
||||
return
|
||||
}
|
||||
val clearIntent = Intent(this, DownloadService::class.java)
|
||||
clearIntent.action = ACTION_CLEAR
|
||||
val piClear = PendingIntent.getService(this, 0, clearIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(DownloadsScene.KEY_ACTION, DownloadsScene.ACTION_CLEAR_DOWNLOAD_SERVICE)
|
||||
val activityIntent = Intent(this, MainActivity::class.java)
|
||||
activityIntent.action = StageActivity.ACTION_START_SCENE
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene::class.java.name)
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle)
|
||||
val piActivity = PendingIntent.getActivity(
|
||||
this@DownloadService, 0,
|
||||
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
mDownloadedBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentTitle(getString(R.string.stat_download_done_title))
|
||||
.setDeleteIntent(piClear)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(piActivity)
|
||||
mDownloadedDelay =
|
||||
NotificationDelay(this, mNotifyManager!!, mDownloadedBuilder!!, ID_DOWNLOADED)
|
||||
}
|
||||
|
||||
private fun ensure509Builder() {
|
||||
if (m509dBuilder != null) {
|
||||
return
|
||||
}
|
||||
m509dBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
|
||||
.setSmallIcon(R.drawable.ic_baseline_warning_24)
|
||||
.setContentText(getString(R.string.stat_509_alert_title))
|
||||
.setContentText(getString(R.string.stat_509_alert_text))
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle().bigText(getString(R.string.stat_509_alert_text))
|
||||
)
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
m509Delay = NotificationDelay(this, mNotifyManager!!, m509dBuilder!!, ID_509)
|
||||
}
|
||||
|
||||
override fun onGet509() {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.stopAllDownload()
|
||||
}
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
ensure509Builder()
|
||||
m509dBuilder!!.setWhen(System.currentTimeMillis())
|
||||
m509Delay!!.show()
|
||||
}
|
||||
|
||||
override fun onStart(info: DownloadInfo) {
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
ensureDownloadingBuilder()
|
||||
val bundle = Bundle()
|
||||
bundle.putLong(DownloadsScene.KEY_GID, info.gid)
|
||||
val activityIntent = Intent(this, MainActivity::class.java)
|
||||
activityIntent.action = StageActivity.ACTION_START_SCENE
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene::class.java.name)
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle)
|
||||
val piActivity = PendingIntent.getActivity(
|
||||
this@DownloadService, 0,
|
||||
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
mDownloadingBuilder!!.setContentTitle(EhUtils.getSuitableTitle(info))
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setProgress(0, 0, true)
|
||||
.setContentIntent(piActivity)
|
||||
mDownloadingDelay!!.startForeground()
|
||||
}
|
||||
|
||||
private fun onUpdate(info: DownloadInfo) {
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
ensureDownloadingBuilder()
|
||||
var speed = info.speed
|
||||
if (speed < 0) {
|
||||
speed = 0
|
||||
}
|
||||
var text = FileUtils.humanReadableByteCount(speed, false) + "/s"
|
||||
val remaining = info.remaining
|
||||
text = if (remaining >= 0) {
|
||||
getString(
|
||||
R.string.download_speed_text_2,
|
||||
text,
|
||||
ReadableTime.getShortTimeInterval(remaining)
|
||||
)
|
||||
} else {
|
||||
getString(R.string.download_speed_text, text)
|
||||
}
|
||||
mDownloadingBuilder!!.setContentTitle(EhUtils.getSuitableTitle(info))
|
||||
.setContentText(text)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
|
||||
.setSubText(if (info.total == -1 || info.finished == -1) null else info.finished.toString() + "/" + info.total)
|
||||
.setProgress(info.total, info.finished, false)
|
||||
mDownloadingDelay!!.startForeground()
|
||||
}
|
||||
|
||||
override fun onDownload(info: DownloadInfo) {
|
||||
onUpdate(info)
|
||||
}
|
||||
|
||||
override fun onGetPage(info: DownloadInfo) {
|
||||
onUpdate(info)
|
||||
}
|
||||
|
||||
override fun onFinish(info: DownloadInfo) {
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
if (null != mDownloadingDelay) {
|
||||
mDownloadingDelay!!.cancel()
|
||||
}
|
||||
ensureDownloadedBuilder()
|
||||
val finish = info.state == DownloadInfo.STATE_FINISH
|
||||
val gid = info.gid
|
||||
val index = sItemStateArray.indexOfKey(gid)
|
||||
if (index < 0) { // Not contain
|
||||
sItemStateArray.put(gid, finish)
|
||||
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info))
|
||||
sDownloadedCount++
|
||||
if (finish) {
|
||||
sFinishedCount++
|
||||
} else {
|
||||
sFailedCount++
|
||||
}
|
||||
} else { // Contain
|
||||
val oldFinish = sItemStateArray.valueAt(index)
|
||||
sItemStateArray.put(gid, finish)
|
||||
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info))
|
||||
if (oldFinish && !finish) {
|
||||
sFinishedCount--
|
||||
sFailedCount++
|
||||
} else if (!oldFinish && finish) {
|
||||
sFinishedCount++
|
||||
sFailedCount--
|
||||
}
|
||||
}
|
||||
val text: String
|
||||
val needStyle: Boolean
|
||||
if (sFinishedCount != 0 && sFailedCount == 0) {
|
||||
if (sFinishedCount == 1) {
|
||||
text = if (sItemTitleArray.size() >= 1) {
|
||||
getString(
|
||||
R.string.stat_download_done_line_succeeded,
|
||||
sItemTitleArray.valueAt(0)
|
||||
)
|
||||
} else {
|
||||
Log.d("TAG", "WTF, sItemTitleArray is null")
|
||||
getString(R.string.error_unknown)
|
||||
}
|
||||
needStyle = false
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_succeeded, sFinishedCount)
|
||||
needStyle = true
|
||||
}
|
||||
} else if (sFinishedCount == 0 && sFailedCount != 0) {
|
||||
if (sFailedCount == 1) {
|
||||
text = if (sItemTitleArray.size() >= 1) {
|
||||
getString(
|
||||
R.string.stat_download_done_line_failed,
|
||||
sItemTitleArray.valueAt(0)
|
||||
)
|
||||
} else {
|
||||
Log.d("TAG", "WTF, sItemTitleArray is null")
|
||||
getString(R.string.error_unknown)
|
||||
}
|
||||
needStyle = false
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_failed, sFailedCount)
|
||||
needStyle = true
|
||||
}
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_mix, sFinishedCount, sFailedCount)
|
||||
needStyle = true
|
||||
}
|
||||
val style: NotificationCompat.InboxStyle?
|
||||
if (needStyle) {
|
||||
style = NotificationCompat.InboxStyle()
|
||||
style.setBigContentTitle(getString(R.string.stat_download_done_title))
|
||||
val stateArray = sItemStateArray
|
||||
var i = 0
|
||||
val n = stateArray.size()
|
||||
while (i < n) {
|
||||
val id = stateArray.keyAt(i)
|
||||
val fin = stateArray.valueAt(i)
|
||||
val title = sItemTitleArray[id]
|
||||
if (title == null) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
style.addLine(
|
||||
getString(
|
||||
if (fin) R.string.stat_download_done_line_succeeded else R.string.stat_download_done_line_failed,
|
||||
title
|
||||
)
|
||||
)
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
style = null
|
||||
}
|
||||
mDownloadedBuilder!!.setContentText(text)
|
||||
.setStyle(style)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setNumber(sDownloadedCount)
|
||||
mDownloadedDelay!!.show()
|
||||
checkStopSelf()
|
||||
}
|
||||
|
||||
override fun onCancel(info: DownloadInfo) {
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
if (null != mDownloadingDelay) {
|
||||
mDownloadingDelay!!.cancel()
|
||||
}
|
||||
checkStopSelf()
|
||||
}
|
||||
|
||||
private fun checkStopSelf() {
|
||||
if (mDownloadManager == null || mDownloadManager!!.isIdle) {
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
private class NotificationDelay(
|
||||
private var mService: Service,
|
||||
private val mNotifyManager: NotificationManagerCompat,
|
||||
private val mBuilder: NotificationCompat.Builder,
|
||||
private val mId: Int
|
||||
) : Runnable {
|
||||
private var mLastTime: Long = 0
|
||||
private var mPosted = false
|
||||
|
||||
// false for show, true for cancel
|
||||
@Ops
|
||||
private var mOps = 0
|
||||
|
||||
fun show() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_NOTIFY
|
||||
} else {
|
||||
val now = SystemClock.currentThreadTimeMillis()
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
mService,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
mNotifyManager.notify(mId, mBuilder.build())
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_NOTIFY
|
||||
mPosted = true
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY)
|
||||
}
|
||||
mLastTime = now
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_CANCEL
|
||||
} else {
|
||||
val now = SystemClock.currentThreadTimeMillis()
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
mNotifyManager.cancel(mId)
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_CANCEL
|
||||
mPosted = true
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startForeground() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_START_FOREGROUND
|
||||
} else {
|
||||
val now = SystemClock.currentThreadTimeMillis()
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
mService.startForeground(mId, mBuilder.build())
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_START_FOREGROUND
|
||||
mPosted = true
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
mPosted = false
|
||||
when (mOps) {
|
||||
OPS_NOTIFY -> {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
mService,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
mNotifyManager.notify(mId, mBuilder.build())
|
||||
}
|
||||
OPS_CANCEL -> mNotifyManager.cancel(mId)
|
||||
OPS_START_FOREGROUND -> mService.startForeground(mId, mBuilder.build())
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef(OPS_NOTIFY, OPS_CANCEL, OPS_START_FOREGROUND)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class Ops
|
||||
companion object {
|
||||
private const val OPS_NOTIFY = 0
|
||||
private const val OPS_CANCEL = 1
|
||||
private const val OPS_START_FOREGROUND = 2
|
||||
private const val DELAY: Long = 1000 // 1s
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION_START = "start"
|
||||
const val ACTION_START_RANGE = "start_range"
|
||||
const val ACTION_START_ALL = "start_all"
|
||||
const val ACTION_STOP = "stop"
|
||||
const val ACTION_STOP_RANGE = "stop_range"
|
||||
const val ACTION_STOP_CURRENT = "stop_current"
|
||||
const val ACTION_STOP_ALL = "stop_all"
|
||||
const val ACTION_DELETE = "delete"
|
||||
const val ACTION_DELETE_RANGE = "delete_range"
|
||||
const val ACTION_CLEAR = "clear"
|
||||
const val KEY_GALLERY_INFO = "gallery_info"
|
||||
const val KEY_LABEL = "label"
|
||||
const val KEY_GID = "gid"
|
||||
const val KEY_GID_LIST = "gid_list"
|
||||
private const val ID_DOWNLOADING = 1
|
||||
private const val ID_DOWNLOADED = 2
|
||||
private const val ID_509 = 3
|
||||
private val sItemStateArray = SparseJBArray()
|
||||
private val sItemTitleArray = SparseJLArray<String>()
|
||||
private var sFailedCount = 0
|
||||
private var sFinishedCount = 0
|
||||
private var sDownloadedCount = 0
|
||||
|
||||
@JvmStatic
|
||||
fun clear() {
|
||||
sFailedCount = 0
|
||||
sFinishedCount = 0
|
||||
sDownloadedCount = 0
|
||||
sItemStateArray.clear()
|
||||
sItemTitleArray.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,13 @@ 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.InputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -47,15 +49,11 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
public static PVLock pv = new PVLock(0);
|
||||
private final UriArchiveAccessor archiveAccessor;
|
||||
private final Stack<Integer> requests = new Stack<>();
|
||||
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
|
||||
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private final LinkedHashMap<Integer, InputStream> streams = new LinkedHashMap<>();
|
||||
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private Thread archiveThread;
|
||||
private Thread[] decodeThread = new Thread[] {
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask())
|
||||
};
|
||||
|
||||
private Thread decodeThread;
|
||||
private volatile int size = STATE_WAIT;
|
||||
private String error;
|
||||
|
||||
@@ -77,22 +75,25 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
int id = sIdGenerator.incrementAndGet();
|
||||
|
||||
archiveThread = new PriorityThread(
|
||||
new ArchiveHostTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
new ArchiveTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
archiveThread.start();
|
||||
for (Thread i : decodeThread) {
|
||||
i.start();
|
||||
}
|
||||
|
||||
decodeThread = new PriorityThread(
|
||||
new DecodeTask(), "DecodeTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
decodeThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
|
||||
if (archiveThread != null) {
|
||||
archiveThread.interrupt();
|
||||
archiveThread = null;
|
||||
}
|
||||
for (Thread i : decodeThread) {
|
||||
i.interrupt();
|
||||
if (decodeThread != null) {
|
||||
decodeThread.interrupt();
|
||||
decodeThread = null;
|
||||
}
|
||||
try {
|
||||
archiveAccessor.close();
|
||||
@@ -110,11 +111,11 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
protected void onRequest(int index) {
|
||||
boolean inDecodeTask;
|
||||
synchronized (streams) {
|
||||
inDecodeTask = streams.containsKey(index);
|
||||
inDecodeTask = streams.containsKey(index) || index == decodingIndex.get();
|
||||
}
|
||||
|
||||
synchronized (requests) {
|
||||
boolean inArchiveTask = requests.contains(index);
|
||||
boolean inArchiveTask = requests.contains(index) || index == extractingIndex.get();
|
||||
if (!inArchiveTask && !inDecodeTask) {
|
||||
requests.add(index);
|
||||
requests.notify();
|
||||
@@ -167,14 +168,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
return Integer.toString(index);
|
||||
}
|
||||
|
||||
private class ArchiveHostTask implements Runnable {
|
||||
public final Thread[] archiveThreads = new Thread[] {
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask())
|
||||
};
|
||||
|
||||
private class ArchiveTask implements Runnable {
|
||||
private void waitPasswd() throws InterruptedException {
|
||||
showPasswd.sendEmptyMessage(0);
|
||||
pv.p();
|
||||
@@ -232,26 +226,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Thread i : archiveThreads) {
|
||||
i.start();
|
||||
}
|
||||
|
||||
Object o = new Object();
|
||||
synchronized (o) {
|
||||
try {
|
||||
o.wait();
|
||||
} catch (InterruptedException e) {
|
||||
for (Thread i : archiveThreads) {
|
||||
i.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ArchiveTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int index;
|
||||
synchronized (requests) {
|
||||
@@ -265,26 +240,28 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
continue;
|
||||
}
|
||||
index = requests.pop();
|
||||
extractingIndex.lazySet(index);
|
||||
}
|
||||
|
||||
// Check index valid
|
||||
if (index < 0 || index >= size) {
|
||||
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
|
||||
continue;
|
||||
}
|
||||
|
||||
Pipe pipe = new Pipe();
|
||||
|
||||
synchronized (streams) {
|
||||
if (streams.get(index) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
long addr = archiveAccessor.extracttoOutputStream(index);
|
||||
|
||||
synchronized (streams) {
|
||||
streams.put(index, addr);
|
||||
streams.put(index, pipe.getInputStream());
|
||||
streams.notify();
|
||||
}
|
||||
|
||||
archiveAccessor.extracttoOutputStream(index, pipe.getOutputFd());
|
||||
pipe.closeOutputFd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,29 +271,35 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
public void run() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int index;
|
||||
Long addr;
|
||||
InputStream stream;
|
||||
synchronized (streams) {
|
||||
if (streams.isEmpty()) {
|
||||
try {
|
||||
streams.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// Interrupted
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, Long> entry = iterator.next();
|
||||
Iterator<Map.Entry<Integer, InputStream>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, InputStream> entry = iterator.next();
|
||||
iterator.remove();
|
||||
index = entry.getKey();
|
||||
addr = entry.getValue();
|
||||
stream = entry.getValue();
|
||||
decodingIndex.lazySet(index);
|
||||
}
|
||||
|
||||
Image image = Image.decodeAddr(addr, true);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
try {
|
||||
Image image = Image.decode(stream, true);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
}
|
||||
} finally {
|
||||
decodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -259,7 +258,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = files[index].openInputStream();
|
||||
Image image = Image.decode(((FileInputStream) is).getFD(), true);
|
||||
Image image = Image.decode(is, true);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
class Pipe {
|
||||
private ParcelFileDescriptor[] fd;
|
||||
{
|
||||
try {
|
||||
fd = ParcelFileDescriptor.createPipe();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private final InputStream inputStream = new FileInputStream(fd[0].getFileDescriptor());
|
||||
|
||||
InputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
int getOutputFd() {
|
||||
return fd[1].getFd();
|
||||
}
|
||||
|
||||
void closeOutputFd() {
|
||||
try {
|
||||
fd[1].close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.preference
|
||||
|
||||
import android.content.*
|
||||
import android.os.Build
|
||||
import android.os.PersistableBundle
|
||||
import android.text.Html
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.ui.SettingsActivity
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
import com.hippo.preference.MessagePreference
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import java.util.*
|
||||
|
||||
class AccountPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : MessagePreference(context, attrs) {
|
||||
private val mActivity = context as SettingsActivity
|
||||
private var message: String? = null
|
||||
|
||||
init {
|
||||
val store = EhApplication.getEhCookieStore(context)
|
||||
val eCookies = store.getCookies(EhUrl.HOST_E.toHttpUrl())
|
||||
val exCookies = store.getCookies(EhUrl.HOST_EX.toHttpUrl())
|
||||
val cookies: MutableList<Cookie> = LinkedList(eCookies)
|
||||
cookies.addAll(exCookies)
|
||||
var ipbMemberId: String? = null
|
||||
var ipbPassHash: String? = null
|
||||
var igneous: String? = null
|
||||
var i = 0
|
||||
val n = cookies.size
|
||||
while (i < n) {
|
||||
val cookie = cookies[i]
|
||||
when (cookie.name) {
|
||||
EhCookieStore.KEY_IPD_MEMBER_ID -> ipbMemberId = cookie.value
|
||||
EhCookieStore.KEY_IPD_PASS_HASH -> ipbPassHash = cookie.value
|
||||
EhCookieStore.KEY_IGNEOUS -> igneous = cookie.value
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (ipbMemberId != null || ipbPassHash != null || igneous != null) {
|
||||
message = (EhCookieStore.KEY_IPD_MEMBER_ID + ": " + ipbMemberId + "<br>"
|
||||
+ EhCookieStore.KEY_IPD_PASS_HASH + ": " + ipbPassHash + "<br>"
|
||||
+ EhCookieStore.KEY_IGNEOUS + ": " + igneous)
|
||||
setDialogMessage(
|
||||
Html.fromHtml(
|
||||
context.getString(
|
||||
R.string.settings_eh_identity_cookies_signed,
|
||||
message
|
||||
), Html.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
message = message!!.replace("<br>", "\n")
|
||||
} else {
|
||||
setDialogMessage(context.getString(R.string.settings_eh_identity_cookies_tourist))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
|
||||
super.onPrepareDialogBuilder(builder)
|
||||
if (message != null) {
|
||||
builder.setNeutralButton(R.string.settings_eh_identity_cookies_copy) { dialog: DialogInterface?, which: Int ->
|
||||
val clipboardManager =
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
|
||||
val clipData = ClipData.newPlainText(null, message)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
clipData.apply {
|
||||
description.extras = PersistableBundle().apply {
|
||||
putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
|
||||
}
|
||||
}
|
||||
clipboardManager?.setPrimaryClip(clipData)
|
||||
|
||||
// There is no need to notify user by toast since Tiramisu have its clipboard own logic
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
mActivity.showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
|
||||
this@AccountPreference.onClick(dialog, which)
|
||||
}
|
||||
}
|
||||
builder.setPositiveButton(R.string.settings_eh_sign_out) { _: DialogInterface?, _: Int ->
|
||||
EhUtils.signOut(context)
|
||||
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2018 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.preference;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.text.Html;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhCookieStore;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.ui.SettingsActivity;
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene;
|
||||
import com.hippo.preference.MessagePreference;
|
||||
import com.hippo.util.ClipboardUtil;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class IdentityCookiePreference extends MessagePreference {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private final SettingsActivity mActivity;
|
||||
private String message;
|
||||
|
||||
public IdentityCookiePreference(Context context) {
|
||||
super(context);
|
||||
mActivity = (SettingsActivity) context;
|
||||
init();
|
||||
}
|
||||
|
||||
public IdentityCookiePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mActivity = (SettingsActivity) context;
|
||||
init();
|
||||
}
|
||||
|
||||
public IdentityCookiePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mActivity = (SettingsActivity) context;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
EhCookieStore store = EhApplication.getEhCookieStore(getContext());
|
||||
List<Cookie> eCookies = store.getCookies(HttpUrl.get(EhUrl.HOST_E));
|
||||
List<Cookie> exCookies = store.getCookies(HttpUrl.get(EhUrl.HOST_EX));
|
||||
List<Cookie> cookies = new LinkedList<>(eCookies);
|
||||
cookies.addAll(exCookies);
|
||||
|
||||
String ipbMemberId = null;
|
||||
String ipbPassHash = null;
|
||||
String igneous = null;
|
||||
|
||||
for (int i = 0, n = cookies.size(); i < n; i++) {
|
||||
Cookie cookie = cookies.get(i);
|
||||
switch (cookie.name()) {
|
||||
case EhCookieStore.KEY_IPD_MEMBER_ID:
|
||||
ipbMemberId = cookie.value();
|
||||
break;
|
||||
case EhCookieStore.KEY_IPD_PASS_HASH:
|
||||
ipbPassHash = cookie.value();
|
||||
break;
|
||||
case EhCookieStore.KEY_IGNEOUS:
|
||||
igneous = cookie.value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ipbMemberId != null || ipbPassHash != null || igneous != null) {
|
||||
message = EhCookieStore.KEY_IPD_MEMBER_ID + ": " + ipbMemberId + "<br>"
|
||||
+ EhCookieStore.KEY_IPD_PASS_HASH + ": " + ipbPassHash + "<br>"
|
||||
+ EhCookieStore.KEY_IGNEOUS + ": " + igneous;
|
||||
setDialogMessage(Html.fromHtml(getContext().getString(R.string.settings_eh_identity_cookies_signed, message), Html.FROM_HTML_MODE_LEGACY));
|
||||
message = message.replace("<br>", "\n");
|
||||
} else {
|
||||
setDialogMessage(getContext().getString(R.string.settings_eh_identity_cookies_tourist));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
if (message != null) {
|
||||
builder.setPositiveButton(R.string.settings_eh_identity_cookies_copy, (dialog, which) -> {
|
||||
ClipboardUtil.addTextToClipboard(message);
|
||||
mActivity.showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT);
|
||||
|
||||
IdentityCookiePreference.this.onClick(dialog, which);
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
} else {
|
||||
builder.setPositiveButton(android.R.string.ok, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.preference;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.ui.SettingsActivity;
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene;
|
||||
import com.hippo.preference.MessagePreference;
|
||||
|
||||
public class SignOutPreference extends MessagePreference {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private final SettingsActivity mActivity;
|
||||
|
||||
public SignOutPreference(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
mActivity = (SettingsActivity) context;
|
||||
}
|
||||
|
||||
public SignOutPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
mActivity = (SettingsActivity) context;
|
||||
}
|
||||
|
||||
public SignOutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
mActivity = (SettingsActivity) context;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setDialogMessage(getContext().getString(R.string.settings_eh_sign_out_warning));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
builder.setPositiveButton(R.string.settings_eh_sign_out_yes, this);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
super.onDialogClosed(positiveResult);
|
||||
if (positiveResult) {
|
||||
EhUtils.signOut(getContext());
|
||||
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,14 @@ package com.hippo.ehviewer.preference
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.Preference
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.hippo.Native
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.preference.MessagePreference
|
||||
|
||||
class VersionPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Preference(context, attrs) {
|
||||
) : MessagePreference(context, attrs) {
|
||||
init {
|
||||
setTitle(R.string.settings_about_version)
|
||||
@Suppress("DEPRECATION") val version: String = try {
|
||||
@@ -35,4 +37,32 @@ class VersionPreference @JvmOverloads constructor(
|
||||
}
|
||||
summary = version
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,6 @@ import com.hippo.yorozuya.thread.PriorityThread;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -1097,6 +1096,33 @@ public final class SpiderQueen implements Runnable {
|
||||
void onGetImageFailure(int index, String error);
|
||||
}
|
||||
|
||||
private static class AutoCloseInputStream extends InputStream {
|
||||
|
||||
private final InputStreamPipe mPipe;
|
||||
private final InputStream mIs;
|
||||
|
||||
public AutoCloseInputStream(InputStreamPipe pipe, InputStream is) {
|
||||
mPipe = pipe;
|
||||
mIs = is;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return mIs.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
||||
return mIs.read(buffer, byteOffset, byteCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mPipe.close();
|
||||
mPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
private class SpiderWorker implements Runnable {
|
||||
|
||||
private final long mGid;
|
||||
@@ -1667,7 +1693,7 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
pipe.obtain();
|
||||
try {
|
||||
is = pipe.open();
|
||||
is = new AutoCloseInputStream(pipe, pipe.open());
|
||||
} catch (IOException e) {
|
||||
// Can't open pipe
|
||||
error = GetText.getString(R.string.error_reading_failed);
|
||||
@@ -1677,17 +1703,7 @@ public final class SpiderQueen implements Runnable {
|
||||
}
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
image = Image.decode(((FileInputStream) is).getFD(), true);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
image = Image.decode(is, true);
|
||||
if (image == null) {
|
||||
error = GetText.getString(R.string.error_decoding_failed);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.hippo.ehviewer.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
@@ -43,7 +42,6 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class CommonOperations {
|
||||
|
||||
@@ -112,14 +110,8 @@ public final class CommonOperations {
|
||||
startDownload(activity, Collections.singletonList(galleryInfo), forceDefault);
|
||||
}
|
||||
|
||||
// TODO Add context if activity and context are different style
|
||||
public static void startDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Objects.requireNonNull(EhApplication.getInstance().getTopActivity()).checkAndRequestNotificationPermission();
|
||||
}
|
||||
doStartDownload(activity, galleryInfos, forceDefault);
|
||||
}
|
||||
|
||||
private static void doStartDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
||||
final DownloadManager dm = EhApplication.getDownloadManager(activity);
|
||||
|
||||
LongList toStart = new LongList();
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
|
||||
import rikka.core.res.ResourcesKt;
|
||||
import rikka.insets.WindowInsetsHelper;
|
||||
import rikka.layoutinflater.view.LayoutInflaterFactory;
|
||||
|
||||
public abstract class EhActivity extends AppCompatActivity {
|
||||
|
||||
private static final String THEME_DEFAULT = "DEFAULT";
|
||||
private static final String THEME_BLACK = "BLACK";
|
||||
|
||||
public static boolean isBlackNightTheme() {
|
||||
return Settings.getBoolean("black_dark_theme", false);
|
||||
}
|
||||
|
||||
public static String getTheme(Context context) {
|
||||
if (isBlackNightTheme() && isNightMode(context.getResources().getConfiguration()))
|
||||
return THEME_BLACK;
|
||||
return THEME_DEFAULT;
|
||||
}
|
||||
|
||||
public static boolean isNightMode(Configuration configuration) {
|
||||
return (configuration.uiMode & Configuration.UI_MODE_NIGHT_YES) > 0;
|
||||
}
|
||||
|
||||
@StyleRes
|
||||
public int getThemeStyleRes(Context context) {
|
||||
if (THEME_BLACK.equals(getTheme(context))) {
|
||||
return R.style.ThemeOverlay_Black;
|
||||
}
|
||||
return R.style.ThemeOverlay;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
|
||||
if (getParent() == null) {
|
||||
theme.applyStyle(resid, true);
|
||||
} else {
|
||||
try {
|
||||
theme.setTo(getParent().getTheme());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
theme.applyStyle(resid, false);
|
||||
}
|
||||
theme.applyStyle(getThemeStyleRes(this), true);
|
||||
super.onApplyThemeResource(theme, R.style.ThemeOverlay, first);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNightModeChanged(int mode) {
|
||||
getTheme().applyStyle(getThemeStyleRes(this), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
getLayoutInflater().setFactory2((new LayoutInflaterFactory(getDelegate())).addOnViewCreatedListener(WindowInsetsHelper.getLISTENER()));
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
|
||||
Window window = getWindow();
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
|
||||
window.getDecorView().post(() -> {
|
||||
WindowInsets rootWindowInsets = window.getDecorView().getRootWindowInsets();
|
||||
if (rootWindowInsets != null && rootWindowInsets.getSystemWindowInsetBottom() >= Resources.getSystem().getDisplayMetrics().density * 40) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
window.setNavigationBarDividerColor(getColor(R.color.navigation_bar_divider));
|
||||
}
|
||||
window.setNavigationBarColor(ResourcesKt.resolveColor(getTheme(), android.R.attr.navigationBarColor) & 0x00ffffff | -0x20000000);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.setNavigationBarContrastEnforced(false);
|
||||
}
|
||||
} else {
|
||||
window.setNavigationBarColor(Color.TRANSPARENT);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.setNavigationBarContrastEnforced(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
((EhApplication) getApplication()).registerActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
((EhApplication) getApplication()).unregisterActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (Settings.getEnabledSecurity()) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE);
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,156 +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.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.content.res.Resources.Theme
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.EhApplication.locked_last_leave_time
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
|
||||
import rikka.core.res.resolveColor
|
||||
import rikka.insets.WindowInsetsHelper
|
||||
import rikka.layoutinflater.view.LayoutInflaterFactory
|
||||
|
||||
abstract class EhActivity : AppCompatActivity() {
|
||||
@StyleRes
|
||||
fun getThemeStyleRes(context: Context): Int {
|
||||
return if (THEME_BLACK == getTheme(context)) {
|
||||
R.style.ThemeOverlay_Black
|
||||
} else R.style.ThemeOverlay
|
||||
}
|
||||
|
||||
override fun onApplyThemeResource(theme: Theme, resid: Int, first: Boolean) {
|
||||
if (parent == null) {
|
||||
theme.applyStyle(resid, true)
|
||||
} else {
|
||||
try {
|
||||
theme.setTo(parent.theme)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
theme.applyStyle(resid, false)
|
||||
}
|
||||
theme.applyStyle(getThemeStyleRes(this), true)
|
||||
super.onApplyThemeResource(theme, R.style.ThemeOverlay, first)
|
||||
}
|
||||
|
||||
override fun onNightModeChanged(mode: Int) {
|
||||
theme.applyStyle(getThemeStyleRes(this), true)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
layoutInflater.factory2 =
|
||||
LayoutInflaterFactory(delegate).addOnViewCreatedListener(
|
||||
WindowInsetsHelper.LISTENER
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
val window = window
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
window.decorView.post {
|
||||
val rootWindowInsets = window.decorView.rootWindowInsets
|
||||
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
|
||||
}
|
||||
window.navigationBarColor =
|
||||
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
} else {
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = true
|
||||
}
|
||||
}
|
||||
}
|
||||
(application as EhApplication).registerActivity(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
(application as EhApplication).unregisterActivity(this)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val locked_resume_time = System.currentTimeMillis() / 1000
|
||||
val locked_delay_time = locked_resume_time - locked_last_leave_time
|
||||
if (Settings.getSecurity() && locked_delay_time >= Settings.getSecurityDelay() * 60
|
||||
&& isAuthenticationSupported(this) && EhApplication.locked
|
||||
) {
|
||||
startActivity(Intent(this, SecurityActivity::class.java))
|
||||
}
|
||||
super.onResume()
|
||||
if (Settings.getEnabledSecurity()) {
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE
|
||||
)
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
|
||||
private val requestPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) {
|
||||
Settings.putNotificationRequired()
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||
fun checkAndRequestNotificationPermission() {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (Settings.getNotificationRequired())
|
||||
return
|
||||
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val THEME_DEFAULT = "DEFAULT"
|
||||
private const val THEME_BLACK = "BLACK"
|
||||
private val isBlackNightTheme: Boolean
|
||||
get() = Settings.getBoolean("black_dark_theme", false)
|
||||
|
||||
fun getTheme(context: Context): String {
|
||||
return if (isBlackNightTheme && isNightMode(context.resources.configuration)) THEME_BLACK else THEME_DEFAULT
|
||||
}
|
||||
|
||||
private fun isNightMode(configuration: Configuration): Boolean {
|
||||
return configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,8 +67,8 @@ import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.materialswitch.MaterialSwitch;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.BuildConfig;
|
||||
@@ -226,9 +226,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
private int mSize;
|
||||
private int mCurrentIndex;
|
||||
private int mSavingPage = -1;
|
||||
private EditTextDialogBuilder builder;
|
||||
private boolean dialogShown = false;
|
||||
private AlertDialog dialog;
|
||||
|
||||
private void buildProvider() {
|
||||
if (mGalleryProvider != null) {
|
||||
@@ -303,16 +300,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
break;
|
||||
}
|
||||
super.attachBaseContext(newBase);
|
||||
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> {
|
||||
if (result && mSavingPage != -1) {
|
||||
saveImage(mSavingPage);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSavingPage = -1;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"WrongConstant"})
|
||||
@@ -406,22 +394,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -494,7 +477,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
FrameLayout mainLayout = (FrameLayout) ViewUtils.$$(this, R.id.main);
|
||||
mainLayout.addView(new GalleryGuideView(this));
|
||||
}
|
||||
}
|
||||
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> {
|
||||
if (result && mSavingPage != -1) {
|
||||
saveImage(mSavingPage);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSavingPage = -1;
|
||||
});
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
@@ -990,7 +982,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
|
||||
private void showPageDialog(final int page) {
|
||||
Resources resources = GalleryActivity.this.getResources();
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(GalleryActivity.this);
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
|
||||
builder.setTitle(resources.getString(R.string.page_menu_title, page + 1));
|
||||
|
||||
final CharSequence[] items;
|
||||
@@ -1054,66 +1046,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
}
|
||||
|
||||
private void showPasswdDialog() {
|
||||
if (!dialogShown) {
|
||||
dialogShown = true;
|
||||
dialog.show();
|
||||
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
GalleryActivity.this.onProvidePasswd();
|
||||
});
|
||||
}
|
||||
dialog.setOnCancelListener(v -> finish());
|
||||
}
|
||||
}
|
||||
|
||||
private void onProvidePasswd() {
|
||||
String passwd = builder.getText();
|
||||
if (passwd.isEmpty())
|
||||
builder.setError(getString(R.string.passwd_cannot_be_empty));
|
||||
else {
|
||||
ArchiveGalleryProvider.passwd = passwd;
|
||||
ArchiveGalleryProvider.pv.v();
|
||||
}
|
||||
}
|
||||
|
||||
private void onPasswdWrong() {
|
||||
builder.setError(getString(R.string.passwd_wrong));
|
||||
}
|
||||
|
||||
private void onPasswdCorrect() {
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
private static class ShowPasswdDialogHandler extends Handler {
|
||||
|
||||
//弱引用持有HandlerActivity , GC 回收时会被回收掉
|
||||
private final WeakReference<GalleryActivity> weakReference;
|
||||
|
||||
public ShowPasswdDialogHandler(GalleryActivity activity) {
|
||||
this.weakReference = new WeakReference(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
GalleryActivity activity = weakReference.get();
|
||||
super.handleMessage(msg);
|
||||
if (null != activity) {
|
||||
switch (msg.what) {
|
||||
case 0:
|
||||
activity.showPasswdDialog();
|
||||
break;
|
||||
case 1:
|
||||
activity.onPasswdWrong();
|
||||
break;
|
||||
case 2:
|
||||
activity.onPasswdCorrect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class GalleryMenuHelper implements DialogInterface.OnClickListener {
|
||||
|
||||
private final View mView;
|
||||
@@ -1286,7 +1218,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
}
|
||||
|
||||
private void onTapMenuArea() {
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(GalleryActivity.this);
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
|
||||
GalleryMenuHelper helper = new GalleryMenuHelper(builder.getContext());
|
||||
builder.setTitle(R.string.gallery_menu_title)
|
||||
.setView(helper.getView())
|
||||
@@ -1374,7 +1306,69 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,11 +42,12 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhTagDatabase;
|
||||
@@ -66,9 +67,11 @@ import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryPreviewsScene;
|
||||
import com.hippo.ehviewer.ui.scene.HistoryScene;
|
||||
import com.hippo.ehviewer.ui.scene.ProgressScene;
|
||||
import com.hippo.ehviewer.ui.scene.SecurityScene;
|
||||
import com.hippo.ehviewer.ui.scene.SelectSiteScene;
|
||||
import com.hippo.ehviewer.ui.scene.SignInScene;
|
||||
import com.hippo.ehviewer.ui.scene.SolidScene;
|
||||
import com.hippo.ehviewer.ui.scene.WarningScene;
|
||||
import com.hippo.ehviewer.ui.scene.WebViewSignInScene;
|
||||
import com.hippo.ehviewer.widget.EhStageLayout;
|
||||
import com.hippo.io.UniFileInputStreamPipe;
|
||||
@@ -95,6 +98,8 @@ public final class MainActivity extends StageActivity
|
||||
private static final String KEY_NAV_CHECKED_ITEM = "nav_checked_item";
|
||||
|
||||
static {
|
||||
registerLaunchMode(SecurityScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(WarningScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(SignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(WebViewSignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(CookieSignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
@@ -138,7 +143,11 @@ public final class MainActivity extends StageActivity
|
||||
@NonNull
|
||||
@Override
|
||||
protected Announcer getLaunchAnnouncer() {
|
||||
if (EhUtils.needSignedIn(this)) {
|
||||
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this)) {
|
||||
return new Announcer(SecurityScene.class);
|
||||
} else if (Settings.getShowWarning()) {
|
||||
return new Announcer(WarningScene.class);
|
||||
} else if (EhUtils.needSignedIn(this)) {
|
||||
return new Announcer(SignInScene.class);
|
||||
} else if (Settings.getSelectSite()) {
|
||||
return new Announcer(SelectSiteScene.class);
|
||||
@@ -152,7 +161,17 @@ public final class MainActivity extends StageActivity
|
||||
// Sometimes scene can't show directly
|
||||
private Announcer processAnnouncer(Announcer announcer) {
|
||||
if (0 == getSceneCount()) {
|
||||
if (EhUtils.needSignedIn(this)) {
|
||||
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this)) {
|
||||
Bundle newArgs = new Bundle();
|
||||
newArgs.putString(SecurityScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
||||
newArgs.putBundle(SecurityScene.KEY_TARGET_ARGS, announcer.getArgs());
|
||||
return new Announcer(SecurityScene.class).setArgs(newArgs);
|
||||
} else if (Settings.getShowWarning()) {
|
||||
Bundle newArgs = new Bundle();
|
||||
newArgs.putString(WarningScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
||||
newArgs.putBundle(WarningScene.KEY_TARGET_ARGS, announcer.getArgs());
|
||||
return new Announcer(WarningScene.class).setArgs(newArgs);
|
||||
} else if (EhUtils.needSignedIn(this)) {
|
||||
Bundle newArgs = new Bundle();
|
||||
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
||||
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.getArgs());
|
||||
@@ -288,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);
|
||||
|
||||
@@ -337,7 +357,7 @@ public final class MainActivity extends StageActivity
|
||||
break;
|
||||
}
|
||||
if (hasUnverified) {
|
||||
new BaseDialogBuilder(this)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.app_link_not_verified_title)
|
||||
.setMessage(R.string.app_link_not_verified_message)
|
||||
.setPositiveButton(R.string.open_settings, (dialogInterface, i) -> {
|
||||
@@ -363,7 +383,7 @@ public final class MainActivity extends StageActivity
|
||||
if (null == uniFile || uniFile.ensureDir()) {
|
||||
return;
|
||||
}
|
||||
new BaseDialogBuilder(this)
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.waring)
|
||||
.setMessage(R.string.invalid_download_location)
|
||||
.setPositiveButton(R.string.get_it, null)
|
||||
@@ -407,6 +427,9 @@ public final class MainActivity extends StageActivity
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this) && EhApplication.locked) {
|
||||
startScene(new Announcer(SecurityScene.class), true);
|
||||
}
|
||||
super.onResume();
|
||||
setNavCheckedItem(mNavCheckedItem);
|
||||
checkClipboardUrl();
|
||||
|
||||
@@ -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,65 +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;
|
||||
@@ -33,18 +32,13 @@ import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.BuildConfig;
|
||||
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(),
|
||||
@@ -85,7 +77,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
AlertDialog alertDialog = new BaseDialogBuilder(requireActivity())
|
||||
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.preference_dialog_task)
|
||||
.show();
|
||||
@@ -202,7 +194,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
AlertDialog alertDialog = new BaseDialogBuilder(requireActivity())
|
||||
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.preference_dialog_task)
|
||||
.show();
|
||||
@@ -236,12 +228,9 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
Preference dumpLogcat = findPreference(KEY_DUMP_LOGCAT);
|
||||
Preference clearMemoryCache = findPreference(KEY_CLEAR_MEMORY_CACHE);
|
||||
Preference appLanguage = findPreference(KEY_APP_LANGUAGE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
appLanguage.setVisible(false);
|
||||
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);
|
||||
@@ -253,7 +242,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
clearMemoryCache.setOnPreferenceClickListener(this);
|
||||
importData.setOnPreferenceClickListener(this);
|
||||
exportData.setOnPreferenceClickListener(this);
|
||||
backupFavorite.setOnPreferenceClickListener(this);
|
||||
|
||||
appLanguage.setOnPreferenceChangeListener(this);
|
||||
}
|
||||
@@ -300,66 +288,14 @@ 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.setIndex(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.setIndex(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();
|
||||
if (KEY_APP_LANGUAGE.equals(key) && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
if (KEY_APP_LANGUAGE.equals(key)) {
|
||||
if ("system".equals(newValue)) {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.wrap(Resources.getSystem().getConfiguration().getLocales()));
|
||||
} else {
|
||||
|
||||
@@ -70,8 +70,7 @@ public class BasePreferenceFragment extends PreferenceFragmentCompat
|
||||
.replace(R.id.fragment, classObj)
|
||||
.addToBackStack(null)
|
||||
.commitAllowingStateLoss();
|
||||
} catch (ClassNotFoundException | IllegalAccessException |
|
||||
java.lang.InstantiationException ignored) {
|
||||
} catch (ClassNotFoundException | IllegalAccessException | java.lang.InstantiationException ignored) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
@@ -99,7 +99,7 @@ public class DownloadFragment extends BasePreferenceFragment {
|
||||
if (KEY_DOWNLOAD_LOCATION.equals(key)) {
|
||||
UniFile file = Settings.getDownloadLocation();
|
||||
if (file != null && !UniFile.isFileUri(Settings.getDownloadLocation().getUri())) {
|
||||
new BaseDialogBuilder(requireContext())
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.settings_download_download_location)
|
||||
.setMessage(file.getUri().toString())
|
||||
.setPositiveButton(R.string.pick_new_download_location, (dialogInterface, i) -> openDirPickerL())
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhTagDatabase;
|
||||
|
||||
public class EhFragment extends BasePreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.eh_settings);
|
||||
|
||||
Preference theme = findPreference(Settings.KEY_THEME);
|
||||
Preference blackDarkTheme = findPreference(Settings.KEY_BLACK_DARK_THEME);
|
||||
Preference gallerySite = findPreference(Settings.KEY_GALLERY_SITE);
|
||||
Preference listMode = findPreference(Settings.KEY_LIST_MODE);
|
||||
Preference detailSize = findPreference(Settings.KEY_DETAIL_SIZE);
|
||||
Preference thumbSize = findPreference(Settings.KEY_THUMB_SIZE);
|
||||
Preference showTagTranslations = findPreference(Settings.KEY_SHOW_TAG_TRANSLATIONS);
|
||||
Preference tagTranslationsSource = findPreference("tag_translations_source");
|
||||
|
||||
theme.setOnPreferenceChangeListener(this);
|
||||
gallerySite.setOnPreferenceChangeListener(this);
|
||||
listMode.setOnPreferenceChangeListener(this);
|
||||
detailSize.setOnPreferenceChangeListener(this);
|
||||
thumbSize.setOnPreferenceChangeListener(this);
|
||||
showTagTranslations.setOnPreferenceChangeListener(this);
|
||||
blackDarkTheme.setOnPreferenceChangeListener(this);
|
||||
|
||||
if (!EhTagDatabase.isPossible(requireActivity())) {
|
||||
getPreferenceScreen().removePreference(showTagTranslations);
|
||||
getPreferenceScreen().removePreference(tagTranslationsSource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
String key = preference.getKey();
|
||||
if (Settings.KEY_THEME.equals(key)) {
|
||||
AppCompatDelegate.setDefaultNightMode(Integer.parseInt((String) newValue));
|
||||
requireActivity().recreate();
|
||||
return true;
|
||||
} else if (Settings.KEY_GALLERY_SITE.equals(key)) {
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
return true;
|
||||
} else if (Settings.KEY_LIST_MODE.equals(key)) {
|
||||
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)) {
|
||||
requireActivity().setResult(Activity.RESULT_OK);
|
||||
} else if (Settings.KEY_SHOW_TAG_TRANSLATIONS.equals(key)) {
|
||||
if (Boolean.TRUE.equals(newValue)) {
|
||||
EhTagDatabase.update(requireActivity());
|
||||
}
|
||||
} else if (Settings.KEY_BLACK_DARK_THEME.equals(key)) {
|
||||
if ((requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) > 0) {
|
||||
requireActivity().recreate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFragmentTitle() {
|
||||
return R.string.settings_eh;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.fragment
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.Preference
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
|
||||
class EhFragment : BasePreferenceFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.eh_settings)
|
||||
val account = findPreference<Preference>(Settings.KEY_ACCOUNT)
|
||||
val theme = findPreference<Preference>(Settings.KEY_THEME)
|
||||
val blackDarkTheme = findPreference<Preference>(Settings.KEY_BLACK_DARK_THEME)
|
||||
val gallerySite = findPreference<Preference>(Settings.KEY_GALLERY_SITE)
|
||||
val listMode = findPreference<Preference>(Settings.KEY_LIST_MODE)
|
||||
val listThumbSize = findPreference<Preference>(Settings.KEY_LIST_THUMB_SIZE)
|
||||
val detailSize = findPreference<Preference>(Settings.KEY_DETAIL_SIZE)
|
||||
val thumbSize = findPreference<Preference>(Settings.KEY_THUMB_SIZE)
|
||||
val showTagTranslations = findPreference<Preference>(Settings.KEY_SHOW_TAG_TRANSLATIONS)
|
||||
val tagTranslationsSource = findPreference<Preference>("tag_translations_source")
|
||||
Settings.getDisplayName()?.let { account?.summary = it }
|
||||
theme!!.onPreferenceChangeListener = this
|
||||
gallerySite!!.onPreferenceChangeListener = this
|
||||
listMode!!.onPreferenceChangeListener = this
|
||||
listThumbSize!!.onPreferenceChangeListener = this
|
||||
detailSize!!.onPreferenceChangeListener = this
|
||||
thumbSize!!.onPreferenceChangeListener = this
|
||||
showTagTranslations!!.onPreferenceChangeListener = this
|
||||
blackDarkTheme!!.onPreferenceChangeListener = this
|
||||
if (!EhTagDatabase.isPossible(requireActivity())) {
|
||||
preferenceScreen.removePreference(showTagTranslations)
|
||||
preferenceScreen.removePreference(tagTranslationsSource!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
|
||||
val key = preference.key
|
||||
if (Settings.KEY_THEME == key) {
|
||||
AppCompatDelegate.setDefaultNightMode(newValue as Int)
|
||||
requireActivity().recreate()
|
||||
return true
|
||||
} else if (Settings.KEY_GALLERY_SITE == key) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
return true
|
||||
} else if (Settings.KEY_LIST_MODE == key) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
return true
|
||||
} else if (Settings.KEY_LIST_THUMB_SIZE == key) {
|
||||
Settings.LIST_THUMB_SIZE_INITED = false
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
return true
|
||||
} else if (Settings.KEY_DETAIL_SIZE == key) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
} else if (Settings.KEY_THUMB_SIZE == key) {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
} else if (Settings.KEY_SHOW_TAG_TRANSLATIONS == key) {
|
||||
if (java.lang.Boolean.TRUE == newValue) {
|
||||
EhTagDatabase.update(requireActivity())
|
||||
}
|
||||
} else if (Settings.KEY_BLACK_DARK_THEME == key) {
|
||||
if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) {
|
||||
requireActivity().recreate()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getFragmentTitle(): Int {
|
||||
return R.string.settings_eh
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,9 @@ import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.checkbox.MaterialCheckBox
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhFilter
|
||||
@@ -112,7 +112,7 @@ class FilterFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
private fun showTipDialog() {
|
||||
BaseDialogBuilder(requireActivity())
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.filter)
|
||||
.setMessage(R.string.filter_tip)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
@@ -120,7 +120,7 @@ class FilterFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
private fun showAddFilterDialog() {
|
||||
val dialog = BaseDialogBuilder(requireActivity())
|
||||
val dialog = MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.add_filter)
|
||||
.setView(R.layout.dialog_add_filter)
|
||||
.setPositiveButton(R.string.add, null)
|
||||
@@ -131,7 +131,7 @@ class FilterFragment : BaseFragment() {
|
||||
|
||||
private fun showDeleteFilterDialog(filter: Filter) {
|
||||
val message = getString(R.string.delete_filter, filter.text)
|
||||
BaseDialogBuilder(requireActivity())
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.delete) { _: DialogInterface?, which: Int ->
|
||||
if (DialogInterface.BUTTON_POSITIVE != which) {
|
||||
|
||||
@@ -34,9 +34,9 @@ import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.Hosts;
|
||||
import com.hippo.ehviewer.R;
|
||||
@@ -149,8 +149,7 @@ public class HostsFragment extends BaseFragment
|
||||
ip.setText(arguments.getString(KEY_IP));
|
||||
}
|
||||
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(requireContext());
|
||||
builder.setView(view);
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()).setView(view);
|
||||
onCreateDialogBuilder(builder);
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
|
||||
|
||||
@@ -19,55 +19,29 @@
|
||||
package com.hippo.ehviewer.ui.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.SeekBarPreference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
|
||||
import com.hippo.ehviewer.ui.scene.SecurityScene.Companion.isAuthenticationSupported
|
||||
|
||||
/**
|
||||
* Created by Mo10 on 2018/2/10.
|
||||
*/
|
||||
class PrivacyFragment : BasePreferenceFragment() {
|
||||
private lateinit var requireUnlock: SwitchPreferenceCompat
|
||||
private lateinit var unlockDelay: SeekBarPreference
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.privacy_settings)
|
||||
requireUnlock = findPreference("require_unlock")!!
|
||||
unlockDelay = findPreference("require_unlock_delay")!!
|
||||
requireUnlock.setOnPreferenceChangeListener { _, newValue ->
|
||||
unlockDelay.isEnabled = newValue as Boolean
|
||||
setUnlockDelaySummary(unlockDelay.value)
|
||||
true
|
||||
}
|
||||
unlockDelay.setOnPreferenceChangeListener { _, newValue ->
|
||||
setUnlockDelaySummary(newValue as Int)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!isAuthenticationSupported(requireContext())) {
|
||||
Settings.putSecurity(false)
|
||||
requireUnlock.isEnabled = false
|
||||
requireUnlock.isChecked = false
|
||||
unlockDelay.isEnabled = false
|
||||
} else {
|
||||
unlockDelay.isEnabled = requireUnlock.isChecked == true
|
||||
setUnlockDelaySummary(unlockDelay.value)
|
||||
findPreference<SwitchPreferenceCompat>("require_unlock")?.isEnabled = false
|
||||
findPreference<SwitchPreferenceCompat>("require_unlock")?.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFragmentTitle(): Int {
|
||||
return R.string.settings_privacy
|
||||
}
|
||||
|
||||
private fun setUnlockDelaySummary(value: Int) {
|
||||
unlockDelay.summary = if (value == 0) {
|
||||
getString(R.string.settings_privacy_require_unlock_delay_summary_immediately)
|
||||
} else {
|
||||
getString(R.string.settings_privacy_require_unlock_delay_summary, value.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -31,8 +31,8 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhCookieStore;
|
||||
@@ -217,7 +217,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
hideSoftInput();
|
||||
|
||||
if (!checkIpbMemberId(ipbMemberId) || !(checkIpbPassHash(ipbPassHash))) {
|
||||
new BaseDialogBuilder(context).setTitle(R.string.waring)
|
||||
new MaterialAlertDialogBuilder(context).setTitle(R.string.waring)
|
||||
.setMessage(R.string.wrong_cookie_warning)
|
||||
.setPositiveButton(R.string.i_will_check_it, null)
|
||||
.setNegativeButton(R.string.i_dont_think_so, (dialog, which) -> {
|
||||
|
||||
@@ -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,28 @@ 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.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
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,8 +83,11 @@ 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;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.view.ViewTransition;
|
||||
@@ -81,10 +96,15 @@ import com.hippo.widget.LoadImageView;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -417,7 +437,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_filter) {
|
||||
new BaseDialogBuilder(requireActivity())
|
||||
new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setSingleChoiceItems(R.array.download_state, mType + 1, (dialog, which) -> {
|
||||
mType = which - 1;
|
||||
updateForLabel();
|
||||
@@ -440,7 +460,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
openDrawer(Gravity.RIGHT);
|
||||
return true;
|
||||
} else if (id == R.id.action_reset_reading_progress) {
|
||||
new BaseDialogBuilder(getContext())
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
.setMessage(R.string.reset_reading_progress_message)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
@@ -513,7 +533,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
items[i + 2] = list.get(i).getLabel();
|
||||
}
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.default_download_label)
|
||||
.setItems(items, (dialog1, which) -> {
|
||||
if (which == 0) {
|
||||
@@ -536,13 +556,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;
|
||||
}
|
||||
@@ -700,7 +727,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
MoveDialogHelper helper = new MoveDialogHelper(labels, downloadInfoList);
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show();
|
||||
@@ -865,7 +892,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;
|
||||
@@ -877,7 +904,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;
|
||||
|
||||
@@ -931,15 +1025,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);
|
||||
@@ -956,6 +1071,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 {
|
||||
@@ -1155,7 +1307,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);
|
||||
@@ -1292,43 +1445,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,13 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -42,9 +45,9 @@ import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.annotation.Implemented;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.drawable.AddDeleteDrawable;
|
||||
import com.hippo.drawable.DrawerArrowDrawable;
|
||||
@@ -74,6 +77,7 @@ import com.hippo.widget.ContentLayout;
|
||||
import com.hippo.widget.FabLayout;
|
||||
import com.hippo.widget.SearchBarMover;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
@@ -398,7 +402,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
items[1] = getString(R.string.local_favorites);
|
||||
String[] favCat = Settings.getFavCat();
|
||||
System.arraycopy(favCat, 0, items, 2, 10);
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.default_favorites_collection)
|
||||
.setItems(items, (dialog, which) -> Settings.putDefaultFavSlot(which - 2)).show();
|
||||
return true;
|
||||
@@ -727,7 +731,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
case 4: { // Delete
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper();
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_favorites_dialog_title)
|
||||
.setMessage(getString(R.string.delete_favorites_dialog_message, mModifyGiList.size()))
|
||||
.setPositiveButton(android.R.string.ok, helper)
|
||||
@@ -741,7 +745,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
String[] array = new String[11];
|
||||
array[0] = getString(R.string.local_favorites);
|
||||
System.arraycopy(Settings.getFavCat(), 0, array, 1, 10);
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.move_favorites_dialog_title)
|
||||
.setItems(array, helper)
|
||||
.setOnCancelListener(helper)
|
||||
|
||||
@@ -54,8 +54,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
@@ -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();
|
||||
@@ -290,7 +297,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
}
|
||||
}
|
||||
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(context);
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||
context = builder.getContext();
|
||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||
EasyRecyclerView rv = (EasyRecyclerView) inflater.inflate(R.layout.dialog_recycler_view, null);
|
||||
@@ -347,7 +354,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
menuId.add(R.id.check_vote_status);
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
|
||||
if (which < 0 || which >= menuId.size()) {
|
||||
return;
|
||||
|
||||
@@ -62,9 +62,9 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
@@ -260,7 +260,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
result -> {
|
||||
if (result && mGalleryDetail != null) {
|
||||
TorrentListDialogHelper helper = new TorrentListDialogHelper();
|
||||
Dialog dialog = new BaseDialogBuilder(requireActivity())
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.torrents)
|
||||
.setView(R.layout.dialog_torrent_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -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;
|
||||
@@ -1203,7 +1218,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
if (EhApplication.getDownloadManager(context).getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
|
||||
CommonOperations.startDownload(activity, galleryInfo, false);
|
||||
} else {
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
.setMessage(getString(R.string.download_remove_dialog_message, galleryInfo.title))
|
||||
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> EhApplication.getDownloadManager(context).deleteDownload(galleryInfo.gid))
|
||||
@@ -1229,7 +1244,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
for (GalleryInfo newerVersion : mGalleryDetail.newerVersions) {
|
||||
titles.add(getString(R.string.newer_version_title, newerVersion.title, newerVersion.posted));
|
||||
}
|
||||
new BaseDialogBuilder(requireContext())
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setItems(titles.toArray(new CharSequence[0]), (dialog, which) -> {
|
||||
GalleryInfo newerVersion = mGalleryDetail.newerVersions.get(which);
|
||||
Bundle args = new Bundle();
|
||||
@@ -1275,7 +1290,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
requestStoragePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
} else {
|
||||
TorrentListDialogHelper helper = new TorrentListDialogHelper();
|
||||
Dialog dialog = new BaseDialogBuilder(context)
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.torrents)
|
||||
.setView(R.layout.dialog_torrent_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -1292,7 +1307,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
ArchiveListDialogHelper helper = new ArchiveListDialogHelper();
|
||||
Dialog dialog = new BaseDialogBuilder(context)
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.dialog_archive_title)
|
||||
.setView(R.layout.dialog_archive_list)
|
||||
.setOnDismissListener(helper)
|
||||
@@ -1307,7 +1322,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
RateDialogHelper helper = new RateDialogHelper();
|
||||
Dialog dialog = new BaseDialogBuilder(context)
|
||||
Dialog dialog = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.rate)
|
||||
.setView(R.layout.dialog_rate)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
@@ -1369,7 +1384,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setMessage(getString(R.string.filter_the_uploader, uploader))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
Filter filter = new Filter();
|
||||
@@ -1389,7 +1404,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return;
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setMessage(getString(R.string.filter_the_tag, tag))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
Filter filter = new Filter();
|
||||
@@ -1438,7 +1453,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
menuId.add(R.id.vote_down);
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(tag)
|
||||
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
|
||||
if (which < 0 || which >= menuId.size()) {
|
||||
|
||||
@@ -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;
|
||||
@@ -40,23 +45,28 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.PickVisualMediaRequest;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
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.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
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;
|
||||
@@ -99,6 +109,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;
|
||||
@@ -150,8 +161,8 @@ public final class GalleryListScene extends BaseScene
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
private SearchLayout mSearchLayout;
|
||||
ActivityResultLauncher<PickVisualMediaRequest> selectImageLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.PickVisualMedia(),
|
||||
ActivityResultLauncher<String[]> selectImageLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.OpenDocument(),
|
||||
result -> mSearchLayout.setImageUri(result));
|
||||
@Nullable
|
||||
private SearchBar mSearchBar;
|
||||
@@ -215,7 +226,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
};
|
||||
// Double click back exit
|
||||
private final long mPressBackTime = 0;
|
||||
private long mPressBackTime = 0;
|
||||
private boolean mHasFirstRefresh = false;
|
||||
private int mNavCheckedId = 0;
|
||||
private DownloadManager mDownloadManager;
|
||||
@@ -663,7 +674,7 @@ public final class GalleryListScene extends BaseScene
|
||||
if (null == context) {
|
||||
return;
|
||||
}
|
||||
final BaseDialogBuilder builder = new BaseDialogBuilder(context);
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||
builder.setMessage(R.string.add_quick_search_tip);
|
||||
builder.setTitle(R.string.readme);
|
||||
builder.show();
|
||||
@@ -742,13 +753,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) {
|
||||
@@ -950,7 +967,7 @@ public final class GalleryListScene extends BaseScene
|
||||
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
|
||||
};
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(EhUtils.getSuitableTitle(gi))
|
||||
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
|
||||
switch (which) {
|
||||
@@ -962,7 +979,7 @@ public final class GalleryListScene extends BaseScene
|
||||
break;
|
||||
case 1: // Download
|
||||
if (downloaded) {
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
.setMessage(getString(R.string.download_remove_dialog_message, gi.title))
|
||||
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> mDownloadManager.deleteDownload(gi.gid))
|
||||
@@ -989,7 +1006,7 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show();
|
||||
@@ -1335,9 +1352,7 @@ public final class GalleryListScene extends BaseScene
|
||||
@Override
|
||||
public void onSelectImage() {
|
||||
try {
|
||||
PickVisualMediaRequest.Builder builder = new PickVisualMediaRequest.Builder();
|
||||
builder.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE);
|
||||
selectImageLauncher.launch(builder.build());
|
||||
selectImageLauncher.launch(new String[]{"image/*"});
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
||||
@@ -1477,7 +1492,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;
|
||||
@@ -1522,7 +1537,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;
|
||||
|
||||
@@ -1553,6 +1568,34 @@ public final class GalleryListScene extends BaseScene
|
||||
setState(STATE_NORMAL);
|
||||
closeDrawer(Gravity.RIGHT);
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
PopupMenu popupMenu = new PopupMenu(requireContext(), holder.option);
|
||||
popupMenu.inflate(R.menu.quicksearch_option);
|
||||
popupMenu.show();
|
||||
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
|
||||
final QuickSearch quickSearch = mQuickSearchList.get(position);
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_qs_remove) {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(getString(R.string.delete_quick_search_title))
|
||||
.setMessage(getString(R.string.delete_quick_search_message, quickSearch.name))
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
EhDB.deleteQuickSearch(quickSearch);
|
||||
mQuickSearchList.remove(position);
|
||||
notifyDataSetChanged();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
int[] keywords = {11, 12, 13, 15};
|
||||
int[] toplists = {R.string.toplist_alltime, R.string.toplist_pastyear, R.string.toplist_pastmonth, R.string.toplist_yesterday};
|
||||
@@ -1588,6 +1631,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 {
|
||||
@@ -1772,47 +1853,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,8 @@ import androidx.annotation.Nullable;
|
||||
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.app.BaseDialogBuilder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
@@ -211,7 +211,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
int pages = mHelper.getPages();
|
||||
if (pages > 1 && mHelper.canGoTo()) {
|
||||
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
|
||||
AlertDialog dialog = new BaseDialogBuilder(context).setTitle(R.string.go_to)
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setTitle(R.string.go_to)
|
||||
.setView(R.layout.dialog_go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
|
||||
@@ -0,0 +1,704 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.scene;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemConstants;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionRemoveItem;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.touchguard.RecyclerViewTouchActionGuardManager;
|
||||
import com.h6ah4i.android.widget.advrecyclerview.utils.AbstractSwipeableItemViewHolder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.FastScroller;
|
||||
import com.hippo.easyrecyclerview.HandlerDrawable;
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.FavouriteStatusRouter;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.dao.HistoryInfo;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.widget.LoadImageView;
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import org.greenrobot.greendao.query.LazyList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
public class HistoryScene extends ToolbarScene {
|
||||
|
||||
/*---------------
|
||||
View life cycle
|
||||
---------------*/
|
||||
@Nullable
|
||||
private TextView mTip;
|
||||
@Nullable
|
||||
private FastScroller mFastScroller;
|
||||
@Nullable
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
private ViewTransition mViewTransition;
|
||||
@Nullable
|
||||
private RecyclerView.Adapter<?> mAdapter;
|
||||
@Nullable
|
||||
private LazyList<HistoryInfo> mLazyList;
|
||||
|
||||
private DownloadManager mDownloadManager;
|
||||
private DownloadManager.DownloadInfoListener mDownloadInfoListener;
|
||||
private FavouriteStatusRouter mFavouriteStatusRouter;
|
||||
private FavouriteStatusRouter.Listener mFavouriteStatusRouterListener;
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
mDownloadManager.removeDownloadInfoListener(mDownloadInfoListener);
|
||||
mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNavCheckedItem() {
|
||||
return R.id.nav_history;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Context context = getContext();
|
||||
assert context != null;
|
||||
mDownloadManager = EhApplication.getDownloadManager(context);
|
||||
mDownloadManager = EhApplication.getDownloadManager(context);
|
||||
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context);
|
||||
|
||||
mDownloadInfoListener = new DownloadManager.DownloadInfoListener() {
|
||||
@Override
|
||||
public void onAdd(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateAll() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReload() {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange() {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenameLabel(String from, String to) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemove(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateLabels() {
|
||||
}
|
||||
};
|
||||
mDownloadManager.addDownloadInfoListener(mDownloadInfoListener);
|
||||
|
||||
mFavouriteStatusRouterListener = (gid, slot) -> {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
mFavouriteStatusRouter.addListener(mFavouriteStatusRouterListener);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateViewWithToolbar(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_history, container, false);
|
||||
View content = ViewUtils.$$(view, R.id.content);
|
||||
mRecyclerView = (EasyRecyclerView) ViewUtils.$$(content, R.id.recycler_view);
|
||||
mFastScroller = (FastScroller) ViewUtils.$$(content, R.id.fast_scroller);
|
||||
mTip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
mViewTransition = new ViewTransition(content, mTip);
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
Resources resources = context.getResources();
|
||||
|
||||
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.big_history);
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
mTip.setCompoundDrawables(null, drawable, null, null);
|
||||
|
||||
RecyclerViewTouchActionGuardManager guardManager = new RecyclerViewTouchActionGuardManager();
|
||||
guardManager.setInterceptVerticalScrollingWhileAnimationRunning(true);
|
||||
guardManager.setEnabled(true);
|
||||
RecyclerViewSwipeManager swipeManager = new RecyclerViewSwipeManager();
|
||||
mAdapter = new HistoryAdapter();
|
||||
mAdapter.setHasStableIds(true);
|
||||
mAdapter = swipeManager.createWrappedAdapter(mAdapter);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
final GeneralItemAnimator animator = new SwipeDismissItemAnimator();
|
||||
animator.setSupportsChangeAnimations(false);
|
||||
mRecyclerView.setItemAnimator(animator);
|
||||
AutoStaggeredGridLayoutManager layoutManager = new AutoStaggeredGridLayoutManager(
|
||||
0, StaggeredGridLayoutManager.VERTICAL);
|
||||
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()));
|
||||
layoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE);
|
||||
mRecyclerView.setLayoutManager(layoutManager);
|
||||
mRecyclerView.setClipToPadding(false);
|
||||
mRecyclerView.setClipChildren(false);
|
||||
int interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval);
|
||||
int paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h);
|
||||
int paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v);
|
||||
MarginItemDecoration decoration = new MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV);
|
||||
mRecyclerView.addItemDecoration(decoration);
|
||||
guardManager.attachRecyclerView(mRecyclerView);
|
||||
swipeManager.attachRecyclerView(mRecyclerView);
|
||||
|
||||
mFastScroller.attachToRecyclerView(mRecyclerView);
|
||||
HandlerDrawable handlerDrawable = new HandlerDrawable();
|
||||
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorPrimary));
|
||||
mFastScroller.setHandlerDrawable(handlerDrawable);
|
||||
|
||||
updateLazyList();
|
||||
updateView(false);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setTitle(R.string.history);
|
||||
setNavigationIcon(R.drawable.ic_baseline_menu_24);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
if (null != mLazyList) {
|
||||
mLazyList.close();
|
||||
mLazyList = null;
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView.stopScroll();
|
||||
mRecyclerView = null;
|
||||
}
|
||||
|
||||
mViewTransition = null;
|
||||
mAdapter = null;
|
||||
}
|
||||
|
||||
// Remember to notify
|
||||
private void updateLazyList() {
|
||||
LazyList<HistoryInfo> lazyList = EhDB.getHistoryLazyList();
|
||||
if (mLazyList != null) {
|
||||
mLazyList.close();
|
||||
}
|
||||
mLazyList = lazyList;
|
||||
}
|
||||
|
||||
private void updateView(boolean animation) {
|
||||
if (null == mAdapter || null == mViewTransition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAdapter.getItemCount() == 0) {
|
||||
mViewTransition.showView(1, animation);
|
||||
} else {
|
||||
mViewTransition.showView(0, animation);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
@Override
|
||||
public void onNavigationClick() {
|
||||
toggleDrawer(Gravity.LEFT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuResId() {
|
||||
return R.menu.scene_history;
|
||||
}
|
||||
|
||||
private void showClearAllDialog() {
|
||||
new MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(R.string.clear_all_history)
|
||||
.setPositiveButton(R.string.clear_all, (dialog, which) -> {
|
||||
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
EhDB.clearHistoryInfo();
|
||||
updateLazyList();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateView(true);
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
// Skip when in choice mode
|
||||
Context context = getContext();
|
||||
if (null == context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_clear_all) {
|
||||
showClearAllDialog();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onItemClick(View view, int position) {
|
||||
if (null == mLazyList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO);
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, mLazyList.get(position));
|
||||
Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args);
|
||||
startScene(announcer);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onItemLongClick(int position) {
|
||||
final Context context = getContext();
|
||||
final MainActivity activity = getMainActivity();
|
||||
if (null == context || null == activity || null == mLazyList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final GalleryInfo gi = mLazyList.get(position);
|
||||
|
||||
if (gi == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean downloaded = mDownloadManager.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID;
|
||||
boolean favourited = gi.favoriteSlot != -2;
|
||||
|
||||
CharSequence[] items = downloaded ? new CharSequence[]{
|
||||
context.getString(R.string.read),
|
||||
context.getString(R.string.delete_downloads),
|
||||
context.getString(favourited ? R.string.remove_from_favourites : R.string.add_to_favourites),
|
||||
context.getString(R.string.delete),
|
||||
context.getString(R.string.download_move_dialog_title),
|
||||
} : new CharSequence[]{
|
||||
context.getString(R.string.read),
|
||||
context.getString(R.string.download),
|
||||
context.getString(favourited ? R.string.remove_from_favourites : R.string.add_to_favourites),
|
||||
context.getString(R.string.delete),
|
||||
};
|
||||
|
||||
int[] icons = downloaded ? new int[]{
|
||||
R.drawable.v_book_open_x24,
|
||||
R.drawable.v_delete_x24,
|
||||
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
|
||||
R.drawable.v_delete_x24,
|
||||
R.drawable.v_folder_move_x24,
|
||||
} : new int[]{
|
||||
R.drawable.v_book_open_x24,
|
||||
R.drawable.v_download_x24,
|
||||
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
|
||||
R.drawable.v_delete_x24,
|
||||
};
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(EhUtils.getSuitableTitle(gi))
|
||||
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
|
||||
switch (which) {
|
||||
case 0: // Read
|
||||
Intent intent = new Intent(activity, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case 1: // Download
|
||||
if (downloaded) {
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
.setMessage(getString(R.string.download_remove_dialog_message, gi.title))
|
||||
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> mDownloadManager.deleteDownload(gi.gid))
|
||||
.show();
|
||||
} else {
|
||||
CommonOperations.startDownload(activity, gi, false);
|
||||
}
|
||||
break;
|
||||
case 2: // Favorites
|
||||
if (favourited) {
|
||||
CommonOperations.removeFromFavorites(activity, gi, new RemoveFromFavoriteListener(context, activity.getStageId(), getTag()));
|
||||
} else {
|
||||
CommonOperations.addToFavorites(activity, gi, new AddToFavoriteListener(context, activity.getStageId(), getTag()));
|
||||
}
|
||||
break;
|
||||
case 3: // Delete
|
||||
if (null == mLazyList || null == mAdapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
HistoryInfo info = mLazyList.get(position);
|
||||
EhDB.deleteHistoryInfo(info);
|
||||
updateLazyList();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateView(true);
|
||||
break;
|
||||
case 4: // Move
|
||||
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager(context).getLabelList();
|
||||
List<String> labelList = new ArrayList<>(labelRawList.size() + 1);
|
||||
labelList.add(getString(R.string.default_download_label_name));
|
||||
for (int i = 0, n = labelRawList.size(); i < n; i++) {
|
||||
labelList.add(labelRawList.get(i).getLabel());
|
||||
}
|
||||
String[] labels = labelList.toArray(new String[0]);
|
||||
|
||||
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
|
||||
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class AddToFavoriteListener extends EhCallback<GalleryListScene, Void> {
|
||||
|
||||
public AddToFavoriteListener(Context context, int stageId, String sceneTag) {
|
||||
super(context, stageId, sceneTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
showTip(R.string.add_to_favorite_success, LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
showTip(R.string.add_to_favorite_failure, LENGTH_LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstance(SceneFragment scene) {
|
||||
return scene instanceof GalleryListScene;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoveFromFavoriteListener extends EhCallback<GalleryListScene, Void> {
|
||||
|
||||
public RemoveFromFavoriteListener(Context context, int stageId, String sceneTag) {
|
||||
super(context, stageId, sceneTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Void result) {
|
||||
showTip(R.string.remove_from_favorite_success, LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
showTip(R.string.remove_from_favorite_failure, LENGTH_LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstance(SceneFragment scene) {
|
||||
return scene instanceof GalleryListScene;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HistoryHolder extends AbstractSwipeableItemViewHolder {
|
||||
|
||||
public final View card;
|
||||
public final LoadImageView thumb;
|
||||
public final TextView title;
|
||||
public final TextView uploader;
|
||||
public final SimpleRatingView rating;
|
||||
public final TextView category;
|
||||
public final TextView posted;
|
||||
public final TextView simpleLanguage;
|
||||
public final TextView pages;
|
||||
public final ImageView downloaded;
|
||||
|
||||
public HistoryHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
card = itemView.findViewById(R.id.card);
|
||||
thumb = itemView.findViewById(R.id.thumb);
|
||||
title = itemView.findViewById(R.id.title);
|
||||
uploader = itemView.findViewById(R.id.uploader);
|
||||
rating = itemView.findViewById(R.id.rating);
|
||||
category = itemView.findViewById(R.id.category);
|
||||
posted = itemView.findViewById(R.id.posted);
|
||||
simpleLanguage = itemView.findViewById(R.id.simple_language);
|
||||
pages = itemView.findViewById(R.id.pages);
|
||||
downloaded = itemView.findViewById(R.id.downloaded);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getSwipeableContainerView() {
|
||||
return card;
|
||||
}
|
||||
}
|
||||
|
||||
private class MoveDialogHelper implements DialogInterface.OnClickListener {
|
||||
|
||||
private final String[] mLabels;
|
||||
private final GalleryInfo mGi;
|
||||
|
||||
public MoveDialogHelper(String[] labels, GalleryInfo gi) {
|
||||
mLabels = labels;
|
||||
mGi = gi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// Cancel check mode
|
||||
Context context = getContext();
|
||||
if (null == context) {
|
||||
return;
|
||||
}
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView.outOfCustomChoiceMode();
|
||||
}
|
||||
|
||||
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
|
||||
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
|
||||
if (downloadInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String label = which == 0 ? null : mLabels[which];
|
||||
|
||||
downloadManager.changeLabel(Collections.singletonList(downloadInfo), label);
|
||||
}
|
||||
}
|
||||
|
||||
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder>
|
||||
implements SwipeableItemAdapter<HistoryHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
private final int mListThumbWidth;
|
||||
private final int mListThumbHeight;
|
||||
|
||||
public HistoryAdapter() {
|
||||
mInflater = getLayoutInflater();
|
||||
|
||||
@SuppressLint("InflateParams") View calculator = mInflater.inflate(R.layout.item_gallery_list_thumb_height, null);
|
||||
ViewUtils.measureView(calculator, 1024, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mListThumbHeight = calculator.getMeasuredHeight();
|
||||
mListThumbWidth = mListThumbHeight * 2 / 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (null == mLazyList) {
|
||||
return super.getItemId(position);
|
||||
} else {
|
||||
return mLazyList.get(position).gid;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public HistoryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
HistoryHolder holder = new HistoryHolder(mInflater.inflate(R.layout.item_history, parent, false));
|
||||
|
||||
ViewGroup.LayoutParams lp = holder.thumb.getLayoutParams();
|
||||
lp.width = mListThumbWidth;
|
||||
lp.height = mListThumbHeight;
|
||||
holder.thumb.setLayoutParams(lp);
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull HistoryHolder holder, int position) {
|
||||
if (null == mLazyList) {
|
||||
return;
|
||||
}
|
||||
|
||||
GalleryInfo gi = mLazyList.get(position);
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
|
||||
holder.title.setText(EhUtils.getSuitableTitle(gi));
|
||||
holder.uploader.setText(gi.uploader);
|
||||
holder.rating.setRating(gi.rating);
|
||||
TextView category = holder.category;
|
||||
String newCategoryText = EhUtils.getCategory(gi.category);
|
||||
if (!newCategoryText.contentEquals(category.getText())) {
|
||||
category.setText(newCategoryText);
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category));
|
||||
}
|
||||
holder.posted.setText(gi.posted);
|
||||
holder.pages.setText(null);
|
||||
holder.pages.setVisibility(View.GONE);
|
||||
if (TextUtils.isEmpty(gi.simpleLanguage)) {
|
||||
holder.simpleLanguage.setText(null);
|
||||
holder.simpleLanguage.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.simpleLanguage.setText(gi.simpleLanguage);
|
||||
holder.simpleLanguage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
holder.downloaded.setVisibility(mDownloadManager.containDownloadInfo(gi.gid) ? View.VISIBLE : View.GONE);
|
||||
|
||||
// Update transition name
|
||||
long gid = gi.gid;
|
||||
ViewCompat.setTransitionName(holder.thumb, TransitionNameFactory.getThumbTransitionName(gid));
|
||||
|
||||
holder.card.setOnClickListener(v -> onItemClick(holder.itemView, position));
|
||||
holder.card.setOnLongClickListener(v -> onItemLongClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return null != mLazyList ? mLazyList.size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onGetSwipeReactionType(@NonNull HistoryHolder holder, int position, int x, int y) {
|
||||
return SwipeableItemConstants.REACTION_CAN_SWIPE_LEFT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwipeItemStarted(@NonNull HistoryHolder holder, int position) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetSwipeBackground(@NonNull HistoryHolder holder, int position, int type) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SwipeResultAction onSwipeItem(@NonNull HistoryHolder holder, int position, int result) {
|
||||
switch (result) {
|
||||
case SwipeableItemConstants.RESULT_SWIPED_LEFT:
|
||||
return new SwipeResultActionClear(position);
|
||||
case SwipeableItemConstants.RESULT_SWIPED_RIGHT:
|
||||
case SwipeableItemConstants.RESULT_CANCELED:
|
||||
case SwipeableItemConstants.RESULT_NONE:
|
||||
case SwipeableItemConstants.RESULT_SWIPED_DOWN:
|
||||
case SwipeableItemConstants.RESULT_SWIPED_UP:
|
||||
default:
|
||||
return new SwipeResultActionDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SwipeResultActionClear extends SwipeResultActionRemoveItem {
|
||||
|
||||
private final int mPosition;
|
||||
|
||||
protected SwipeResultActionClear(int position) {
|
||||
mPosition = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPerformAction() {
|
||||
super.onPerformAction();
|
||||
if (null == mLazyList || null == mAdapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
HistoryInfo info = mLazyList.get(mPosition);
|
||||
EhDB.deleteHistoryInfo(info);
|
||||
updateLazyList();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateView(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,489 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.*
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.easyrecyclerview.FastScroller
|
||||
import com.hippo.easyrecyclerview.HandlerDrawable
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration
|
||||
import com.hippo.ehviewer.*
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.HistoryInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
|
||||
import com.hippo.ehviewer.ui.CommonOperations
|
||||
import com.hippo.ehviewer.ui.GalleryActivity
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView
|
||||
import com.hippo.scene.Announcer
|
||||
import com.hippo.scene.SceneFragment
|
||||
import com.hippo.view.ViewTransition
|
||||
import com.hippo.widget.LoadImageView
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import rikka.core.res.resolveColor
|
||||
|
||||
class HistoryScene : ToolbarScene() {
|
||||
private var mRecyclerView: EasyRecyclerView? = null
|
||||
private var mViewTransition: ViewTransition? = null
|
||||
private var mAdapter: HistoryAdapter? = null
|
||||
private var mDownloadManager: DownloadManager? = null
|
||||
private var mDownloadInfoListener: DownloadInfoListener? = null
|
||||
private var mFavouriteStatusRouter: FavouriteStatusRouter? = null
|
||||
private var mFavouriteStatusRouterListener: FavouriteStatusRouter.Listener? = null
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mDownloadManager!!.removeDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouter!!.removeListener(mFavouriteStatusRouterListener)
|
||||
}
|
||||
|
||||
override fun getNavCheckedItem(): Int {
|
||||
return R.id.nav_history
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val context = requireContext()
|
||||
mDownloadManager = EhApplication.getDownloadManager(context)
|
||||
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context)
|
||||
mDownloadInfoListener = object : DownloadInfoListener {
|
||||
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>) {}
|
||||
override fun onUpdateAll() {}
|
||||
override fun onReload() {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onChange() {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onRenameLabel(from: String, to: String) {}
|
||||
override fun onRemove(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onUpdateLabels() {}
|
||||
}
|
||||
mDownloadManager!!.addDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouterListener = FavouriteStatusRouter.Listener { _: Long, _: Int ->
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
mFavouriteStatusRouter!!.addListener(mFavouriteStatusRouterListener)
|
||||
}
|
||||
|
||||
override fun onCreateViewWithToolbar(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.scene_history, container, false)
|
||||
val content = ViewUtils.`$$`(view, R.id.content)
|
||||
val recyclerView = ViewUtils.`$$`(content, R.id.recycler_view) as EasyRecyclerView
|
||||
val mFastScroller = ViewUtils.`$$`(content, R.id.fast_scroller) as FastScroller
|
||||
val mTip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
mViewTransition = ViewTransition(content, mTip)
|
||||
val resources = requireContext().resources
|
||||
val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.big_history)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
mTip.setCompoundDrawables(null, drawable, null, null)
|
||||
val historyData = Pager(
|
||||
PagingConfig(20)
|
||||
) {
|
||||
EhDB.getHistoryLazyList()
|
||||
}.flow.cachedIn(viewLifecycleOwner.lifecycleScope)
|
||||
mAdapter = HistoryAdapter(object : DiffUtil.ItemCallback<HistoryInfo>() {
|
||||
override fun areItemsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
|
||||
return oldItem.gid == newItem.gid
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
|
||||
return oldItem.gid == newItem.gid
|
||||
}
|
||||
})
|
||||
recyclerView.adapter = mAdapter
|
||||
val layoutManager = AutoStaggeredGridLayoutManager(
|
||||
0, StaggeredGridLayoutManager.VERTICAL
|
||||
)
|
||||
layoutManager.setColumnSize(resources.getDimensionPixelOffset(Settings.getDetailSizeResId()))
|
||||
layoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.clipChildren = false
|
||||
val interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval)
|
||||
val paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h)
|
||||
val paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v)
|
||||
val decoration = MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV)
|
||||
recyclerView.addItemDecoration(decoration)
|
||||
val itemTouchHelper = ItemTouchHelper(HistoryItemTouchHelperCallback())
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
mFastScroller.attachToRecyclerView(recyclerView)
|
||||
mRecyclerView = recyclerView
|
||||
val handlerDrawable = HandlerDrawable()
|
||||
handlerDrawable.setColor(theme.resolveColor(com.google.android.material.R.attr.colorPrimary))
|
||||
mFastScroller.setHandlerDrawable(handlerDrawable)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
historyData.collectLatest { value ->
|
||||
mAdapter!!.submitData(
|
||||
value
|
||||
)
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setTitle(R.string.history)
|
||||
setNavigationIcon(R.drawable.ic_baseline_menu_24)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mRecyclerView?.stopScroll()
|
||||
mRecyclerView = null
|
||||
mViewTransition = null
|
||||
mAdapter = null
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
override fun onNavigationClick() {
|
||||
toggleDrawer(Gravity.LEFT)
|
||||
}
|
||||
|
||||
override fun getMenuResId(): Int {
|
||||
return R.menu.scene_history
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun showClearAllDialog() {
|
||||
BaseDialogBuilder(requireContext())
|
||||
.setMessage(R.string.clear_all_history)
|
||||
.setPositiveButton(R.string.clear_all) { _: DialogInterface?, which: Int ->
|
||||
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
|
||||
return@setPositiveButton
|
||||
}
|
||||
EhDB.clearHistoryInfo()
|
||||
mAdapter!!.refresh()
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
// Skip when in choice mode
|
||||
context ?: return false
|
||||
val id = item.itemId
|
||||
if (id == R.id.action_clear_all) {
|
||||
showClearAllDialog()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
val args = Bundle()
|
||||
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO)
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, mAdapter?.peek(position))
|
||||
val announcer = Announcer(GalleryDetailScene::class.java).setArgs(args)
|
||||
startScene(announcer)
|
||||
return true
|
||||
}
|
||||
|
||||
fun onItemLongClick(position: Int): Boolean {
|
||||
val context = requireContext()
|
||||
val activity = mainActivity ?: return false
|
||||
val gi: GalleryInfo = mAdapter?.peek(position) ?: return true
|
||||
val downloaded = mDownloadManager!!.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID
|
||||
val favourited = gi.favoriteSlot != -2
|
||||
val items = if (downloaded) arrayOf<CharSequence>(
|
||||
context.getString(R.string.read),
|
||||
context.getString(R.string.delete_downloads),
|
||||
context.getString(if (favourited) R.string.remove_from_favourites else R.string.add_to_favourites),
|
||||
context.getString(R.string.download_move_dialog_title)
|
||||
) else arrayOf<CharSequence>(
|
||||
context.getString(R.string.read),
|
||||
context.getString(R.string.download),
|
||||
context.getString(if (favourited) R.string.remove_from_favourites else R.string.add_to_favourites),
|
||||
)
|
||||
val icons = if (downloaded) intArrayOf(
|
||||
R.drawable.v_book_open_x24,
|
||||
R.drawable.v_delete_x24,
|
||||
if (favourited) R.drawable.v_heart_broken_x24 else R.drawable.v_heart_x24,
|
||||
R.drawable.v_folder_move_x24
|
||||
) else intArrayOf(
|
||||
R.drawable.v_book_open_x24,
|
||||
R.drawable.v_download_x24,
|
||||
if (favourited) R.drawable.v_heart_broken_x24 else R.drawable.v_heart_x24,
|
||||
)
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(EhUtils.getSuitableTitle(gi))
|
||||
.setAdapter(
|
||||
SelectItemWithIconAdapter(
|
||||
context,
|
||||
items,
|
||||
icons
|
||||
)
|
||||
) { _: DialogInterface?, which: Int ->
|
||||
when (which) {
|
||||
0 -> {
|
||||
val intent = Intent(activity, GalleryActivity::class.java)
|
||||
intent.action = GalleryActivity.ACTION_EH
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
|
||||
startActivity(intent)
|
||||
}
|
||||
1 -> if (downloaded) {
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
.setMessage(
|
||||
getString(
|
||||
R.string.download_remove_dialog_message,
|
||||
gi.title
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
mDownloadManager!!.deleteDownload(
|
||||
gi.gid
|
||||
)
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
CommonOperations.startDownload(activity, gi, false)
|
||||
}
|
||||
2 -> if (favourited) {
|
||||
CommonOperations.removeFromFavorites(
|
||||
activity,
|
||||
gi,
|
||||
RemoveFromFavoriteListener(context, activity.stageId, tag)
|
||||
)
|
||||
} else {
|
||||
CommonOperations.addToFavorites(
|
||||
activity,
|
||||
gi,
|
||||
AddToFavoriteListener(context, activity.stageId, tag)
|
||||
)
|
||||
}
|
||||
3 -> {
|
||||
val labelRawList = EhApplication.getDownloadManager(context).labelList
|
||||
val labelList: MutableList<String> = ArrayList(labelRawList.size + 1)
|
||||
labelList.add(getString(R.string.default_download_label_name))
|
||||
var i = 0
|
||||
val n = labelRawList.size
|
||||
while (i < n) {
|
||||
labelList.add(labelRawList[i].label)
|
||||
i++
|
||||
}
|
||||
val labels = labelList.toTypedArray()
|
||||
val helper = MoveDialogHelper(labels, gi)
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_move_dialog_title)
|
||||
.setItems(labels, helper)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
return true
|
||||
}
|
||||
|
||||
private class AddToFavoriteListener(context: Context?, stageId: Int, sceneTag: String?) :
|
||||
EhCallback<GalleryListScene?, Void?>(context, stageId, sceneTag) {
|
||||
override fun onSuccess(result: Void?) {
|
||||
showTip(R.string.add_to_favorite_success, LENGTH_SHORT)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
showTip(R.string.add_to_favorite_failure, LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
override fun isInstance(scene: SceneFragment): Boolean {
|
||||
return scene is GalleryListScene
|
||||
}
|
||||
}
|
||||
|
||||
private class RemoveFromFavoriteListener(context: Context?, stageId: Int, sceneTag: String?) :
|
||||
EhCallback<GalleryListScene?, Void?>(context, stageId, sceneTag) {
|
||||
override fun onSuccess(result: Void?) {
|
||||
showTip(R.string.remove_from_favorite_success, LENGTH_SHORT)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
showTip(R.string.remove_from_favorite_failure, LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
override fun isInstance(scene: SceneFragment): Boolean {
|
||||
return scene is GalleryListScene
|
||||
}
|
||||
}
|
||||
|
||||
private class HistoryHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val card: View
|
||||
val thumb: LoadImageView
|
||||
val title: TextView
|
||||
val uploader: TextView
|
||||
val rating: SimpleRatingView
|
||||
val category: TextView
|
||||
val posted: TextView
|
||||
val simpleLanguage: TextView
|
||||
val pages: TextView
|
||||
val downloaded: ImageView
|
||||
|
||||
init {
|
||||
card = itemView.findViewById(R.id.card)
|
||||
thumb = itemView.findViewById(R.id.thumb)
|
||||
title = itemView.findViewById(R.id.title)
|
||||
uploader = itemView.findViewById(R.id.uploader)
|
||||
rating = itemView.findViewById(R.id.rating)
|
||||
category = itemView.findViewById(R.id.category)
|
||||
posted = itemView.findViewById(R.id.posted)
|
||||
simpleLanguage = itemView.findViewById(R.id.simple_language)
|
||||
pages = itemView.findViewById(R.id.pages)
|
||||
downloaded = itemView.findViewById(R.id.downloaded)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MoveDialogHelper(
|
||||
private val mLabels: Array<String>,
|
||||
private val mGi: GalleryInfo
|
||||
) : DialogInterface.OnClickListener {
|
||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||
mRecyclerView?.outOfCustomChoiceMode()
|
||||
val downloadManager = EhApplication.getDownloadManager(requireContext())
|
||||
val downloadInfo = downloadManager.getDownloadInfo(mGi.gid) ?: return
|
||||
val label = if (which == 0) null else mLabels[which]
|
||||
downloadManager.changeLabel(listOf(downloadInfo), label)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class HistoryAdapter(diffCallback: DiffUtil.ItemCallback<HistoryInfo>) :
|
||||
PagingDataAdapter<HistoryInfo, HistoryHolder>(diffCallback) {
|
||||
private val mInflater: LayoutInflater = layoutInflater
|
||||
private val mListThumbWidth: Int
|
||||
private val mListThumbHeight: Int
|
||||
|
||||
init {
|
||||
@SuppressLint("InflateParams") val calculator =
|
||||
mInflater.inflate(R.layout.item_gallery_list_thumb_height, null)
|
||||
ViewUtils.measureView(calculator, 1024, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
mListThumbHeight = calculator.measuredHeight
|
||||
mListThumbWidth = mListThumbHeight * 2 / 3
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HistoryHolder {
|
||||
val holder = HistoryHolder(mInflater.inflate(R.layout.item_history, parent, false))
|
||||
val lp = holder.thumb.layoutParams
|
||||
lp.width = mListThumbWidth
|
||||
lp.height = mListThumbHeight
|
||||
holder.thumb.layoutParams = lp
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: HistoryHolder, position: Int) {
|
||||
val gi: GalleryInfo? = getItem(position)
|
||||
gi ?: return
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb)
|
||||
holder.title.text = EhUtils.getSuitableTitle(gi)
|
||||
holder.uploader.text = gi.uploader
|
||||
holder.rating.rating = gi.rating
|
||||
val category = holder.category
|
||||
val newCategoryText = EhUtils.getCategory(gi.category)
|
||||
if (!newCategoryText!!.contentEquals(category.text)) {
|
||||
category.text = newCategoryText
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category))
|
||||
}
|
||||
holder.posted.text = gi.posted
|
||||
holder.pages.text = null
|
||||
holder.pages.visibility = View.GONE
|
||||
if (TextUtils.isEmpty(gi.simpleLanguage)) {
|
||||
holder.simpleLanguage.text = null
|
||||
holder.simpleLanguage.visibility = View.GONE
|
||||
} else {
|
||||
holder.simpleLanguage.text = gi.simpleLanguage
|
||||
holder.simpleLanguage.visibility = View.VISIBLE
|
||||
}
|
||||
holder.downloaded.visibility =
|
||||
if (mDownloadManager!!.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
|
||||
|
||||
// Update transition name
|
||||
val gid = gi.gid
|
||||
ViewCompat.setTransitionName(
|
||||
holder.thumb,
|
||||
TransitionNameFactory.getThumbTransitionName(gid)
|
||||
)
|
||||
holder.card.setOnClickListener { onItemClick(position) }
|
||||
holder.card.setOnLongClickListener { onItemLongClick(position) }
|
||||
}
|
||||
}
|
||||
|
||||
private inner class HistoryItemTouchHelperCallback : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
return makeMovementFlags(0, ItemTouchHelper.LEFT)
|
||||
}
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val mPosition = viewHolder.bindingAdapterPosition
|
||||
if (mAdapter == null) {
|
||||
return
|
||||
}
|
||||
val info: HistoryInfo = mAdapter!!.peek(mPosition)!!
|
||||
EhDB.deleteHistoryInfo(info)
|
||||
mAdapter!!.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-8
@@ -16,10 +16,10 @@
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
import androidx.biometric.BiometricPrompt
|
||||
@@ -30,10 +30,14 @@ import androidx.fragment.app.FragmentActivity
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
class SecurityActivity : AppCompatActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isAuthenticationSupported(this))
|
||||
class SecurityScene : SolidScene() {
|
||||
override fun needShowLeftDrawer(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (isAuthenticationSupported(requireContext()))
|
||||
startAuthentication(getString(R.string.settings_privacy_require_unlock))
|
||||
else
|
||||
onSuccess()
|
||||
@@ -48,7 +52,7 @@ class SecurityActivity : AppCompatActivity() {
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
confirmationRequired = confirmationRequired,
|
||||
executor = ContextCompat.getMainExecutor(this),
|
||||
executor = ContextCompat.getMainExecutor(requireContext()),
|
||||
callback = AuthenticationCallback()
|
||||
)
|
||||
}
|
||||
@@ -66,7 +70,7 @@ class SecurityActivity : AppCompatActivity() {
|
||||
errorCode: Int,
|
||||
errString: CharSequence
|
||||
) {
|
||||
moveTaskToBack(true)
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +84,8 @@ class SecurityActivity : AppCompatActivity() {
|
||||
|
||||
private fun onSuccess() {
|
||||
EhApplication.locked = false
|
||||
if (stackIndex == 0)
|
||||
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,8 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
@@ -333,7 +333,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
return;
|
||||
}
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.sign_in_failed)
|
||||
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.sign_in_failed_tip))
|
||||
.setPositiveButton(R.string.get_it, null)
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.scene.Announcer;
|
||||
|
||||
/**
|
||||
@@ -27,14 +28,26 @@ import com.hippo.scene.Announcer;
|
||||
*/
|
||||
public class SolidScene extends BaseScene {
|
||||
|
||||
public static final int CHECK_STEP_SIGN_IN = 1;
|
||||
public static final int CHECK_STEP_SELECT_SITE = 2;
|
||||
public static final int CHECK_STEP_SECURITY = 0;
|
||||
public static final int CHECK_STEP_WARNING = 1;
|
||||
public static final int CHECK_STEP_SIGN_IN = 2;
|
||||
public static final int CHECK_STEP_SELECT_SITE = 3;
|
||||
public static final String KEY_TARGET_SCENE = "target_scene";
|
||||
public static final String KEY_TARGET_ARGS = "target_args";
|
||||
private static final String TAG = SolidScene.class.getSimpleName();
|
||||
|
||||
public void startSceneForCheckStep(int checkStep, Bundle args) {
|
||||
switch (checkStep) {
|
||||
case CHECK_STEP_SECURITY:
|
||||
if (Settings.getShowWarning()) {
|
||||
startScene(new Announcer(WarningScene.class).setArgs(args), true);
|
||||
break;
|
||||
}
|
||||
case CHECK_STEP_WARNING:
|
||||
if (EhUtils.needSignedIn(getContext())) {
|
||||
startScene(new Announcer(SignInScene.class).setArgs(args), true);
|
||||
break;
|
||||
}
|
||||
case CHECK_STEP_SIGN_IN:
|
||||
if (Settings.getSelectSite()) {
|
||||
startScene(new Announcer(SelectSiteScene.class).setArgs(args), true);
|
||||
|
||||
@@ -87,6 +87,12 @@ public abstract class ToolbarScene extends BaseScene {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needWhiteStatusBar() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public int getMenuResId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.scene;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.annotation.ViewLifeCircle;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
public final class WarningScene extends SolidScene implements View.OnClickListener {
|
||||
|
||||
@Nullable
|
||||
private View mReject;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private View mAccept;
|
||||
|
||||
@Override
|
||||
public boolean needShowLeftDrawer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_warning, container, false);
|
||||
|
||||
mReject = ViewUtils.$$(view, R.id.reject);
|
||||
mAccept = ViewUtils.$$(view, R.id.accept);
|
||||
|
||||
mReject.setOnClickListener(this);
|
||||
mAccept.setOnClickListener(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
mReject = null;
|
||||
mAccept = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mReject == v) {
|
||||
finishStage();
|
||||
} else if (mAccept == v) {
|
||||
// Never show this warning anymore
|
||||
Settings.putShowWarning(false);
|
||||
|
||||
// Start new scene and finish it self
|
||||
MainActivity activity = getMainActivity();
|
||||
if (null != activity) {
|
||||
startSceneForCheckStep(CHECK_STEP_WARNING, getArguments());
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import android.webkit.WebView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public class DialogWebChromeClient extends WebChromeClient {
|
||||
@@ -39,7 +39,7 @@ public class DialogWebChromeClient extends WebChromeClient {
|
||||
|
||||
@Override
|
||||
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
|
||||
new BaseDialogBuilder(view.getContext())
|
||||
new MaterialAlertDialogBuilder(view.getContext())
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
|
||||
.setOnCancelListener(dialog -> result.cancel())
|
||||
@@ -49,7 +49,7 @@ public class DialogWebChromeClient extends WebChromeClient {
|
||||
|
||||
@Override
|
||||
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
|
||||
new BaseDialogBuilder(view.getContext())
|
||||
new MaterialAlertDialogBuilder(view.getContext())
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel())
|
||||
@@ -67,7 +67,7 @@ public class DialogWebChromeClient extends WebChromeClient {
|
||||
final EditText valueView = promptView.findViewById(R.id.value);
|
||||
valueView.setText(defaultValue);
|
||||
|
||||
new BaseDialogBuilder(context)
|
||||
new MaterialAlertDialogBuilder(context)
|
||||
.setView(promptView)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm(valueView.getText().toString()))
|
||||
.setOnCancelListener(dialog -> result.cancel())
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhTagDatabase;
|
||||
@@ -668,7 +668,7 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
|
||||
|
||||
@Override
|
||||
public boolean onLongClick() {
|
||||
new BaseDialogBuilder(getContext())
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
.setMessage(getContext().getString(R.string.delete_search_history, mKeyword))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
|
||||
@@ -34,9 +34,9 @@ import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration
|
||||
import com.hippo.easyrecyclerview.SimpleHolder
|
||||
@@ -217,7 +217,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (mNormalSearchModeHelp === v) {
|
||||
BaseDialogBuilder(context)
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(R.string.search_tip)
|
||||
.show()
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ public abstract class GalleryProvider {
|
||||
public void stop() {
|
||||
OSUtils.checkMainLoop();
|
||||
mImageCache.evictAll();
|
||||
Image.lazyEvictAll();
|
||||
}
|
||||
|
||||
public void setGLRoot(GLRoot glRoot) {
|
||||
@@ -246,14 +245,18 @@ public abstract class GalleryProvider {
|
||||
}
|
||||
|
||||
public void add(Integer key, ImageWrapper value) {
|
||||
if (value.getFormat() != Image.FORMAT_ANIMATED)
|
||||
if (value.obtain())
|
||||
put(key, value);
|
||||
if (value.getFormat() != Image.FORMAT_GIF && value.obtain()) {
|
||||
put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(Integer key, ImageWrapper value) {
|
||||
return value.getWidth() * value.getHeight() * 4;
|
||||
int size = value.getWidth() * value.getHeight() * 4;
|
||||
if (value.getFormat() == Image.FORMAT_GIF) {
|
||||
size *= 5;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -153,6 +153,12 @@ public class ImageTexture implements Texture, Animatable {
|
||||
}
|
||||
|
||||
mTiles = list.toArray(new Tile[list.size()]);
|
||||
|
||||
if (!mImage.isCompleted()) {
|
||||
Runnable runnable = new AnimateRunnable();
|
||||
mAnimateRunnable = runnable;
|
||||
sThreadExecutor.execute(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private static Tile obtainSmallTile() {
|
||||
@@ -233,7 +239,7 @@ public class ImageTexture implements Texture, Animatable {
|
||||
}
|
||||
|
||||
boolean end = mReleased.get() || mImage.isImageRecycled() || mNeedRelease.get() ||
|
||||
(mImage.getFormat() == 0) || mRunning.get();
|
||||
(mImage.isCompleted() && mImage.getFrameCount() <= 1) || mRunning.get();
|
||||
|
||||
synchronized (mImage) {
|
||||
mImageBusy = false;
|
||||
@@ -591,11 +597,25 @@ public class ImageTexture implements Texture, Animatable {
|
||||
mImageBusy = true;
|
||||
}
|
||||
|
||||
if (!mImage.isCompleted()) {
|
||||
try {
|
||||
sPVLock.p();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
if (!mNeedRelease.get()) {
|
||||
mImage.complete();
|
||||
}
|
||||
sPVLock.v();
|
||||
}
|
||||
|
||||
int frameCount = mImage.getFrameCount();
|
||||
|
||||
synchronized (mImage) {
|
||||
// Release image
|
||||
mImageBusy = false;
|
||||
// Check need release, frameCount <= 1
|
||||
if (mNeedRelease.get() || mImage.getFormat() == 0) {
|
||||
if (mNeedRelease.get() || frameCount <= 1) {
|
||||
mAnimateRunnable = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,22 @@ public class ImageWrapper {
|
||||
return mCut.height();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#complete()
|
||||
*/
|
||||
public boolean complete() {
|
||||
synchronized (mImage) {
|
||||
return mImage.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#isCompleted()
|
||||
*/
|
||||
public boolean isCompleted() {
|
||||
return mImage.isCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int)
|
||||
*/
|
||||
@@ -152,6 +168,13 @@ public class ImageWrapper {
|
||||
return mImage.getDelay();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getFrameCount()
|
||||
*/
|
||||
public int getFrameCount() {
|
||||
return mImage.getFrameCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#isOpaque()
|
||||
*/
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.image;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* The {@code Image} is a image which stored pixel data in native heap
|
||||
*/
|
||||
public final class Image {
|
||||
public static final int FORMAT_NORMAL = 0;
|
||||
public static final int FORMAT_ANIMATED = 1;
|
||||
private static final ArrayList<Image> imageList = new ArrayList<>();
|
||||
private static boolean needEvictAll = false;
|
||||
private long mNativePtr;
|
||||
|
||||
private boolean needRecycle = false;
|
||||
|
||||
private Integer nativeProcessCnt = 0;
|
||||
|
||||
private Image(long nativePtr) {
|
||||
if (needEvictAll)
|
||||
evictAll();
|
||||
mNativePtr = nativePtr;
|
||||
imageList.add(this);
|
||||
}
|
||||
|
||||
private static Image newFromAddr(long addr) {
|
||||
if (addr == 0)
|
||||
return null;
|
||||
return new Image(addr);
|
||||
}
|
||||
|
||||
public static void lazyEvictAll() {
|
||||
needEvictAll = true;
|
||||
}
|
||||
|
||||
private static void evictAll() {
|
||||
(new ArrayList<>(imageList)).forEach(Image::recycle);
|
||||
needEvictAll = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(FileDescriptor fd, boolean partially) {
|
||||
return newFromAddr(nativeDecode(fd));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(Integer fd, boolean partially) {
|
||||
return newFromAddr(nativeDecodeFdInt(fd));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decodeAddr(Long addr, boolean partially) {
|
||||
return newFromAddr(nativeDecodeAddr(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@Nullable
|
||||
public static Image create(Bitmap bitmap) {
|
||||
return newFromAddr(nativeCreate(bitmap));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled {@code Image} instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
public static int getImageCount() {
|
||||
return imageList.size();
|
||||
}
|
||||
|
||||
private static native long nativeDecode(FileDescriptor fd);
|
||||
|
||||
private static native long nativeDecodeFdInt(int fd);
|
||||
|
||||
private static native long nativeDecodeAddr(long addr);
|
||||
|
||||
private static native long nativeCreate(Bitmap bitmap);
|
||||
|
||||
private static native void nativeRender(long nativePtr,
|
||||
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height);
|
||||
|
||||
private static native void nativeTexImage(long nativePtr, boolean init, int offsetX, int offsetY, int width, int height);
|
||||
|
||||
private static native void nativeAdvance(long nativePtr);
|
||||
|
||||
private static native int nativeGetDelay(long nativePtr);
|
||||
|
||||
private static native boolean nativeIsOpaque(long nativePtr);
|
||||
|
||||
private static native void nativeRecycle(long nativePtr);
|
||||
|
||||
private static native int nativeGetFormat(long nativePtr);
|
||||
|
||||
private static native int nativeGetWidth(long nativePtr);
|
||||
|
||||
private static native int nativeGetHeight(long nativePtr);
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
public int getFormat() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetFormat(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the image
|
||||
*/
|
||||
public int getWidth() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetWidth(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the image
|
||||
*/
|
||||
public int getHeight() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetHeight(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private synchronized void checkRecycledAndLock() {
|
||||
if (mNativePtr == 0) {
|
||||
throw new IllegalStateException("The image is recycled.");
|
||||
}
|
||||
nativeProcessCnt++;
|
||||
}
|
||||
|
||||
private synchronized void releaseNativeLock() {
|
||||
nativeProcessCnt--;
|
||||
if (needRecycle)
|
||||
recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the image to {@code Bitmap}
|
||||
*/
|
||||
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height, boolean fillBlank, int defaultColor) {
|
||||
checkRecycledAndLock();
|
||||
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@code glTexImage2D} for init is true and
|
||||
* call {@code glTexSubImage2D} for init is false.
|
||||
* width * height must <= 512 * 512 or do nothing
|
||||
*/
|
||||
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
|
||||
checkRecycledAndLock();
|
||||
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to next frame. Do nothing for non-animation image
|
||||
*/
|
||||
public void advance() {
|
||||
checkRecycledAndLock();
|
||||
nativeAdvance(mNativePtr);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current frame delay. 0 for non-animation image
|
||||
*/
|
||||
public int getDelay() {
|
||||
checkRecycledAndLock();
|
||||
int delay = nativeGetDelay(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return delay <= 10 ? 100 : delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return is the image opaque
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
checkRecycledAndLock();
|
||||
boolean ret = nativeIsOpaque(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the native object associated with this image.
|
||||
* It must be called when the image will not be used.
|
||||
* The image can't be used after this method is called.
|
||||
*/
|
||||
public synchronized void recycle() {
|
||||
if (mNativePtr != 0) {
|
||||
if (nativeProcessCnt != 0) {
|
||||
needRecycle = true;
|
||||
return;
|
||||
}
|
||||
nativeRecycle(mNativePtr);
|
||||
mNativePtr = 0;
|
||||
imageList.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this image has been recycled.
|
||||
*/
|
||||
public boolean isRecycled() {
|
||||
return mNativePtr == 0;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
/**
|
||||
@@ -255,7 +255,7 @@ public abstract class DialogPreference extends Preference implements
|
||||
|
||||
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
|
||||
|
||||
mBuilder = new BaseDialogBuilder(context)
|
||||
mBuilder = new MaterialAlertDialogBuilder(context)
|
||||
.setTitle(mDialogTitle)
|
||||
.setIcon(mDialogIcon)
|
||||
.setPositiveButton(mPositiveButtonText, this)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -33,7 +33,6 @@ final class DocumentsContractApi21 {
|
||||
private static final String TAG = DocumentsContractApi21.class.getSimpleName();
|
||||
private static final String PATH_DOCUMENT = "document";
|
||||
private static final String PATH_TREE = "tree";
|
||||
|
||||
private DocumentsContractApi21() {
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ class FileDescriptorRandomAccessFile implements UniRandomAccessFile {
|
||||
private final int flushAfterWrite;
|
||||
private final Object closeLock = new Object();
|
||||
private volatile boolean closed = false;
|
||||
|
||||
private FileDescriptorRandomAccessFile(ParcelFileDescriptor pfd, FileDescriptor fd, String mode) {
|
||||
this.pfd = pfd;
|
||||
this.fd = fd;
|
||||
|
||||
@@ -82,13 +82,6 @@ public class BatteryView extends AppCompatTextView {
|
||||
}
|
||||
mCurrentColor = color;
|
||||
super.setTextColor(color);
|
||||
}
|
||||
|
||||
private void startCharger() {
|
||||
if (!mIsChargerWorking) {
|
||||
getHandler().post(mCharger);
|
||||
mIsChargerWorking = true;
|
||||
}
|
||||
} private final Runnable mCharger = new Runnable() {
|
||||
|
||||
private int level = 0;
|
||||
@@ -104,6 +97,13 @@ public class BatteryView extends AppCompatTextView {
|
||||
}
|
||||
};
|
||||
|
||||
private void startCharger() {
|
||||
if (!mIsChargerWorking) {
|
||||
getHandler().post(mCharger);
|
||||
mIsChargerWorking = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void stopCharger() {
|
||||
if (mIsChargerWorking) {
|
||||
getHandler().removeCallbacks(mCharger);
|
||||
@@ -145,7 +145,6 @@ public class BatteryView extends AppCompatTextView {
|
||||
|
||||
|
||||
|
||||
|
||||
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
|
||||
Loaded 100 of 236 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user