Compare commits
127
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e568e688f5 | ||
|
|
a201c930c2 | ||
|
|
7d927dde6b | ||
|
|
f76fece81d | ||
|
|
069d7e2382 | ||
|
|
e2e0562719 | ||
|
|
347aaadb43 | ||
|
|
6ddd64232f | ||
|
|
e532b3b05b | ||
|
|
d7c17d80e7 | ||
|
|
b35980cf60 | ||
|
|
263853d6a7 | ||
|
|
6a100b8250 | ||
|
|
893560e589 | ||
|
|
6b7ce7de5b | ||
|
|
6082d8cc1a | ||
|
|
ec37c9cab2 | ||
|
|
309e2a715e | ||
|
|
fbb4afa4f7 | ||
|
|
90863b95f4 | ||
|
|
3576eaf3ff | ||
|
|
f14c9741ea | ||
|
|
6d04e51e10 | ||
|
|
6ea1aa6c1a | ||
|
|
c86e1a9409 | ||
|
|
f1845f2d4e | ||
|
|
a20ca4ea2e | ||
|
|
0effc0d181 | ||
|
|
7ce273ec67 | ||
|
|
39e080cdca | ||
|
|
17584ae5fb | ||
|
|
c46c72d66d | ||
|
|
36f00ab568 | ||
|
|
dae7dacf3d | ||
|
|
b69b72cd1b | ||
|
|
68d1e39410 | ||
|
|
d4482dec23 | ||
|
|
8f36cecc7a | ||
|
|
a59fbcc9c8 | ||
|
|
25cddb2d61 | ||
|
|
9a7af235b8 | ||
|
|
c9751ab2a2 | ||
|
|
5e13986cc0 | ||
|
|
35e2cc535e | ||
|
|
81355a0248 | ||
|
|
3e98502e2d | ||
|
|
6973fa4538 | ||
|
|
20191c3f46 | ||
|
|
f084fec54e | ||
|
|
367e3b3a93 | ||
|
|
36383b6f47 | ||
|
|
9ecd14820e | ||
|
|
be552e92a1 | ||
|
|
1bf676fd56 | ||
|
|
154a96b5fd | ||
|
|
443c384f41 | ||
|
|
85c7829636 | ||
|
|
68e3bb8d66 | ||
|
|
63b2e52eaf | ||
|
|
c7492f3faf | ||
|
|
9052343ef7 | ||
|
|
00c09e4a1a | ||
|
|
086c0d9f28 | ||
|
|
3ca0189f28 | ||
|
|
8f5a2ba85d | ||
|
|
1ab9811d7a | ||
|
|
0b720848ba | ||
|
|
eebd95e700 | ||
|
|
913e02f0fa | ||
|
|
10fdab2473 | ||
|
|
26710761ed | ||
|
|
17bf6ac12b | ||
|
|
6a01b7729d | ||
|
|
039e817002 | ||
|
|
3ea1e1914a | ||
|
|
e6c5152123 | ||
|
|
7bdaf186f3 | ||
|
|
b6b4e826cb | ||
|
|
949f3cb246 | ||
|
|
501855a8c0 | ||
|
|
44634be13d | ||
|
|
7c718df1af | ||
|
|
16cca9cbbd | ||
|
|
b715320298 | ||
|
|
d31d9eb137 | ||
|
|
b53c29c035 | ||
|
|
d3bf20bee6 | ||
|
|
cff445ddf5 | ||
|
|
877d96a50a | ||
|
|
12863dfa21 | ||
|
|
959959d880 | ||
|
|
cd676eabfc | ||
|
|
0a3e95c8d4 | ||
|
|
35b483e655 | ||
|
|
7dae7b72f4 | ||
|
|
620f5df241 | ||
|
|
d5ddb6eb8c | ||
|
|
33c9b9d4e4 | ||
|
|
216cafbe54 | ||
|
|
72a644bd3b | ||
|
|
7008f0bc89 | ||
|
|
289d3ea287 | ||
|
|
92ae157f00 | ||
|
|
9800f7c01b | ||
|
|
8281af4590 | ||
|
|
d609e5e113 | ||
|
|
238d769f27 | ||
|
|
1e648cc6b4 | ||
|
|
7ac20d59fc | ||
|
|
1d8ab51a22 | ||
|
|
cdfb3b74b3 | ||
|
|
6f2868f7c3 | ||
|
|
f3061fa497 | ||
|
|
468cce5f3d | ||
|
|
bfaba80d3b | ||
|
|
2ca4a2af86 | ||
|
|
7d0b245d3d | ||
|
|
96fcf69bdd | ||
|
|
63d95b1265 | ||
|
|
d173e83e01 | ||
|
|
54d11dea95 | ||
|
|
fbdab389c4 | ||
|
|
3dd3bad362 | ||
|
|
65a9a4b4f2 | ||
|
|
5f6cf5550d | ||
|
|
673dad4032 | ||
|
|
5e45ec9a9c |
No files matched your search
@@ -32,11 +32,11 @@ jobs:
|
||||
|
||||
- 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
|
||||
cp app/build/outputs/apk/release/app-universal-release.apk app/build/outputs/apk/release/EhViewer-universal.apk
|
||||
cp app/build/outputs/apk/release/app-arm64-v8a-release.apk app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
||||
cp app/build/outputs/apk/release/app-x86_64-release.apk app/build/outputs/apk/release/EhViewer-x86_64.apk
|
||||
cp app/build/outputs/apk/release/app-x86-release.apk app/build/outputs/apk/release/EhViewer-x86.apk
|
||||
cp app/build/outputs/apk/release/app-armeabi-v7a-release.apk app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
||||
|
||||
- name: Releases
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -44,11 +44,11 @@ jobs:
|
||||
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
|
||||
app/build/outputs/apk/release/EhViewer-universal.apk
|
||||
app/build/outputs/apk/release/EhViewer-arm64-v8a.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86_64.apk
|
||||
app/build/outputs/apk/release/EhViewer-x86.apk
|
||||
app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
|
||||
|
||||
- name: Collect Apks
|
||||
run: |
|
||||
|
||||
@@ -64,16 +64,10 @@ EhViewer fork dedicated to lightweight and high-performance
|
||||
|
||||
with Material Design 3 and Dynamic Color Support
|
||||
|
||||
Maintained by Tarsin Norbin aka 飛鳥澪 (asuka-mio)
|
||||
|
||||
Also thanks @txyyh for her help in docs, release notes, design and 1.7.x.x maintenance
|
||||
|
||||
# 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>
|
||||
|
||||
There are two development branches: 1.8.x.x and 1.7.x.x . Version 1.8 only support platform api 31 (aka Android S) and above, also the develop mainline. Version 1.7 should only be used on Android 11 and below, since these platforms lack native api of image decoding.
|
||||
|
||||
# Screenshot
|
||||
|
||||

|
||||
@@ -84,6 +78,7 @@ There are two development branches: 1.8.x.x and 1.7.x.x . Version 1.8 only suppo
|
||||
Here is the libraries
|
||||
|
||||
- [AOSP & AndroidX](http://source.android.com/)
|
||||
- [Coil](https://coil-kt.github.io/coil/)
|
||||
- [FullDraggableDrawer](https://github.com/PureWriter/FullDraggableDrawer)
|
||||
- [GnuTLS](https://gnutls.org/)
|
||||
- [material-components-android](https://github.com/material-components/material-components-android)
|
||||
|
||||
+23
-13
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
buildToolsVersion "33.0.1"
|
||||
ndkVersion "25.1.8937393"
|
||||
|
||||
splits {
|
||||
@@ -29,10 +29,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 33
|
||||
versionCode 180020
|
||||
versionName "1.8.3.0-beta02"
|
||||
versionCode 180026
|
||||
versionName "1.8.4.0-alpha02"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -43,12 +43,22 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
sourceCompatibility JavaVersion.VERSION_19
|
||||
targetCompatibility JavaVersion.VERSION_19
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
jvmTarget = '19'
|
||||
freeCompilerArgs += [
|
||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||
"-progressive",
|
||||
// Generate smaller bytecode by not generating runtime not-null assertions.
|
||||
"-Xno-call-assertions",
|
||||
"-Xno-param-assertions",
|
||||
"-Xno-receiver-assertions",
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
|
||||
]
|
||||
}
|
||||
|
||||
lint {
|
||||
@@ -82,6 +92,7 @@ android {
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
compose true
|
||||
}
|
||||
|
||||
@@ -122,7 +133,7 @@ dependencies {
|
||||
|
||||
implementation "androidx.compose.material3:material3:1.0.1"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
|
||||
def core_version = "1.9.0"
|
||||
@@ -132,7 +143,7 @@ dependencies {
|
||||
implementation "androidx.customview:customview:1.2.0-alpha02"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.6.0-alpha03"
|
||||
def fragment_version = "1.6.0-alpha04"
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
@@ -157,7 +168,7 @@ dependencies {
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
|
||||
|
||||
def room_version = "2.5.0-beta02"
|
||||
def room_version = "2.5.0-rc01"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-paging:$room_version"
|
||||
@@ -169,20 +180,19 @@ 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 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.8.0-SNAPSHOT'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||
|
||||
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
|
||||
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
|
||||
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
||||
implementation 'io.coil-kt:coil:2.2.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.3'
|
||||
}
|
||||
|
||||
Vendored
-4
@@ -14,10 +14,6 @@
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
|
||||
-keepnames class com.hippo.ehviewer.ui.scene.* { }
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
-renamesourcefileattribute
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
project(ehviewer)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto=thin \
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -funroll-loops -flto=thin \
|
||||
-mllvm -polly \
|
||||
-mllvm -polly-run-dce \
|
||||
-mllvm -polly-run-inliner \
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <syscall.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
@@ -51,6 +52,8 @@ static char *passwd = NULL;
|
||||
static void *archiveAddr = NULL;
|
||||
static size_t archiveSize = 0;
|
||||
|
||||
#define POPULATE_THRESHOLD (512 * 1024 * 1024) // 512 MiB
|
||||
|
||||
const char supportExt[9][6] = {
|
||||
"jpeg",
|
||||
"jpg",
|
||||
@@ -198,7 +201,10 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
archiveAddr = mmap64(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
int mmap_flags = MAP_PRIVATE;
|
||||
if (size <= POPULATE_THRESHOLD)
|
||||
mmap_flags |= MAP_POPULATE;
|
||||
archiveAddr = mmap64(0, size, PROT_READ, mmap_flags, fd, 0);
|
||||
if (archiveAddr == MAP_FAILED) {
|
||||
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
|
||||
return 0;
|
||||
@@ -249,8 +255,8 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
return r;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToAddr(JNIEnv *env, jobject thiz, jint index) {
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToByteBuffer(JNIEnv *env, jobject thiz, jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
@@ -259,17 +265,16 @@ Java_com_hippo_UriArchiveAccessor_extractToAddr(JNIEnv *env, jobject thiz, jint
|
||||
if (ret)
|
||||
return 0;
|
||||
long long size = archive_entry_size(ctx->entry);
|
||||
void *buffer = malloc(size + sizeof(long long));
|
||||
void *buffer = malloc(size);
|
||||
if (!buffer) {
|
||||
ctx->using = 0;
|
||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||
return 0;
|
||||
}
|
||||
*(long long *) buffer = size;
|
||||
ret = archive_read_data(ctx->arc, buffer + sizeof(long long), size);
|
||||
ret = archive_read_data(ctx->arc, buffer, size);
|
||||
ctx->using = 0;
|
||||
if (ret == size)
|
||||
return (jlong) buffer;
|
||||
return (*env)->NewDirectByteBuffer(env, buffer, size);
|
||||
if (ret != size)
|
||||
LOGE("%s", "No enough data read, WTF?");
|
||||
if (ret < 0)
|
||||
@@ -354,4 +359,10 @@ Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint in
|
||||
archive_read_data_into_fd(ctx->arc, fd);
|
||||
ctx->using = 0;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_releaseByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) {
|
||||
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
free(addr);
|
||||
}
|
||||
+12
-211
@@ -20,58 +20,21 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/bitmap.h>
|
||||
#include <android/data_space.h>
|
||||
#include <android/imagedecoder.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
#include <jni.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__)
|
||||
|
||||
#include "ehviewer.h"
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -159,138 +122,17 @@ bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
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 long size) {
|
||||
AImageDecoder *decoder;
|
||||
AImageDecoder_createFromBuffer((const void *) addr + sizeof(long long), 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jint fd) {
|
||||
IMAGE *image = createFromFd(fd);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
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) {
|
||||
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jobject bitmap, jboolean init,
|
||||
jint offset_x, jint offset_y, jint width, jint height) {
|
||||
if (width * height > IMAGE_TILE_MAX_SIZE)
|
||||
return;
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, tile_buffer, width, height, 0, 0, width, height);
|
||||
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
copy_pixels(pixels, info.width, info.height, offset_x, offset_y, tile_buffer, width, height, 0, 0, width, height);
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
if (init) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
@@ -299,44 +141,3 @@ Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jlong ptr,
|
||||
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) {
|
||||
IMAGE *image = createFromAddr((void *) addr, *(long long *) addr);
|
||||
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/libarchive updated: 37d2cf6063...67b9c960d2.
+1
-1
Submodule app/src/main/cpp/liblzma updated: 2327a461e1...12b86bd8ca.
@@ -20,7 +20,6 @@ package com.hippo
|
||||
import java.io.FileDescriptor
|
||||
|
||||
object Native {
|
||||
@JvmStatic
|
||||
fun initialize() {
|
||||
System.loadLibrary("ehviewer")
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.hippo
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
val pfd by lazy { ctx.contentResolver.openFileDescriptor(uri, "r")!! }
|
||||
@@ -27,7 +28,9 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
}
|
||||
|
||||
private external fun openArchive(fd: Int, size: Long): Int
|
||||
external fun extractToAddr(index: Int): Long
|
||||
external fun extractToByteBuffer(index: Int): ByteBuffer?
|
||||
|
||||
external fun releaseByteBuffer(buffer: ByteBuffer)
|
||||
external fun extractToFd(index: Int, fd: Int)
|
||||
external fun getFilename(index: Int): String
|
||||
external fun needPassword(): Boolean
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.METHOD})
|
||||
public @interface Implemented {
|
||||
Class value();
|
||||
}
|
||||
@@ -1,20 +1,8 @@
|
||||
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().apply {
|
||||
window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) : MaterialAlertDialogBuilder(context)
|
||||
@@ -1,493 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.beerbelly;
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public abstract class BeerBelly<V> {
|
||||
|
||||
private static final String TAG = BeerBelly.class.getSimpleName();
|
||||
private final boolean mHasMemoryCache;
|
||||
private final boolean mHasDiskCache;
|
||||
@Nullable
|
||||
private MemoryCache<V> mMemoryCache;
|
||||
@Nullable
|
||||
private DiskCache<V> mDiskCache;
|
||||
|
||||
public BeerBelly(BeerBellyParams params) {
|
||||
params.isValid();
|
||||
mHasMemoryCache = params.hasMemoryCache;
|
||||
mHasDiskCache = params.hasDiskCache;
|
||||
|
||||
if (mHasMemoryCache) {
|
||||
initMemoryCache(params.memoryCacheMaxSize);
|
||||
}
|
||||
|
||||
if (mHasDiskCache) {
|
||||
initDiskCache(params.diskCacheDir, params.diskCacheMaxSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void initMemoryCache(int maxSize) {
|
||||
mMemoryCache = new MemoryCache<>(maxSize, this);
|
||||
}
|
||||
|
||||
private void initDiskCache(File cacheDir, int maxSize) {
|
||||
// Set up disk cache
|
||||
try {
|
||||
mDiskCache = new DiskCache<>(cacheDir, maxSize, this);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Can't create disk cache", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int sizeOf(String key, V value);
|
||||
|
||||
protected abstract void memoryEntryAdded(V value);
|
||||
|
||||
protected abstract void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue);
|
||||
|
||||
protected abstract V read(@NonNull InputStreamPipe isPipe);
|
||||
|
||||
protected abstract boolean write(OutputStream os, V value);
|
||||
|
||||
@Nullable
|
||||
public LruCache<String, V> getMemoryCache() {
|
||||
return mMemoryCache;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SimpleDiskCache getDiskCache() {
|
||||
if (mDiskCache != null) {
|
||||
return mDiskCache.mDiskCache;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if have memory cache
|
||||
*
|
||||
* @return true if have memory cache
|
||||
*/
|
||||
public boolean hasMemoryCache() {
|
||||
return mHasMemoryCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if have disk cache
|
||||
*
|
||||
* @return true if have disk cache
|
||||
*/
|
||||
public boolean hasDiskCache() {
|
||||
return mHasDiskCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory cache usage size
|
||||
*
|
||||
* @return {@code -1} for no memory cache
|
||||
*/
|
||||
public int memorySize() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.size();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get memory cache max size
|
||||
*
|
||||
* @return {@code -1} for no memory cache
|
||||
*/
|
||||
public int memoryMaxSize() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.maxSize();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disk cache usage size
|
||||
*
|
||||
* @return {@code -1L} for no memory cache
|
||||
*/
|
||||
public long diskSize() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.size();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disk cache max size
|
||||
*
|
||||
* @return {@code -1L} for no memory cache
|
||||
*/
|
||||
public long diskMaxSize() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.getMaxSize();
|
||||
} else {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from memory cache
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get, null for miss or no memory cache
|
||||
*/
|
||||
public V getFromMemory(@NonNull String key) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
return mMemoryCache.get(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from disk cache. Override {@link #read(InputStreamPipe)} to do it
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get, null for miss or no memory cache or get error
|
||||
*/
|
||||
public V getFromDisk(@NonNull String key) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.get(key);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from memory cache and disk cache. If miss in memory cache and
|
||||
* get in disk cache, it will put value from disk cache to memory cache.
|
||||
*
|
||||
* @param key the key to get value
|
||||
* @return the value you get
|
||||
*/
|
||||
public V get(@NonNull String key) {
|
||||
V value = getFromMemory(key);
|
||||
|
||||
if (value != null) {
|
||||
// Get it in memory cache
|
||||
return value;
|
||||
}
|
||||
|
||||
value = getFromDisk(key);
|
||||
|
||||
if (value != null) {
|
||||
// Get it in disk cache
|
||||
putToMemory(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to memory cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return false if no memory cache
|
||||
*/
|
||||
public boolean putToMemory(@NonNull String key, @NonNull V value) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
memoryEntryAdded(value);
|
||||
mMemoryCache.put(key, value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to disk cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
* @return false if no disk cache or get error
|
||||
*/
|
||||
public boolean putToDisk(@NonNull String key, @NonNull V value) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.put(key, value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put value to memory cache and disk cache
|
||||
*
|
||||
* @param key the key
|
||||
* @param value the value
|
||||
*/
|
||||
public void put(@NonNull String key, @NonNull V value) {
|
||||
putToMemory(key, value);
|
||||
putToDisk(key, value);
|
||||
}
|
||||
|
||||
public void removeFromMemory(@NonNull String key) {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
mMemoryCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFromDisk(@NonNull String key) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(@NonNull String key) {
|
||||
removeFromMemory(key);
|
||||
removeFromDisk(key);
|
||||
}
|
||||
|
||||
public boolean pullFromDiskCache(@NonNull String key, @NonNull OutputStream os) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.pull(key, os);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key the key
|
||||
* @param is the input stream to store
|
||||
* @return false if no disk cache or get error
|
||||
*/
|
||||
public boolean putRawToDisk(@NonNull String key, @NonNull InputStream is) {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
return mDiskCache.putRaw(key, is);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all of the items from the memory cache
|
||||
*/
|
||||
public void clearMemory() {
|
||||
if (mHasMemoryCache && mMemoryCache != null) {
|
||||
mMemoryCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear disk cache
|
||||
*/
|
||||
public void clearDisk() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (mHasDiskCache && mDiskCache != null) {
|
||||
mDiskCache.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all of the items from the memory cache and lets the system know
|
||||
* now would be a good time to garbage collect
|
||||
*/
|
||||
public void clear() {
|
||||
clearMemory();
|
||||
clearDisk();
|
||||
}
|
||||
|
||||
/**
|
||||
* A holder class that contains cache parameters.
|
||||
*/
|
||||
public static class BeerBellyParams {
|
||||
|
||||
/**
|
||||
* is memory cache available
|
||||
*/
|
||||
public boolean hasMemoryCache = false;
|
||||
/**
|
||||
* the maximum number of bytes the memory cache should use to store
|
||||
*/
|
||||
public int memoryCacheMaxSize = 0;
|
||||
/**
|
||||
* is disk cache available
|
||||
*/
|
||||
public boolean hasDiskCache = false;
|
||||
/**
|
||||
* the dir to store disk cache
|
||||
*/
|
||||
public File diskCacheDir = null;
|
||||
/**
|
||||
* the maximum number of bytes the disk cache should use to store
|
||||
*/
|
||||
public int diskCacheMaxSize = 0;
|
||||
|
||||
/**
|
||||
* Check BeerBellyParams is valid
|
||||
*/
|
||||
public void isValid() throws IllegalStateException {
|
||||
if (!hasMemoryCache && !hasDiskCache) {
|
||||
throw new IllegalStateException("No memory cache and no disk cache. What can I do for you?");
|
||||
}
|
||||
|
||||
if (hasMemoryCache && memoryCacheMaxSize <= 0) {
|
||||
throw new IllegalStateException("Memory cache max size must be bigger than 0.");
|
||||
}
|
||||
|
||||
if (hasDiskCache) {
|
||||
if (diskCacheDir == null) {
|
||||
throw new IllegalStateException("Disk cache dir can't be null.");
|
||||
}
|
||||
if (diskCacheMaxSize <= 0) {
|
||||
throw new IllegalStateException("Disk cache max size must be bigger than 0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DiskCache<E> {
|
||||
|
||||
private static final int IO_BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
private final SimpleDiskCache mDiskCache;
|
||||
private final BeerBelly<E> mParent;
|
||||
|
||||
private final File mCacheDir;
|
||||
private final int mMaxSize;
|
||||
|
||||
public DiskCache(File cacheDir, int size, BeerBelly<E> parent) throws IOException {
|
||||
mDiskCache = new SimpleDiskCache(cacheDir, size);
|
||||
mParent = parent;
|
||||
|
||||
mCacheDir = cacheDir;
|
||||
mMaxSize = size;
|
||||
}
|
||||
|
||||
public File getCacheDir() {
|
||||
return mCacheDir;
|
||||
}
|
||||
|
||||
public int getMaxSize() {
|
||||
return mMaxSize;
|
||||
}
|
||||
|
||||
public long size() {
|
||||
return mDiskCache.size();
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
mDiskCache.flush();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mDiskCache.clear();
|
||||
}
|
||||
|
||||
public E get(String key) {
|
||||
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
|
||||
if (isPipe == null) {
|
||||
return null;
|
||||
} else {
|
||||
return mParent.read(isPipe);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean put(String key, E value) {
|
||||
OutputStreamPipe osPipe = mDiskCache.getOutputStreamPipe(key);
|
||||
BufferedOutputStream buffOut = null;
|
||||
try {
|
||||
osPipe.obtain();
|
||||
OutputStream os = osPipe.open();
|
||||
buffOut = new BufferedOutputStream(os, IO_BUFFER_SIZE);
|
||||
return mParent.write(os, value);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
Util.closeQuietly(buffOut);
|
||||
osPipe.close();
|
||||
osPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean putRaw(String key, InputStream is) {
|
||||
return mDiskCache.put(key, is);
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
mDiskCache.remove(key);
|
||||
}
|
||||
|
||||
public boolean pull(@NonNull String key, @NonNull OutputStream os) {
|
||||
InputStreamPipe isPipe = mDiskCache.getInputStreamPipe(key);
|
||||
if (isPipe == null) {
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
InputStream is = isPipe.open();
|
||||
Util.copy(is, os);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
isPipe.close();
|
||||
isPipe.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MemoryCache<E> extends LruCache<String, E> {
|
||||
|
||||
public BeerBelly<E> mParent;
|
||||
|
||||
public MemoryCache(int maxSize, BeerBelly<E> parent) {
|
||||
super(maxSize);
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(String key, E value) {
|
||||
return mParent.sizeOf(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entryRemoved(boolean evicted, String key, E oldValue, E newValue) {
|
||||
mParent.memoryEntryRemoved(evicted, key, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,21 +231,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()) {
|
||||
@@ -264,7 +250,21 @@ public final class DiskLruCache implements Closeable {
|
||||
|
||||
private static String inputStreamToString(InputStream in) throws IOException {
|
||||
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
|
||||
}
|
||||
} 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 void readJournal() throws IOException {
|
||||
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
@@ -140,7 +139,7 @@ class StrictLineReader implements Closeable {
|
||||
for (int i = pos; i != end; ++i) {
|
||||
if (buf[i] == LF) {
|
||||
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
|
||||
String res = new String(buf, pos, lineEnd - pos, charset.name());
|
||||
String res = new String(buf, pos, lineEnd - pos, charset);
|
||||
pos = i + 1;
|
||||
return res;
|
||||
}
|
||||
@@ -152,11 +151,7 @@ class StrictLineReader implements Closeable {
|
||||
@Override
|
||||
public String toString() {
|
||||
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
||||
try {
|
||||
return new String(buf, 0, length, charset.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e); // Since we control the charset this will never happen.
|
||||
}
|
||||
return new String(buf, 0, length, charset);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.yorozuya.thread.PriorityThreadFactory;
|
||||
import com.hippo.yorozuya.thread.SerialThreadExecutor;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.LinkedList;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public final class Conaco<V> {
|
||||
|
||||
public static final int SOURCE_MEMORY = 0;
|
||||
public static final int SOURCE_DISK = 1;
|
||||
public static final int SOURCE_NETWORK = 2;
|
||||
private static final String TAG = Conaco.class.getSimpleName();
|
||||
private final ValueHelper<V> mHelper;
|
||||
private final ValueCache<V> mCache;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
private final Register<V> mRegister;
|
||||
private final SerialThreadExecutor mDiskExecutor;
|
||||
private final ThreadPoolExecutor mNetworkExecutor;
|
||||
private final AtomicInteger mIdGenerator;
|
||||
private final boolean mDebug;
|
||||
|
||||
private Conaco(Builder<V> builder) {
|
||||
mHelper = builder.objectHelper;
|
||||
|
||||
BeerBelly.BeerBellyParams beerBellyParams = new BeerBelly.BeerBellyParams();
|
||||
beerBellyParams.hasMemoryCache = builder.hasMemoryCache;
|
||||
beerBellyParams.memoryCacheMaxSize = builder.memoryCacheMaxSize;
|
||||
beerBellyParams.hasDiskCache = builder.hasDiskCache;
|
||||
beerBellyParams.diskCacheDir = builder.diskCacheDir;
|
||||
beerBellyParams.diskCacheMaxSize = builder.diskCacheMaxSize;
|
||||
|
||||
mCache = new ValueCache<>(beerBellyParams, mHelper);
|
||||
mOkHttpClient = builder.okHttpClient;
|
||||
|
||||
mRegister = new Register<>();
|
||||
|
||||
mDiskExecutor = new SerialThreadExecutor(3000L, new LinkedList<>(),
|
||||
new PriorityThreadFactory("Conaco-Disk", Process.THREAD_PRIORITY_BACKGROUND));
|
||||
mNetworkExecutor = new ThreadPoolExecutor(3, 3, 5L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(),
|
||||
new PriorityThreadFactory("Conaco-Network", Process.THREAD_PRIORITY_BACKGROUND));
|
||||
|
||||
mIdGenerator = new AtomicInteger();
|
||||
|
||||
mDebug = builder.debug;
|
||||
}
|
||||
|
||||
public BeerBelly getBeerBelly() {
|
||||
return mCache;
|
||||
}
|
||||
|
||||
private void onUnregisterConacoTask(ConacoTask<V> task) {
|
||||
ConacoTask<V> next = mRegister.getByKey(task.getKey());
|
||||
if (next != null) {
|
||||
startConacoTask(next);
|
||||
}
|
||||
}
|
||||
|
||||
private void startConacoTask(ConacoTask<V> task) {
|
||||
Unikery<V> unikery = task.getUnikery();
|
||||
|
||||
// Can't unikery is gone, finish the task now
|
||||
if (unikery == null) {
|
||||
finishConacoTask(task);
|
||||
return;
|
||||
}
|
||||
|
||||
String key = task.getKey();
|
||||
V value = null;
|
||||
|
||||
// Get from memory
|
||||
if (key != null && task.useMemoryCache() && mHelper.useMemoryCache(key, null)) {
|
||||
value = mCache.getFromMemory(key);
|
||||
}
|
||||
|
||||
if (value == null || !unikery.onGetValue(value, SOURCE_MEMORY)) {
|
||||
unikery.onMiss(SOURCE_MEMORY);
|
||||
task.start();
|
||||
} else {
|
||||
// Get the object, finish the task
|
||||
finishConacoTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void load(ConacoTask.Builder<V> builder) {
|
||||
builder.isValid();
|
||||
|
||||
if (mDebug) {
|
||||
Log.d(TAG, "Key " + builder.getKey());
|
||||
Log.d(TAG, "Url " + builder.getUrl());
|
||||
}
|
||||
|
||||
Unikery<V> unikery = builder.getUnikery();
|
||||
|
||||
// Cancel first
|
||||
cancel(unikery);
|
||||
|
||||
// Build conaco task
|
||||
int id;
|
||||
//noinspection StatementWithEmptyBody
|
||||
while ((id = mIdGenerator.getAndIncrement()) == Unikery.INVALID_ID) ;
|
||||
unikery.setTaskId(id);
|
||||
builder.setId(id)
|
||||
.setHelper(mHelper)
|
||||
.setCache(mCache)
|
||||
.setOkHttpClient(mOkHttpClient)
|
||||
.setDiskExecutor(mDiskExecutor)
|
||||
.setNetworkExecutor(mNetworkExecutor)
|
||||
.setConaco(this);
|
||||
ConacoTask<V> task = builder.build();
|
||||
|
||||
if (!mRegister.register(id, task)) {
|
||||
startConacoTask(task);
|
||||
} else {
|
||||
// The key is repeated, wait
|
||||
unikery.onWait();
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void cancel(Unikery<V> unikery) {
|
||||
int id = unikery.getTaskId();
|
||||
if (id != Unikery.INVALID_ID) {
|
||||
unikery.setTaskId(Unikery.INVALID_ID);
|
||||
ConacoTask<V> task = mRegister.unregister(id);
|
||||
if (task != null) {
|
||||
task.stop();
|
||||
// Don't need unikery anymore
|
||||
task.clearUnikery();
|
||||
// Callback
|
||||
onUnregisterConacoTask(task);
|
||||
} else {
|
||||
Log.e(TAG, "Can't find conaco task by id " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLoading(Unikery<V> unikery) {
|
||||
int id = unikery.getTaskId();
|
||||
return id != Unikery.INVALID_ID && mRegister.contain(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister task, reset unikery id, clear unikery in task, call next
|
||||
*/
|
||||
void finishConacoTask(ConacoTask<V> task) {
|
||||
mRegister.unregister(task.getId());
|
||||
Unikery unikery = task.getUnikery();
|
||||
if (unikery != null) {
|
||||
unikery.setTaskId(Unikery.INVALID_ID);
|
||||
}
|
||||
task.clearUnikery();
|
||||
|
||||
onUnregisterConacoTask(task);
|
||||
}
|
||||
|
||||
@IntDef({SOURCE_MEMORY, SOURCE_DISK, SOURCE_NETWORK})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Source {
|
||||
}
|
||||
|
||||
public static class Builder<T> extends BeerBelly.BeerBellyParams {
|
||||
/**
|
||||
* The client to get image from internet
|
||||
*/
|
||||
public OkHttpClient okHttpClient = null;
|
||||
|
||||
/**
|
||||
* Decode, get size and others
|
||||
*/
|
||||
public ValueHelper<T> objectHelper = null;
|
||||
|
||||
public boolean debug = false;
|
||||
|
||||
@Override
|
||||
public void isValid() throws IllegalStateException {
|
||||
super.isValid();
|
||||
|
||||
if (okHttpClient == null) {
|
||||
throw new IllegalStateException("No http client? How can I load image via url?");
|
||||
}
|
||||
}
|
||||
|
||||
public Conaco<T> build() {
|
||||
isValid();
|
||||
return new Conaco<>(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,487 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.okhttp.ChromeRequestBuilder;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "deprecation"})
|
||||
public final class ConacoTask<V> {
|
||||
|
||||
private static final String TAG = ConacoTask.class.getSimpleName();
|
||||
|
||||
private final int mId;
|
||||
private final WeakReference<Unikery<V>> mUnikeryWeakReference;
|
||||
private final String mKey;
|
||||
private final String mUrl;
|
||||
private final boolean mUseMemoryCache;
|
||||
private final boolean mUseDiskCache;
|
||||
private final boolean mUseNetwork;
|
||||
private final ValueHelper<V> mHelper;
|
||||
private final ValueCache<V> mCache;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
private final Executor mDiskExecutor;
|
||||
private final Executor mNetworkExecutor;
|
||||
private final Conaco<V> mConaco;
|
||||
|
||||
private DiskLoadTask mDiskLoadTask;
|
||||
private NetworkLoadTask mNetworkLoadTask;
|
||||
private Call mCall;
|
||||
private boolean mStart;
|
||||
private volatile boolean mStop;
|
||||
|
||||
private ConacoTask(Builder<V> builder) {
|
||||
mId = builder.mId;
|
||||
mUnikeryWeakReference = new WeakReference<>(builder.mUnikery);
|
||||
mKey = builder.mKey;
|
||||
mUrl = builder.mUrl;
|
||||
mUseMemoryCache = builder.mUseMemoryCache;
|
||||
mUseDiskCache = builder.mUseDiskCache;
|
||||
mUseNetwork = builder.mUseNetwork;
|
||||
mHelper = builder.mHelper;
|
||||
mCache = builder.mCache;
|
||||
mOkHttpClient = builder.mOkHttpClient;
|
||||
mDiskExecutor = builder.mDiskExecutor;
|
||||
mNetworkExecutor = builder.mNetworkExecutor;
|
||||
mConaco = builder.mConaco;
|
||||
}
|
||||
|
||||
int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
String getKey() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
boolean useMemoryCache() {
|
||||
return mUseMemoryCache;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Unikery<V> getUnikery() {
|
||||
return mUnikeryWeakReference.get();
|
||||
}
|
||||
|
||||
void clearUnikery() {
|
||||
mUnikeryWeakReference.clear();
|
||||
}
|
||||
|
||||
private void onFinish() {
|
||||
if (!mStop) {
|
||||
mConaco.finishConacoTask(this);
|
||||
}/* else {
|
||||
// It is done by Conaco
|
||||
}*/
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void start() {
|
||||
if (mStop || mStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
mStart = true;
|
||||
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
if (mUseDiskCache) {
|
||||
mDiskLoadTask = new DiskLoadTask();
|
||||
mDiskLoadTask.executeOnExecutor(mDiskExecutor);
|
||||
return;
|
||||
} else if (mUseNetwork) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onRequest();
|
||||
mNetworkLoadTask = new NetworkLoadTask();
|
||||
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
||||
return;
|
||||
} else {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onMiss(Conaco.SOURCE_NETWORK);
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
|
||||
onFinish();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void stop() {
|
||||
if (mStop) {
|
||||
return;
|
||||
}
|
||||
|
||||
mStop = true;
|
||||
|
||||
// Stop jobs
|
||||
if (mDiskLoadTask != null) { // Getting from disk
|
||||
mDiskLoadTask.cancel(false);
|
||||
} else if (mNetworkLoadTask != null) { // Getting from network
|
||||
mNetworkLoadTask.cancel(false);
|
||||
if (mCall != null) {
|
||||
mCall = null;
|
||||
}
|
||||
}
|
||||
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null) {
|
||||
unikery.onCancel();
|
||||
}
|
||||
|
||||
// Conaco handle the clean up
|
||||
}
|
||||
|
||||
private boolean isNotNecessary(AsyncTask asyncTask) {
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
return mStop || asyncTask.isCancelled() || unikery == null || unikery.getTaskId() != mId;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"})
|
||||
public static class Builder<T> {
|
||||
|
||||
private int mId;
|
||||
private Unikery<T> mUnikery;
|
||||
private String mKey;
|
||||
private String mUrl;
|
||||
private boolean mUseMemoryCache = true;
|
||||
private boolean mUseDiskCache = true;
|
||||
private boolean mUseNetwork = true;
|
||||
private ValueHelper<T> mHelper;
|
||||
private ValueCache<T> mCache;
|
||||
private OkHttpClient mOkHttpClient;
|
||||
private Executor mDiskExecutor;
|
||||
private Executor mNetworkExecutor;
|
||||
private Conaco<T> mConaco;
|
||||
|
||||
public Builder<T> setId(int id) {
|
||||
mId = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Unikery<T> getUnikery() {
|
||||
return mUnikery;
|
||||
}
|
||||
|
||||
public Builder<T> setUnikery(Unikery<T> unikery) {
|
||||
mUnikery = unikery;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return mKey;
|
||||
}
|
||||
|
||||
public Builder<T> setKey(String key) {
|
||||
mKey = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return mUrl;
|
||||
}
|
||||
|
||||
public Builder<T> setUrl(String url) {
|
||||
mUrl = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseMemoryCache() {
|
||||
return mUseMemoryCache;
|
||||
}
|
||||
|
||||
public Builder<T> setUseMemoryCache(boolean useMemoryCache) {
|
||||
mUseMemoryCache = useMemoryCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseDiskCache() {
|
||||
return mUseDiskCache;
|
||||
}
|
||||
|
||||
public Builder<T> setUseDiskCache(boolean useDiskCache) {
|
||||
mUseDiskCache = useDiskCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
boolean isUseNetwork() {
|
||||
return mUseNetwork;
|
||||
}
|
||||
|
||||
public Builder<T> setUseNetwork(boolean useNetwork) {
|
||||
mUseNetwork = useNetwork;
|
||||
return this;
|
||||
}
|
||||
|
||||
ValueHelper<T> getHelper() {
|
||||
return mHelper;
|
||||
}
|
||||
|
||||
Builder<T> setHelper(ValueHelper<T> helper) {
|
||||
mHelper = helper;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setCache(ValueCache<T> cache) {
|
||||
mCache = cache;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setOkHttpClient(OkHttpClient okHttpClient) {
|
||||
mOkHttpClient = okHttpClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setDiskExecutor(Executor diskExecutor) {
|
||||
mDiskExecutor = diskExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setNetworkExecutor(Executor networkExecutor) {
|
||||
mNetworkExecutor = networkExecutor;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder<T> setConaco(Conaco<T> conaco) {
|
||||
mConaco = conaco;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void isValid() {
|
||||
if (mUnikery == null) {
|
||||
throw new IllegalStateException("Must set unikery");
|
||||
}
|
||||
if (mKey == null && mUrl == null) {
|
||||
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
|
||||
}
|
||||
}
|
||||
|
||||
public ConacoTask<T> build() {
|
||||
return new ConacoTask<>(this);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class DiskLoadTask extends AsyncTask<Void, Void, V> {
|
||||
|
||||
@Override
|
||||
protected V doInBackground(Void... params) {
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
} else {
|
||||
V value = null;
|
||||
|
||||
// Then check disk cache
|
||||
if (mKey != null) {
|
||||
if (value == null && mUseDiskCache) {
|
||||
value = mCache.getFromDisk(mKey);
|
||||
}
|
||||
|
||||
if (value != null && mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(V value) {
|
||||
mDiskLoadTask = null;
|
||||
|
||||
if (isCancelled() || mStop) {
|
||||
onCancelled(value);
|
||||
} else {
|
||||
Unikery<V> unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
boolean getValue = false;
|
||||
if ((value == null || !(getValue = unikery.onGetValue(value, Conaco.SOURCE_DISK))) && mUseNetwork) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onRequest();
|
||||
mNetworkLoadTask = new NetworkLoadTask();
|
||||
mNetworkLoadTask.executeOnExecutor(mNetworkExecutor);
|
||||
return;
|
||||
} else if (!getValue) {
|
||||
unikery.onMiss(Conaco.SOURCE_DISK);
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(V holder) {
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private class NetworkLoadTask extends AsyncTask<Void, Long, V> implements ProgressNotifier {
|
||||
|
||||
@Override
|
||||
public void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
||||
if (!isNotNecessary(this)) {
|
||||
publishProgress(singleReceivedSize, receivedSize, totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean putToDiskCache(InputStream is, long length) {
|
||||
SimpleDiskCache diskCache = mCache.getDiskCache();
|
||||
if (diskCache == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutputStreamPipe pipe = diskCache.getOutputStreamPipe(mKey);
|
||||
try {
|
||||
pipe.obtain();
|
||||
OutputStream os = pipe.open();
|
||||
|
||||
final byte[] buffer = new byte[1024 * 4];
|
||||
long receivedSize = 0;
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = is.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, bytesRead);
|
||||
receivedSize += bytesRead;
|
||||
notifyProgress(bytesRead, receivedSize, length);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected V doInBackground(Void... params) {
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
V value;
|
||||
InputStream is = null;
|
||||
try {
|
||||
Log.d("TAG", "Conaco " + mUrl);
|
||||
|
||||
// Load it from internet
|
||||
Request request = new ChromeRequestBuilder(mUrl).build();
|
||||
mCall = mOkHttpClient.newCall(request);
|
||||
|
||||
Response response = mCall.execute();
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
is = body.byteStream();
|
||||
|
||||
if (isNotNecessary(this)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mKey != null) {
|
||||
if (putToDiskCache(is, body.contentLength())) {
|
||||
// Get object from disk cache
|
||||
value = mCache.getFromDisk(mKey);
|
||||
if (value == null) {
|
||||
// Maybe bad download, remove it from disk cache
|
||||
mCache.removeFromDisk(mKey);
|
||||
} else if (mUseMemoryCache && mHelper.useMemoryCache(mKey, value)) {
|
||||
// Put it to memory
|
||||
mCache.putToMemory(mKey, value);
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
// Maybe bad download, remove it from disk cache
|
||||
mCache.removeFromDisk(mKey);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
mCall = null;
|
||||
try {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(V holder) {
|
||||
mNetworkLoadTask = null;
|
||||
|
||||
if (isCancelled() || mStop) {
|
||||
onCancelled(holder);
|
||||
} else {
|
||||
Unikery<V> unikery = mUnikeryWeakReference.get();
|
||||
if (unikery != null && unikery.getTaskId() == mId) {
|
||||
if (holder == null || !unikery.onGetValue(holder, Conaco.SOURCE_NETWORK)) {
|
||||
unikery.onFailure();
|
||||
}
|
||||
}
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled(V value) {
|
||||
onFinish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Long... values) {
|
||||
Unikery unikery = mUnikeryWeakReference.get();
|
||||
if (!mStop && !isCancelled() && unikery != null && unikery.getTaskId() == mId) {
|
||||
unikery.onProgress(values[0], values[1], values[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
public interface ProgressNotifier {
|
||||
|
||||
void notifyProgress(long singleReceivedSize, long receivedSize, long totalSize);
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import com.hippo.yorozuya.collect.Multimap;
|
||||
import com.hippo.yorozuya.collect.SparseILArray;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class Register<V> {
|
||||
|
||||
private final SparseILArray<ConacoTask<V>> mIdMap = new SparseILArray<>();
|
||||
private final Multimap<String, ConacoTask<V>> mKeyMap = Multimap.create();
|
||||
|
||||
/**
|
||||
* @return true for the key is already registered
|
||||
*/
|
||||
public synchronized boolean register(int id, ConacoTask<V> task) {
|
||||
boolean repeatedKey = false;
|
||||
String taskKey = task.getKey();
|
||||
|
||||
// For no-key task, there no need to check
|
||||
if (taskKey != null) {
|
||||
repeatedKey = mKeyMap.countElements(taskKey) != 0;
|
||||
}
|
||||
|
||||
// Append task
|
||||
mKeyMap.putElement(taskKey, task);
|
||||
mIdMap.append(id, task);
|
||||
|
||||
return repeatedKey;
|
||||
}
|
||||
|
||||
public synchronized ConacoTask<V> unregister(int id) {
|
||||
ConacoTask<V> task = mIdMap.remove(id);
|
||||
if (task != null) {
|
||||
mKeyMap.removeElement(task.getKey(), task);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
public synchronized boolean contain(int id) {
|
||||
return mIdMap.indexOfKey(id) >= 0;
|
||||
}
|
||||
|
||||
public synchronized ConacoTask<V> getByKey(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<ConacoTask<V>> list = mKeyMap.get(key);
|
||||
if (list != null && list.size() > 0) {
|
||||
return list.get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface Unikery<V> {
|
||||
|
||||
int INVALID_ID = -1;
|
||||
|
||||
/**
|
||||
* Get the conaco task id
|
||||
*
|
||||
* @return the conaco task id
|
||||
*/
|
||||
int getTaskId();
|
||||
|
||||
/**
|
||||
* Store the conaco task id
|
||||
*
|
||||
* @param id the conaco task id
|
||||
*/
|
||||
void setTaskId(int id);
|
||||
|
||||
/**
|
||||
* On miss in the source
|
||||
*/
|
||||
void onMiss(@Conaco.Source int source);
|
||||
|
||||
/**
|
||||
* On start http request
|
||||
*/
|
||||
void onRequest();
|
||||
|
||||
/**
|
||||
* On http request progress
|
||||
*/
|
||||
void onProgress(long singleReceivedSize, long receivedSize, long totalSize);
|
||||
|
||||
/**
|
||||
* On start to wait because repeated key
|
||||
*/
|
||||
void onWait();
|
||||
|
||||
/**
|
||||
* On get the value
|
||||
*
|
||||
* @return {@code true} for the {@code Unikery} accepts the value
|
||||
* and the task ends. {@code false} for the {@code Unikery}
|
||||
* reject the value and the task goes on or failed
|
||||
*/
|
||||
boolean onGetValue(@NonNull V value, @Conaco.Source int source);
|
||||
|
||||
/**
|
||||
* On failed to get value
|
||||
*/
|
||||
void onFailure();
|
||||
|
||||
/**
|
||||
* On user cancel the conaco task
|
||||
*/
|
||||
void onCancel();
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
class ValueCache<V> extends BeerBelly<V> {
|
||||
|
||||
private final ValueHelper<V> mHelper;
|
||||
|
||||
public ValueCache(BeerBelly.BeerBellyParams params, ValueHelper<V> helper) {
|
||||
super(params);
|
||||
mHelper = helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(String key, V value) {
|
||||
return mHelper.sizeOf(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void memoryEntryAdded(V value) {
|
||||
mHelper.onAddToMemoryCache(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void memoryEntryRemoved(boolean evicted, String key, V oldValue, V newValue) {
|
||||
if (oldValue != null) {
|
||||
mHelper.onRemoveFromMemoryCache(key, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected V read(@NonNull InputStreamPipe isPipe) {
|
||||
return mHelper.decode(isPipe);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean write(OutputStream os, V value) {
|
||||
throw new UnsupportedOperationException("Not support write object");
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.conaco;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
public interface ValueHelper<V> {
|
||||
|
||||
/**
|
||||
* Decode object for the {@code InputStreamPipe}
|
||||
*
|
||||
* @param isPipe the {@code InputStreamPipe}
|
||||
* @return the decoded object
|
||||
*/
|
||||
@Nullable
|
||||
V decode(@NonNull InputStreamPipe isPipe);
|
||||
|
||||
/**
|
||||
* Get the size of the object
|
||||
*/
|
||||
int sizeOf(@NonNull String key, @NonNull V value);
|
||||
|
||||
/**
|
||||
* Called when the object added to memory cache
|
||||
*/
|
||||
void onAddToMemoryCache(@NonNull V oldValue);
|
||||
|
||||
/**
|
||||
* Called when the object removed from memory cache
|
||||
*/
|
||||
void onRemoveFromMemoryCache(@NonNull String key, @NonNull V oldValue);
|
||||
|
||||
/**
|
||||
* Use cache memory or not. Sometimes large object
|
||||
* should not be stored in memory cache.
|
||||
*/
|
||||
boolean useMemoryCache(@NonNull String key, V holder);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -29,17 +30,25 @@ import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
public class DrawableWrapper extends Drawable implements Drawable.Callback {
|
||||
private Drawable mDrawable;
|
||||
private Rect mBounds;
|
||||
|
||||
public DrawableWrapper(Drawable drawable) {
|
||||
this.setWrappedDrawable(drawable);
|
||||
}
|
||||
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
this.mDrawable.draw(canvas);
|
||||
if (mDrawable instanceof BitmapDrawable bitmapDrawable) {
|
||||
canvas.drawBitmap(bitmapDrawable.getBitmap(), null, mBounds, bitmapDrawable.getPaint());
|
||||
} else {
|
||||
synchronized (mDrawable) {
|
||||
mDrawable.setBounds(mBounds);
|
||||
mDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
this.mDrawable.setBounds(bounds);
|
||||
mBounds = bounds;
|
||||
}
|
||||
|
||||
public int getChangingConfigurations() {
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
/**
|
||||
* A drawable that can draw a "Drawer hamburger" menu or an Arrow and animate between them.
|
||||
*/
|
||||
public class DrawerArrowDrawable extends Drawable {
|
||||
|
||||
// The angle in degrees that the arrow head is inclined at.
|
||||
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
|
||||
private final Paint mPaint = new Paint();
|
||||
private final float mBarThickness;
|
||||
// The length of top and bottom bars when they merge into an arrow
|
||||
private final float mTopBottomArrowSize;
|
||||
// The length of middle bar
|
||||
private final float mBarSize;
|
||||
// The length of the middle bar when arrow is shaped
|
||||
private final float mMiddleArrowSize;
|
||||
// The space between bars when they are parallel
|
||||
private final float mBarGap;
|
||||
// Whether bars should spin or not during progress
|
||||
private final boolean mSpin;
|
||||
// Use Path instead of canvas operations so that if color has transparency, overlapping sections
|
||||
// wont look different
|
||||
private final Path mPath = new Path();
|
||||
// The reported intrinsic size of the drawable.
|
||||
private final int mSize;
|
||||
// the amount that overlaps w/ bar size when rotation is max
|
||||
private final float mMaxCutForBarSize;
|
||||
// Whether we should mirror animation when animation is reversed.
|
||||
private boolean mVerticalMirror = false;
|
||||
// The interpolated version of the original progress
|
||||
private float mProgress;
|
||||
|
||||
/**
|
||||
* @param context used to get the configuration for the drawable from
|
||||
*/
|
||||
public DrawerArrowDrawable(Context context, int color) {
|
||||
Resources resources = context.getResources();
|
||||
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setColor(color);
|
||||
mSize = resources.getDimensionPixelSize(R.dimen.dad_drawable_size);
|
||||
// round this because having this floating may cause bad measurements
|
||||
mBarSize = Math.round(resources.getDimension(R.dimen.dad_bar_size));
|
||||
// round this because having this floating may cause bad measurements
|
||||
mTopBottomArrowSize = Math.round(resources.getDimension(R.dimen.dad_top_bottom_bar_arrow_size));
|
||||
mBarThickness = resources.getDimension(R.dimen.dad_thickness);
|
||||
// round this because having this floating may cause bad measurements
|
||||
mBarGap = Math.round(resources.getDimension(R.dimen.dad_gap_between_bars));
|
||||
mSpin = resources.getBoolean(R.bool.dad_spin_bars);
|
||||
mMiddleArrowSize = resources.getDimension(R.dimen.dad_middle_bar_arrow_size);
|
||||
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeJoin(Paint.Join.MITER);
|
||||
mPaint.setStrokeCap(Paint.Cap.BUTT);
|
||||
mPaint.setStrokeWidth(mBarThickness);
|
||||
|
||||
mMaxCutForBarSize = (float) (mBarThickness / 2 * Math.cos(ARROW_HEAD_ANGLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, canvas is flipped when progress reached to end and going back to start.
|
||||
*/
|
||||
protected void setVerticalMirror(boolean verticalMirror) {
|
||||
mVerticalMirror = verticalMirror;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
Rect bounds = getBounds();
|
||||
// Interpolated widths of arrow bars
|
||||
final float arrowSize = MathUtils.lerp(mBarSize, mTopBottomArrowSize, mProgress);
|
||||
final float middleBarSize = MathUtils.lerp(mBarSize, mMiddleArrowSize, mProgress);
|
||||
// Interpolated size of middle bar
|
||||
final float middleBarCut = Math.round(MathUtils.lerp(0, mMaxCutForBarSize, mProgress));
|
||||
// The rotation of the top and bottom bars (that make the arrow head)
|
||||
final float rotation = MathUtils.lerp(0, ARROW_HEAD_ANGLE, mProgress);
|
||||
|
||||
// The whole canvas rotates as the transition happens
|
||||
final float canvasRotate = MathUtils.lerp(-180, 0, mProgress);
|
||||
final float arrowWidth = Math.round(arrowSize * Math.cos(rotation));
|
||||
final float arrowHeight = Math.round(arrowSize * Math.sin(rotation));
|
||||
|
||||
mPath.rewind();
|
||||
final float topBottomBarOffset = MathUtils.lerp(mBarGap + mBarThickness, -mMaxCutForBarSize,
|
||||
mProgress);
|
||||
|
||||
final float arrowEdge = -middleBarSize / 2;
|
||||
// draw middle bar
|
||||
mPath.moveTo(arrowEdge + middleBarCut, 0);
|
||||
mPath.rLineTo(middleBarSize - middleBarCut * 2, 0);
|
||||
|
||||
// bottom bar
|
||||
mPath.moveTo(arrowEdge, topBottomBarOffset);
|
||||
mPath.rLineTo(arrowWidth, arrowHeight);
|
||||
|
||||
// top bar
|
||||
mPath.moveTo(arrowEdge, -topBottomBarOffset);
|
||||
mPath.rLineTo(arrowWidth, -arrowHeight);
|
||||
|
||||
mPath.close();
|
||||
|
||||
canvas.save();
|
||||
// Rotate the whole canvas if spinning, if not, rotate it 180 to get
|
||||
// the arrow pointing the other way for RTL.
|
||||
canvas.translate(bounds.centerX(), bounds.centerY());
|
||||
if (mSpin) {
|
||||
canvas.rotate(canvasRotate * (mVerticalMirror ? -1 : 1));
|
||||
}
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public void setColor(@ColorInt int color) {
|
||||
mPaint.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int i) {
|
||||
mPaint.setAlpha(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setProgress(float progress) {
|
||||
if (progress == 1f) {
|
||||
setVerticalMirror(true);
|
||||
} else if (progress == 0f) {
|
||||
setVerticalMirror(false);
|
||||
}
|
||||
mProgress = progress;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setMenu(long duration) {
|
||||
setShape(false, duration);
|
||||
}
|
||||
|
||||
public void setArrow(long duration) {
|
||||
setShape(true, duration);
|
||||
}
|
||||
|
||||
public void setShape(boolean arrow, long duration) {
|
||||
if (!((!arrow && mProgress == 0f) || (arrow && mProgress == 1f))) {
|
||||
float endProgress = arrow ? 1f : 0f;
|
||||
if (duration <= 0) {
|
||||
setProgress(endProgress);
|
||||
} else {
|
||||
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", endProgress);
|
||||
oa.setDuration(duration);
|
||||
oa.setAutoCancel(true);
|
||||
oa.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,31 +16,25 @@
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.drawable.AnimatedImageDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.conaco.ConacoTask;
|
||||
import com.hippo.conaco.Unikery;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.image.ImageDrawable;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
|
||||
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
|
||||
ObservedTextView.OnWindowAttachListener {
|
||||
import coil.Coil;
|
||||
import coil.request.Disposable;
|
||||
import coil.request.ImageRequest;
|
||||
|
||||
public class UnikeryDrawable extends WrapDrawable implements ObservedTextView.OnWindowAttachListener {
|
||||
|
||||
private static final String TAG = UnikeryDrawable.class.getSimpleName();
|
||||
private final ObservedTextView mTextView;
|
||||
private final Conaco<ImageBitmap> mConaco;
|
||||
private int mTaskId = Unikery.INVALID_ID;
|
||||
private String mUrl;
|
||||
private Disposable task;
|
||||
|
||||
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
||||
public UnikeryDrawable(ObservedTextView textView) {
|
||||
mTextView = textView;
|
||||
mTextView.setOnWindowAttachListener(this);
|
||||
mConaco = conaco;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,22 +44,28 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
mConaco.cancel(this);
|
||||
if (task != null && !task.isDisposed())
|
||||
task.dispose();
|
||||
clearDrawable();
|
||||
}
|
||||
|
||||
public void load(String url) {
|
||||
if (url != null) {
|
||||
mUrl = url;
|
||||
mConaco.load(new ConacoTask.Builder<ImageBitmap>().setUnikery(this).setUrl(url).setKey(url));
|
||||
var imageLoader = Coil.imageLoader(mTextView.getContext());
|
||||
var request = new ImageRequest.Builder(mTextView.getContext()).data(url).memoryCacheKey(url).diskCacheKey(url).target(
|
||||
drawable -> null,
|
||||
drawable -> null,
|
||||
drawable -> {
|
||||
onGetValue(drawable);
|
||||
return null;
|
||||
}
|
||||
).build();
|
||||
task = imageLoader.enqueue(request);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearDrawable() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable instanceof ImageDrawable) {
|
||||
((ImageDrawable) drawable).recycle();
|
||||
}
|
||||
setDrawable(null);
|
||||
}
|
||||
|
||||
@@ -89,63 +89,16 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTaskId() {
|
||||
return mTaskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskId(int id) {
|
||||
mTaskId = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateSelf() {
|
||||
CharSequence cs = mTextView.getText();
|
||||
mTextView.setText(cs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMiss(int source) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWait() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
|
||||
ImageDrawable drawable;
|
||||
try {
|
||||
drawable = new ImageDrawable(value);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "The ImageBitmap is recycled", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onGetValue(Drawable drawable) {
|
||||
clearDrawable();
|
||||
|
||||
setDrawable(drawable);
|
||||
drawable.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
// Empty
|
||||
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable)
|
||||
animatedImageDrawable.start();
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,7 @@ public final class LayoutManagerUtils {
|
||||
RecyclerView.LayoutManager layoutManager, Context context, int position,
|
||||
int millisecondsPerInch) {
|
||||
SimpleSmoothScroller smoothScroller;
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
||||
if (layoutManager instanceof final LinearLayoutManager linearLayoutManager) {
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
@@ -73,8 +72,7 @@ public final class LayoutManagerUtils {
|
||||
}
|
||||
};
|
||||
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
|
||||
} else if (layoutManager instanceof final StaggeredGridLayoutManager staggeredGridLayoutManager) {
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
|
||||
@@ -1,598 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Debug;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.collection.LruCache;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.ProcessLifecycleOwner;
|
||||
|
||||
import com.hippo.Native;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.conaco.Conaco;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
import com.hippo.ehviewer.client.EhCookieStore;
|
||||
import com.hippo.ehviewer.client.EhDns;
|
||||
import com.hippo.ehviewer.client.EhEngine;
|
||||
import com.hippo.ehviewer.client.EhRequestBuilder;
|
||||
import com.hippo.ehviewer.client.EhSSLSocketFactory;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhX509TrustManager;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.client.parser.EventPaneParser;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.EhActivity;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.network.StatusCodeException;
|
||||
import com.hippo.scene.SceneApplication;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.BitmapUtils;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.OSUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class EhApplication extends SceneApplication {
|
||||
|
||||
public static final boolean BETA = false;
|
||||
private static final String TAG = EhApplication.class.getSimpleName();
|
||||
private static final String KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id";
|
||||
private static final boolean DEBUG_CONACO = false;
|
||||
private static final boolean DEBUG_PRINT_NATIVE_MEMORY = false;
|
||||
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
|
||||
private static final long DEBUG_PRINT_INTERVAL = 3000L;
|
||||
|
||||
public static boolean locked = true;
|
||||
public static long locked_last_leave_time = 0;
|
||||
|
||||
private static EhApplication instance;
|
||||
|
||||
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
|
||||
private final HashMap<Integer, Object> mGlobalStuffMap = new HashMap<>();
|
||||
private final List<Activity> mActivityList = new ArrayList<>();
|
||||
private EhCookieStore mEhCookieStore;
|
||||
private EhClient mEhClient;
|
||||
private EhProxySelector mEhProxySelector;
|
||||
private OkHttpClient mOkHttpClient;
|
||||
private Cache mOkHttpCache;
|
||||
private ImageBitmapHelper mImageBitmapHelper;
|
||||
private Conaco<ImageBitmap> mConaco;
|
||||
private LruCache<Long, GalleryDetail> mGalleryDetailCache;
|
||||
private SimpleDiskCache mSpiderInfoCache;
|
||||
private DownloadManager mDownloadManager;
|
||||
private Hosts mHosts;
|
||||
private FavouriteStatusRouter mFavouriteStatusRouter;
|
||||
private boolean initialized = false;
|
||||
|
||||
public static EhApplication getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static EhCookieStore getEhCookieStore(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhCookieStore == null) {
|
||||
application.mEhCookieStore = new EhCookieStore(context);
|
||||
}
|
||||
return application.mEhCookieStore;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static EhClient getEhClient(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhClient == null) {
|
||||
application.mEhClient = new EhClient(application);
|
||||
}
|
||||
return application.mEhClient;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static EhProxySelector getEhProxySelector(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mEhProxySelector == null) {
|
||||
application.mEhProxySelector = new EhProxySelector();
|
||||
}
|
||||
return application.mEhProxySelector;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static OkHttpClient getOkHttpClient(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mOkHttpClient == null) {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
.callTimeout(10, TimeUnit.SECONDS)
|
||||
.cookieJar(getEhCookieStore(application))
|
||||
.cache(getOkHttpCache(application))
|
||||
.dns(new EhDns(application))
|
||||
.addNetworkInterceptor(chain -> {
|
||||
try {
|
||||
return chain.proceed(chain.request());
|
||||
} catch (NullPointerException e) {
|
||||
// crash on meizu devices due to old Android version
|
||||
// https://github.com/square/okhttp/issues/3301#issuecomment-348415095
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
})
|
||||
.proxySelector(getEhProxySelector(application));
|
||||
try {
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||
}
|
||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), trustManager);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), new EhX509TrustManager());
|
||||
}
|
||||
application.mOkHttpClient = builder.build();
|
||||
}
|
||||
return application.mOkHttpClient;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Cache getOkHttpCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mOkHttpCache == null) {
|
||||
application.mOkHttpCache = new Cache(new File(application.getCacheDir(), "http_cache"), 50L * 1024L * 1024L);
|
||||
}
|
||||
return application.mOkHttpCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ImageBitmapHelper getImageBitmapHelper(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mImageBitmapHelper == null) {
|
||||
application.mImageBitmapHelper = new ImageBitmapHelper();
|
||||
}
|
||||
return application.mImageBitmapHelper;
|
||||
}
|
||||
|
||||
private static int getMemoryCacheMaxSize() {
|
||||
return Math.min(20 * 1024 * 1024, (int) OSUtils.getAppMaxMemory());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Conaco<ImageBitmap> getConaco(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mConaco == null) {
|
||||
Conaco.Builder<ImageBitmap> builder = new Conaco.Builder<>();
|
||||
builder.hasMemoryCache = true;
|
||||
builder.memoryCacheMaxSize = getMemoryCacheMaxSize();
|
||||
builder.hasDiskCache = true;
|
||||
builder.diskCacheDir = new File(context.getCacheDir(), "thumb");
|
||||
builder.diskCacheMaxSize = 320 * 1024 * 1024; // 320MB
|
||||
builder.okHttpClient = getOkHttpClient(context);
|
||||
builder.objectHelper = getImageBitmapHelper(context);
|
||||
builder.debug = DEBUG_CONACO;
|
||||
application.mConaco = builder.build();
|
||||
}
|
||||
return application.mConaco;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static LruCache<Long, GalleryDetail> getGalleryDetailCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mGalleryDetailCache == null) {
|
||||
// Max size 25, 3 min timeout
|
||||
application.mGalleryDetailCache = new LruCache<>(25);
|
||||
getFavouriteStatusRouter().addListener((gid, slot) -> {
|
||||
GalleryDetail gd = application.mGalleryDetailCache.get(gid);
|
||||
if (gd != null) {
|
||||
gd.favoriteSlot = slot;
|
||||
}
|
||||
});
|
||||
}
|
||||
return application.mGalleryDetailCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SimpleDiskCache getSpiderInfoCache(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (null == application.mSpiderInfoCache) {
|
||||
application.mSpiderInfoCache = new SimpleDiskCache(
|
||||
new File(context.getCacheDir(), "spider_info"), 20 * 1024 * 1024); // 20M
|
||||
}
|
||||
return application.mSpiderInfoCache;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static DownloadManager getDownloadManager() {
|
||||
return getDownloadManager(instance);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static DownloadManager getDownloadManager(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mDownloadManager == null) {
|
||||
application.mDownloadManager = new DownloadManager(application);
|
||||
}
|
||||
return application.mDownloadManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Hosts getHosts(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mHosts == null) {
|
||||
application.mHosts = new Hosts(application, "hosts.db");
|
||||
}
|
||||
return application.mHosts;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FavouriteStatusRouter getFavouriteStatusRouter() {
|
||||
return getFavouriteStatusRouter(getInstance());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static FavouriteStatusRouter getFavouriteStatusRouter(@NonNull Context context) {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mFavouriteStatusRouter == null) {
|
||||
application.mFavouriteStatusRouter = new FavouriteStatusRouter();
|
||||
}
|
||||
return application.mFavouriteStatusRouter;
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@Override
|
||||
public void onCreate() {
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(new EhlifecycleObserver());
|
||||
instance = this;
|
||||
|
||||
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
try {
|
||||
// Always save crash file if onCreate() is not done
|
||||
if (!initialized || Settings.getSaveCrashLog()) {
|
||||
Crash.saveCrashLog(instance, e);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
if (handler != null) {
|
||||
handler.uncaughtException(t, e);
|
||||
}
|
||||
});
|
||||
|
||||
super.onCreate();
|
||||
|
||||
Native.initialize();
|
||||
GetText.initialize(this);
|
||||
StatusCodeException.initialize(this);
|
||||
Settings.initialize(this);
|
||||
ReadableTime.initialize(this);
|
||||
AppConfig.initialize(this);
|
||||
SpiderDen.initialize(this);
|
||||
EhDB.initialize(this);
|
||||
EhEngine.initialize();
|
||||
BitmapUtils.initialize(this);
|
||||
|
||||
// Locales can be managed by system automatically above Snow Cone v2
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()));
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme());
|
||||
|
||||
// Do io tasks in new thread
|
||||
//noinspection deprecation
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
// Check no media file
|
||||
try {
|
||||
UniFile downloadLocation = Settings.getDownloadLocation();
|
||||
if (Settings.getMediaScan()) {
|
||||
CommonOperations.removeNoMediaFile(downloadLocation);
|
||||
} else {
|
||||
CommonOperations.ensureNoMediaFile(downloadLocation);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
|
||||
// Clear temp files
|
||||
try {
|
||||
clearTempDir();
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance());
|
||||
|
||||
// Check app update
|
||||
update();
|
||||
|
||||
// Update version code
|
||||
try {
|
||||
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
Settings.putVersionCode(pi.versionCode);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0));
|
||||
|
||||
if (DEBUG_PRINT_NATIVE_MEMORY || DEBUG_PRINT_IMAGE_COUNT) {
|
||||
debugPrint();
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
theDawnOfNewDay();
|
||||
}
|
||||
|
||||
private void theDawnOfNewDay() {
|
||||
if (!Settings.getRequestNews()) {
|
||||
return;
|
||||
}
|
||||
EhCookieStore store = getEhCookieStore(this);
|
||||
HttpUrl eh = HttpUrl.get(EhUrl.HOST_E);
|
||||
|
||||
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(eh, EhCookieStore.KEY_IPD_PASS_HASH)) {
|
||||
IoThreadPoolExecutor.getInstance().execute(() -> {
|
||||
String referer = EhUrl.REFERER_E;
|
||||
Request request = new EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build();
|
||||
Call call = getOkHttpClient(this).newCall(request);
|
||||
try {
|
||||
Response response = call.execute();
|
||||
ResponseBody responseBody = response.body();
|
||||
if (responseBody == null) {
|
||||
return;
|
||||
}
|
||||
String body = responseBody.string();
|
||||
String html = EventPaneParser.parse(body);
|
||||
if (html != null) {
|
||||
showEventPane(html);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void showEventPane(String html) {
|
||||
Activity activity = getTopActivity();
|
||||
if (activity != null) {
|
||||
activity.runOnUiThread(() -> {
|
||||
AlertDialog dialog = new BaseDialogBuilder(activity)
|
||||
.setMessage(Html.fromHtml(html))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
dialog.setOnShowListener(d -> {
|
||||
final View messageView = dialog.findViewById(android.R.id.message);
|
||||
if (messageView instanceof TextView) {
|
||||
((TextView) messageView).setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
});
|
||||
try {
|
||||
dialog.show();
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void clearTempDir() {
|
||||
File dir = AppConfig.getTempDir();
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir);
|
||||
}
|
||||
dir = AppConfig.getExternalTempDir();
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir);
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
int version = Settings.getVersionCode();
|
||||
if (version < 52) {
|
||||
Settings.putGuideGallery(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearMemoryCache() {
|
||||
if (null != mConaco) {
|
||||
mConaco.getBeerBelly().clearMemory();
|
||||
}
|
||||
if (null != mGalleryDetailCache) {
|
||||
mGalleryDetailCache.evictAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrimMemory(int level) {
|
||||
super.onTrimMemory(level);
|
||||
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
||||
clearMemoryCache();
|
||||
}
|
||||
}
|
||||
|
||||
private void debugPrint() {
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (DEBUG_PRINT_NATIVE_MEMORY) {
|
||||
Log.i(TAG, "Native memory: " + FileUtils.humanReadableByteCount(
|
||||
Debug.getNativeHeapAllocatedSize(), false));
|
||||
}
|
||||
if (DEBUG_PRINT_IMAGE_COUNT) {
|
||||
Log.i(TAG, "Image count: " + Image.getImageCount());
|
||||
}
|
||||
SimpleHandler.getInstance().postDelayed(this, DEBUG_PRINT_INTERVAL);
|
||||
}
|
||||
}.run();
|
||||
}
|
||||
|
||||
public int putGlobalStuff(@NonNull Object o) {
|
||||
int id = mIdGenerator.nextId();
|
||||
mGlobalStuffMap.put(id, o);
|
||||
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId());
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean containGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.containsKey(id);
|
||||
}
|
||||
|
||||
public Object getGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.get(id);
|
||||
}
|
||||
|
||||
public Object removeGlobalStuff(int id) {
|
||||
return mGlobalStuffMap.remove(id);
|
||||
}
|
||||
|
||||
public void removeGlobalStuff(Object o) {
|
||||
mGlobalStuffMap.values().removeAll(Collections.singleton(o));
|
||||
}
|
||||
|
||||
public void registerActivity(Activity activity) {
|
||||
mActivityList.add(activity);
|
||||
}
|
||||
|
||||
public void unregisterActivity(Activity activity) {
|
||||
mActivityList.remove(activity);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public EhActivity getTopActivity() {
|
||||
if (!mActivityList.isEmpty()) {
|
||||
return (EhActivity) mActivityList.get(mActivityList.size() - 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public ComponentName startService(Intent service) {
|
||||
try {
|
||||
return super.startService(service);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
|
||||
try {
|
||||
return super.bindService(service, conn, flags);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public void unbindService(ServiceConnection conn) {
|
||||
try {
|
||||
super.unbindService(conn);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid crash on some "energy saving" devices
|
||||
@Override
|
||||
public ComponentName startForegroundService(Intent service) {
|
||||
try {
|
||||
return super.startForegroundService(service);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class EhlifecycleObserver implements DefaultLifecycleObserver {
|
||||
@Override
|
||||
public void onPause(@NonNull LifecycleOwner owner) {
|
||||
if (!locked)
|
||||
locked_last_leave_time = System.currentTimeMillis() / 1000;
|
||||
locked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.ComponentCallbacks2
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.collection.LruCache
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import com.hippo.Native
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.beerbelly.SimpleDiskCache
|
||||
import com.hippo.ehviewer.client.EhClient
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhDns
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhRequestBuilder
|
||||
import com.hippo.ehviewer.client.EhSSLSocketFactory
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhX509TrustManager
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.parser.EventPaneParser
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.spider.SpiderDen
|
||||
import com.hippo.ehviewer.ui.CommonOperations
|
||||
import com.hippo.ehviewer.ui.EhActivity
|
||||
import com.hippo.scene.SceneApplication
|
||||
import com.hippo.util.BitmapUtils
|
||||
import com.hippo.util.ExceptionUtils
|
||||
import com.hippo.util.IoThreadPoolExecutor
|
||||
import com.hippo.util.ReadableTime
|
||||
import com.hippo.yorozuya.FileUtils
|
||||
import com.hippo.yorozuya.IntIdGenerator
|
||||
import okhttp3.Cache
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.File
|
||||
import java.security.KeyStore
|
||||
import java.util.Arrays
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
class EhApplication : SceneApplication() {
|
||||
private val mIdGenerator = IntIdGenerator()
|
||||
private val mGlobalStuffMap = HashMap<Int, Any>()
|
||||
private val mActivityList = ArrayList<Activity>()
|
||||
private var initialized = false
|
||||
val topActivity: EhActivity?
|
||||
get() = if (mActivityList.isNotEmpty()) {
|
||||
mActivityList[mActivityList.size - 1] as EhActivity
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun recreateAllActivity() {
|
||||
mActivityList.forEach { it.recreate() }
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
override fun onCreate() {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(EhlifecycleObserver())
|
||||
application = this
|
||||
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
||||
try {
|
||||
if (Settings.getSaveCrashLog()) {
|
||||
Crash.saveCrashLog(application, e)
|
||||
}
|
||||
} catch (ignored: Throwable) {
|
||||
}
|
||||
handler?.uncaughtException(t, e)
|
||||
}
|
||||
super.onCreate()
|
||||
Native.initialize()
|
||||
GetText.initialize(this)
|
||||
Settings.initialize(this)
|
||||
ReadableTime.initialize(this)
|
||||
AppConfig.initialize(this)
|
||||
SpiderDen.initialize(this)
|
||||
EhDB.initialize(this)
|
||||
EhEngine.initialize()
|
||||
BitmapUtils.initialize(this)
|
||||
Coil.setImageLoader(ImageLoader.Builder(this).okHttpClient(okHttpClient).build())
|
||||
|
||||
// Locales can be managed by system automatically above Snow Cone v2
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()))
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
|
||||
|
||||
IoThreadPoolExecutor.getInstance().execute {
|
||||
try {
|
||||
val downloadLocation = Settings.getDownloadLocation()
|
||||
if (Settings.getMediaScan()) {
|
||||
CommonOperations.removeNoMediaFile(downloadLocation)
|
||||
} else {
|
||||
CommonOperations.ensureNoMediaFile(downloadLocation)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
ExceptionUtils.throwIfFatal(t)
|
||||
}
|
||||
|
||||
// Clear temp files
|
||||
try {
|
||||
clearTempDir()
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
ExceptionUtils.throwIfFatal(t)
|
||||
}
|
||||
}
|
||||
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0))
|
||||
initialized = true
|
||||
theDawnOfNewDay()
|
||||
}
|
||||
|
||||
private fun theDawnOfNewDay() {
|
||||
if (!Settings.getRequestNews()) {
|
||||
return
|
||||
}
|
||||
val store = ehCookieStore
|
||||
val eh = EhUrl.HOST_E.toHttpUrl()
|
||||
|
||||
if (store.contains(eh, EhCookieStore.KEY_IPD_MEMBER_ID) || store.contains(
|
||||
eh,
|
||||
EhCookieStore.KEY_IPD_PASS_HASH
|
||||
)
|
||||
) {
|
||||
IoThreadPoolExecutor.getInstance().execute {
|
||||
val referer = EhUrl.REFERER_E
|
||||
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
|
||||
val call = okHttpClient.newCall(request)
|
||||
try {
|
||||
val response = call.execute()
|
||||
val responseBody = response.body
|
||||
val body = responseBody.string()
|
||||
val html = EventPaneParser.parse(body)
|
||||
if (html != null) {
|
||||
showEventPane(html)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showEventPane(html: String) {
|
||||
if (Settings.getHideHvEvents() && html.contains("You have encountered a monster!")) {
|
||||
return;
|
||||
}
|
||||
val activity = topActivity
|
||||
activity?.runOnUiThread {
|
||||
val dialog = BaseDialogBuilder(activity)
|
||||
.setMessage(Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create()
|
||||
dialog.setOnShowListener {
|
||||
val messageView = dialog.findViewById<View>(android.R.id.message)
|
||||
if (messageView is TextView) {
|
||||
messageView.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
}
|
||||
try {
|
||||
dialog.show()
|
||||
} catch (t: Throwable) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearTempDir() {
|
||||
var dir = AppConfig.getTempDir()
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir)
|
||||
}
|
||||
dir = AppConfig.getExternalTempDir()
|
||||
if (null != dir) {
|
||||
FileUtils.deleteContent(dir)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
|
||||
galleryDetailCache.evictAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun putGlobalStuff(o: Any): Int {
|
||||
val id = mIdGenerator.nextId()
|
||||
mGlobalStuffMap[id] = o
|
||||
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId())
|
||||
return id
|
||||
}
|
||||
|
||||
fun containGlobalStuff(id: Int): Boolean {
|
||||
return mGlobalStuffMap.containsKey(id)
|
||||
}
|
||||
|
||||
fun getGlobalStuff(id: Int): Any? {
|
||||
return mGlobalStuffMap[id]
|
||||
}
|
||||
|
||||
fun removeGlobalStuff(id: Int): Any? {
|
||||
return mGlobalStuffMap.remove(id)
|
||||
}
|
||||
|
||||
fun removeGlobalStuff(o: Any) {
|
||||
mGlobalStuffMap.values.removeAll(setOf(o))
|
||||
}
|
||||
|
||||
fun registerActivity(activity: Activity) {
|
||||
mActivityList.add(activity)
|
||||
}
|
||||
|
||||
fun unregisterActivity(activity: Activity) {
|
||||
mActivityList.remove(activity)
|
||||
}
|
||||
|
||||
internal class EhlifecycleObserver : DefaultLifecycleObserver {
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
if (!locked)
|
||||
locked_last_leave_time = System.currentTimeMillis() / 1000
|
||||
locked = true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id"
|
||||
var locked = true
|
||||
var locked_last_leave_time: Long = 0
|
||||
|
||||
@JvmStatic
|
||||
lateinit var application: EhApplication
|
||||
private set
|
||||
|
||||
@JvmStatic
|
||||
val ehCookieStore by lazy { EhCookieStore(application) }
|
||||
|
||||
@JvmStatic
|
||||
val ehClient by lazy { EhClient(application) }
|
||||
|
||||
@JvmStatic
|
||||
val ehProxySelector by lazy { EhProxySelector() }
|
||||
|
||||
@JvmStatic
|
||||
val okHttpClient by lazy {
|
||||
val builder = OkHttpClient.Builder()
|
||||
.cookieJar(ehCookieStore)
|
||||
.cache(Cache(File(application.cacheDir, "http_cache"), 50L * 1024L * 1024L))
|
||||
.dns(EhDns())
|
||||
.proxySelector(ehProxySelector)
|
||||
|
||||
if (Settings.getDF()) {
|
||||
var trustManager: X509TrustManager
|
||||
try {
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm()
|
||||
)
|
||||
trustManagerFactory.init(null as KeyStore?)
|
||||
val trustManagers = trustManagerFactory.trustManagers
|
||||
check(!(trustManagers.size != 1 || trustManagers[0] !is X509TrustManager)) {
|
||||
"Unexpected default trust managers:" + Arrays.toString(
|
||||
trustManagers
|
||||
)
|
||||
}
|
||||
trustManager = trustManagers[0] as X509TrustManager
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
trustManager = EhX509TrustManager()
|
||||
}
|
||||
|
||||
builder.sslSocketFactory(EhSSLSocketFactory(), trustManager)
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val galleryDetailCache by lazy {
|
||||
LruCache<Long, GalleryDetail>(25).also {
|
||||
favouriteStatusRouter.addListener { gid, slot ->
|
||||
it[gid]?.favoriteSlot = slot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val spiderInfoCache by lazy {
|
||||
SimpleDiskCache(
|
||||
File(application.cacheDir, "spider_info"),
|
||||
20 * 1024 * 1024
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val downloadManager by lazy { DownloadManager(application) }
|
||||
|
||||
@JvmStatic
|
||||
val hosts by lazy { Hosts(application, "hosts.db") }
|
||||
|
||||
@JvmStatic
|
||||
val favouriteStatusRouter by lazy { FavouriteStatusRouter() }
|
||||
}
|
||||
}
|
||||
@@ -398,11 +398,10 @@ public class EhDB {
|
||||
db.filterDao().update(filter);
|
||||
}
|
||||
|
||||
private static <T> boolean copyDao(BasicDao<T> from, BasicDao<T> to) {
|
||||
private static <T> void copyDao(BasicDao<T> from, BasicDao<T> to) {
|
||||
List<T> list = from.list();
|
||||
for (T item : list)
|
||||
to.insert(item);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static synchronized boolean exportDB(Context context, Uri uri) {
|
||||
@@ -414,20 +413,18 @@ public class EhDB {
|
||||
|
||||
try {
|
||||
// Copy data to a export db
|
||||
if (copyDao(db.downloadsDao(), newDb.downloadsDao()))
|
||||
return false;
|
||||
if (copyDao(db.downloadLabelDao(), newDb.downloadLabelDao()))
|
||||
return false;
|
||||
if (copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao()))
|
||||
return false;
|
||||
if (copyDao(db.historyDao(), newDb.historyDao()))
|
||||
return false;
|
||||
if (copyDao(db.quickSearchDao(), newDb.quickSearchDao()))
|
||||
return false;
|
||||
if (copyDao(db.localFavoritesDao(), newDb.localFavoritesDao()))
|
||||
return false;
|
||||
if (copyDao(db.filterDao(), newDb.filterDao()))
|
||||
return false;
|
||||
copyDao(db.downloadsDao(), newDb.downloadsDao());
|
||||
copyDao(db.downloadLabelDao(), newDb.downloadLabelDao());
|
||||
copyDao(db.downloadDirnameDao(), newDb.downloadDirnameDao());
|
||||
copyDao(db.historyDao(), newDb.historyDao());
|
||||
copyDao(db.quickSearchDao(), newDb.quickSearchDao());
|
||||
copyDao(db.localFavoritesDao(), newDb.localFavoritesDao());
|
||||
copyDao(db.filterDao(), newDb.filterDao());
|
||||
|
||||
// Close export db so we can copy it
|
||||
if (newDb.isOpen()) {
|
||||
newDb.close();
|
||||
}
|
||||
|
||||
// Copy export db to data dir
|
||||
File dbFile = context.getDatabasePath(ehExportName);
|
||||
@@ -447,6 +444,7 @@ public class EhDB {
|
||||
IOUtils.closeQuietly(is);
|
||||
IOUtils.closeQuietly(os);
|
||||
}
|
||||
|
||||
// Delete failed file
|
||||
return false;
|
||||
} finally {
|
||||
@@ -460,6 +458,8 @@ public class EhDB {
|
||||
public static synchronized String importDB(Context context, Uri uri) {
|
||||
try {
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||
|
||||
// Copy file to cache dir
|
||||
File file = File.createTempFile("importDatabase", "");
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
byte[] buff = new byte[1024];
|
||||
@@ -474,6 +474,7 @@ public class EhDB {
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
// Check database version
|
||||
SQLiteDatabase oldDB = SQLiteDatabase.openDatabase(
|
||||
file.getPath(), null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
|
||||
int newVersion = CUR_DB_VER;
|
||||
@@ -482,26 +483,29 @@ public class EhDB {
|
||||
upgradeDB(oldDB, oldVersion);
|
||||
oldDB.setVersion(newVersion);
|
||||
} else if (oldVersion > newVersion) {
|
||||
return context.getString(R.string.cant_read_the_file);
|
||||
return context.getString(R.string.db_version_too_high);
|
||||
}
|
||||
|
||||
// Crete temp room database from cache file
|
||||
String tmpDBName = "tmp.db";
|
||||
context.deleteDatabase(tmpDBName);
|
||||
EhDatabase oldRoomDatabase = Room.databaseBuilder(context, EhDatabase.class, tmpDBName)
|
||||
.createFromFile(file).allowMainThreadQueries().build();
|
||||
|
||||
// Download label
|
||||
DownloadManager manager = EhApplication.getDownloadManager(context);
|
||||
DownloadManager manager = EhApplication.getDownloadManager();
|
||||
try {
|
||||
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Downloads
|
||||
try {
|
||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Download dirname
|
||||
try {
|
||||
@@ -509,13 +513,15 @@ public class EhDB {
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// History
|
||||
try {
|
||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// QuickSearch
|
||||
try {
|
||||
@@ -537,7 +543,8 @@ public class EhDB {
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// LocalFavorites
|
||||
try {
|
||||
@@ -545,7 +552,8 @@ public class EhDB {
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Filter
|
||||
try {
|
||||
@@ -556,7 +564,14 @@ public class EhDB {
|
||||
addFilter(filter);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Delete temp database
|
||||
if (oldRoomDatabase.isOpen()) {
|
||||
oldRoomDatabase.close();
|
||||
}
|
||||
context.deleteDatabase(tmpDBName);
|
||||
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.conaco.ValueHelper;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
|
||||
private static final int MAX_CACHE_SIZE = 512 * 512;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
FileInputStream is = (FileInputStream) isPipe.open();
|
||||
return ImageBitmap.decode(is.getFD());
|
||||
} catch (OutOfMemoryError | IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
isPipe.close();
|
||||
isPipe.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeOf(@NonNull String key, @NonNull ImageBitmap value) {
|
||||
return value.getWidth() * value.getHeight() * 4 /* value.getByteCount() TODO Update Image */;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddToMemoryCache(@NonNull ImageBitmap oldValue) {
|
||||
oldValue.obtain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoveFromMemoryCache(@NonNull String key, @NonNull ImageBitmap oldValue) {
|
||||
oldValue.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useMemoryCache(@NonNull String key, ImageBitmap value) {
|
||||
if (value != null) {
|
||||
return value.getWidth() * value.getHeight() <= MAX_CACHE_SIZE
|
||||
/* value.getByteCount() <= MAX_CACHE_BYTE_COUNT TODO Update Image */;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,13 +60,15 @@ public class Settings {
|
||||
public static final String KEY_GALLERY_SITE = "gallery_site";
|
||||
public static final String KEY_LIST_MODE = "list_mode";
|
||||
public static final String KEY_DETAIL_SIZE = "detail_size";
|
||||
public static final String KEY_THUMB_SIZE = "thumb_size";
|
||||
public static final String KEY_THUMB_SIZE = "thumb_size_";
|
||||
public static final String KEY_THUMB_RESOLUTION = "thumb_resolution";
|
||||
public static final String KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
|
||||
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
|
||||
public static final int DEFAULT_DEFAULT_CATEGORIES = EhUtils.ALL_CATEGORY;
|
||||
public static final int DEFAULT_DEFAULT_CATEGORIES = EhConfig.ALL_CATEGORY;
|
||||
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
|
||||
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
|
||||
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
|
||||
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
|
||||
/********************
|
||||
****** Privacy and Security
|
||||
********************/
|
||||
@@ -78,7 +80,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";
|
||||
@@ -100,16 +101,17 @@ public class Settings {
|
||||
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts_2";
|
||||
public static final String KEY_DOMAIN_FRONTING = "domain_fronting";
|
||||
public static final String KEY_APP_LANGUAGE = "app_language";
|
||||
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
|
||||
private static final String KEY_SHOW_COMMENTS = "show_gallery_comments";
|
||||
private static final boolean DEFAULT_SHOW_COMMENTS = true;
|
||||
private static final String KEY_PREVIEW_NUM = "preview_num";
|
||||
private static final int DEFAULT_PREVIEW_NUM = 60;
|
||||
private static final String TAG = Settings.class.getSimpleName();
|
||||
private static final String KEY_VERSION_CODE = "version_code";
|
||||
private static final int DEFAULT_VERSION_CODE = 0;
|
||||
private static final String KEY_DISPLAY_NAME = "display_name";
|
||||
private static final String DEFAULT_DISPLAY_NAME = null;
|
||||
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";
|
||||
@@ -129,10 +131,8 @@ public class Settings {
|
||||
private static final int DEFAULT_LAUNCH_PAGE = 0;
|
||||
private static final int DEFAULT_LIST_MODE = 0;
|
||||
private static final int DEFAULT_DETAIL_SIZE = 0;
|
||||
private static final int DEFAULT_THUMB_SIZE = 1;
|
||||
private static final int DEFAULT_THUMB_SIZE = 120;
|
||||
private static final int DEFAULT_THUMB_RESOLUTION = 0;
|
||||
private static final String KEY_FIX_THUMB_URL = "fix_thumb_url";
|
||||
private static final boolean DEFAULT_FIX_THUMB_URL = false;
|
||||
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
|
||||
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
|
||||
private static final String KEY_SHOW_GALLERY_PAGES = "show_gallery_pages";
|
||||
@@ -248,7 +248,6 @@ public class Settings {
|
||||
****** Update
|
||||
********************/
|
||||
private static final String KEY_BETA_UPDATE_CHANNEL = "beta_update_channel";
|
||||
private static final boolean DEFAULT_BETA_UPDATE_CHANNEL = EhApplication.BETA;
|
||||
private static final String KEY_SKIP_UPDATE_VERSION = "skip_update_version";
|
||||
private static final int DEFAULT_SKIP_UPDATE_VERSION = 0;
|
||||
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = true;
|
||||
@@ -282,11 +281,9 @@ public class Settings {
|
||||
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
|
||||
private static final String KEY_REQUEST_NEWS = "request_news";
|
||||
private static final boolean DEFAULT_REQUEST_NEWS = true;
|
||||
|
||||
|
||||
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
|
||||
|
||||
|
||||
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
private static Context sContext;
|
||||
private static SharedPreferences sSettingsPre;
|
||||
private static EhConfig sEhConfig;
|
||||
@@ -428,14 +425,6 @@ public class Settings {
|
||||
sSettingsPre.edit().putString(key, Integer.toString(value)).apply();
|
||||
}
|
||||
|
||||
public static int getVersionCode() {
|
||||
return getInt(KEY_VERSION_CODE, DEFAULT_VERSION_CODE);
|
||||
}
|
||||
|
||||
public static void putVersionCode(int value) {
|
||||
putInt(KEY_VERSION_CODE, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getDisplayName() {
|
||||
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
|
||||
@@ -544,34 +533,30 @@ public class Settings {
|
||||
}
|
||||
|
||||
public static int getThumbSize() {
|
||||
return getIntFromStr(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE);
|
||||
return dip2px(getInt(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE));
|
||||
}
|
||||
|
||||
@DimenRes
|
||||
public static int getThumbSizeResId() {
|
||||
switch (getThumbSize()) {
|
||||
case 0:
|
||||
return R.dimen.gallery_grid_column_width_large;
|
||||
default:
|
||||
case 1:
|
||||
return R.dimen.gallery_grid_column_width_middle;
|
||||
case 2:
|
||||
return R.dimen.gallery_grid_column_width_small;
|
||||
}
|
||||
public static int dip2px(int dpValue) {
|
||||
final float scale = sContext.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
public static int getThumbResolution() {
|
||||
return getIntFromStr(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION);
|
||||
}
|
||||
|
||||
public static boolean getFixThumbUrl() {
|
||||
return getBoolean(KEY_FIX_THUMB_URL, DEFAULT_FIX_THUMB_URL);
|
||||
public static boolean getShowComments() {
|
||||
return getBoolean(KEY_SHOW_COMMENTS, DEFAULT_SHOW_COMMENTS);
|
||||
}
|
||||
|
||||
public static boolean getRequestNews() {
|
||||
return getBoolean(KEY_REQUEST_NEWS, DEFAULT_REQUEST_NEWS);
|
||||
}
|
||||
|
||||
public static boolean getHideHvEvents() {
|
||||
return getBoolean(KEY_HIDE_HV_EVENTS, DEFAULT_HIDE_HV_EVENTS);
|
||||
}
|
||||
|
||||
public static boolean getShowJpnTitle() {
|
||||
return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE);
|
||||
}
|
||||
@@ -588,6 +573,10 @@ public class Settings {
|
||||
putBoolean(KEY_SHOW_TAG_TRANSLATIONS, value);
|
||||
}
|
||||
|
||||
public static int getPreviewNum() {
|
||||
return getInt(KEY_PREVIEW_NUM, DEFAULT_PREVIEW_NUM);
|
||||
}
|
||||
|
||||
public static int getDefaultCategories() {
|
||||
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
|
||||
}
|
||||
@@ -979,11 +968,11 @@ public class Settings {
|
||||
boolean writeFile = false;
|
||||
String userID = getString(KEY_USER_ID, null);
|
||||
File file = AppConfig.getFileInExternalAppDir(FILENAME_USER_ID);
|
||||
if (null == userID || !isValidUserID(userID)) {
|
||||
if (!isValidUserID(userID)) {
|
||||
writeXml = true;
|
||||
// Get use ID from out sd card file
|
||||
userID = FileUtils.read(file);
|
||||
if (null == userID || !isValidUserID(userID)) {
|
||||
if (!isValidUserID(userID)) {
|
||||
writeFile = true;
|
||||
userID = generateUserID();
|
||||
}
|
||||
@@ -1032,10 +1021,6 @@ public class Settings {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean getBetaUpdateChannel() {
|
||||
return getBoolean(KEY_BETA_UPDATE_CHANNEL, DEFAULT_BETA_UPDATE_CHANNEL);
|
||||
}
|
||||
|
||||
public static void putBetaUpdateChannel(boolean value) {
|
||||
putBoolean(KEY_BETA_UPDATE_CHANNEL, value);
|
||||
}
|
||||
@@ -1067,6 +1052,7 @@ public class Settings {
|
||||
public static void putSecurity(boolean value) {
|
||||
putBoolean(KEY_SECURITY, value);
|
||||
}
|
||||
|
||||
public static int getSecurityDelay() {
|
||||
return getInt(KEY_SECURITY_DELAY, 0);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class EhClient {
|
||||
|
||||
public EhClient(Context context) {
|
||||
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
|
||||
mOkHttpClient = EhApplication.getOkHttpClient(context);
|
||||
mOkHttpClient = EhApplication.getOkHttpClient();
|
||||
}
|
||||
|
||||
public void execute(EhRequest request) {
|
||||
|
||||
@@ -1,95 +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.client;
|
||||
|
||||
/*
|
||||
* Created by Hippo on 2018/3/23.
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.Hosts;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Dns;
|
||||
|
||||
public class EhDns implements Dns {
|
||||
|
||||
private static final Map<String, List<InetAddress>> builtInHosts;
|
||||
|
||||
static {
|
||||
Map<String, List<InetAddress>> map = new HashMap<>();
|
||||
put(map, "e-hentai.org", "104.20.134.21", "104.20.135.21", "172.67.0.127");
|
||||
put(map, "exhentai.org", "178.175.128.252", "178.175.129.252", "178.175.129.254", "178.175.128.254",
|
||||
"178.175.132.20", "178.175.132.22");
|
||||
put(map, "repo.e-hentai.org", "94.100.28.57", "94.100.29.73");
|
||||
put(map, "forums.e-hentai.org", "94.100.18.243");
|
||||
put(map, "ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt0.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt1.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt2.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt3.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "ul.ehgt.org", "94.100.24.82", "94.100.24.72");
|
||||
put(map, "raw.githubusercontent.com", "151.101.0.133", "151.101.64.133", "151.101.128.133", "151.101.192.133");
|
||||
builtInHosts = map;
|
||||
}
|
||||
|
||||
private final Hosts hosts;
|
||||
|
||||
public EhDns(Context context) {
|
||||
hosts = EhApplication.getHosts(context);
|
||||
}
|
||||
|
||||
private static void put(Map<String, List<InetAddress>> map, String host, String... ips) {
|
||||
List<InetAddress> addresses = new ArrayList<>();
|
||||
for (String ip : ips) {
|
||||
addresses.add(Hosts.toInetAddress(host, ip));
|
||||
}
|
||||
map.put(host, addresses);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException {
|
||||
List<InetAddress> inetAddresses = hosts.get(hostname);
|
||||
if (inetAddresses != null) {
|
||||
return inetAddresses;
|
||||
}
|
||||
if (Settings.getBuiltInHosts()) {
|
||||
inetAddresses = builtInHosts.get(hostname);
|
||||
if (inetAddresses != null) {
|
||||
return inetAddresses;
|
||||
}
|
||||
}
|
||||
return SYSTEM.lookup(hostname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.client
|
||||
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.Hosts
|
||||
import com.hippo.ehviewer.Settings
|
||||
import okhttp3.Dns
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
|
||||
class EhDns : Dns {
|
||||
private val hosts = EhApplication.hosts
|
||||
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
|
||||
|
||||
init {
|
||||
/* Pair(ip: String!, blockedByCCP: Boolean!) */
|
||||
val ehgtHosts = arrayOf(
|
||||
Pair("37.48.89.44", false),
|
||||
Pair("81.171.10.48", false),
|
||||
Pair("178.162.139.24", false),
|
||||
Pair("178.162.140.212", false),
|
||||
Pair("2001:1af8:4700:a062:8::47de", false),
|
||||
Pair("2001:1af8:4700:a062:9::47de", true),
|
||||
Pair("2001:1af8:4700:a0c9:4::47de", false),
|
||||
Pair("2001:1af8:4700:a0c9:3::47de", true),
|
||||
)
|
||||
|
||||
put(
|
||||
builtInHosts,
|
||||
"e-hentai.org",
|
||||
Pair("104.20.134.21", true),
|
||||
Pair("104.20.135.21", false),
|
||||
Pair("172.67.0.127", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"exhentai.org",
|
||||
Pair("178.175.128.252", false),
|
||||
Pair("178.175.129.252", false),
|
||||
Pair("178.175.129.254", false),
|
||||
Pair("178.175.128.254", false),
|
||||
Pair("178.175.132.20", false),
|
||||
Pair("178.175.132.22", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"repo.e-hentai.org",
|
||||
Pair("94.100.28.57", true),
|
||||
Pair("94.100.29.73", true),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"forums.e-hentai.org",
|
||||
Pair("94.100.18.243", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt0.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt1.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt2.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt3.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"ul.ehgt.org",
|
||||
Pair("94.100.24.82", true),
|
||||
Pair("94.100.24.72", true),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"raw.githubusercontent.com",
|
||||
Pair("151.101.0.133", false),
|
||||
Pair("151.101.64.133", false),
|
||||
Pair("151.101.128.133", false),
|
||||
Pair("151.101.192.133", false),
|
||||
)
|
||||
}
|
||||
|
||||
private fun put(
|
||||
map: MutableMap<String, List<InetAddress>>,
|
||||
host: String,
|
||||
vararg ips: Pair<String, Boolean>
|
||||
) {
|
||||
map[host] = ips.mapNotNull { pair ->
|
||||
Hosts.toInetAddress(host, pair.first).takeUnless { Settings.getDF() && pair.second }
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
override fun lookup(hostname: String): List<InetAddress> {
|
||||
return hosts[hostname] ?: builtInHosts[hostname].takeIf { Settings.getBuiltInHosts() }
|
||||
?: Dns.SYSTEM.lookup(hostname)
|
||||
}
|
||||
}
|
||||
@@ -245,12 +245,6 @@ public class EhEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.getFixThumbUrl()) {
|
||||
for (GalleryInfo info : list) {
|
||||
info.thumb = EhUrl.getFixedPreviewThumbUrl(info.thumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static GalleryListParser.Result getGalleryList(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||
@@ -367,7 +361,7 @@ public class EhEngine {
|
||||
body = response.body().string();
|
||||
String html = EventPaneParser.parse(body);
|
||||
if (html != null) {
|
||||
EhApplication.getInstance().showEventPane(html);
|
||||
EhApplication.getApplication().showEventPane(html);
|
||||
}
|
||||
return GalleryDetailParser.parse(body);
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -1,79 +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/>.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.Settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class EhSSLSocketFactory extends SSLSocketFactory {
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return ((SSLSocketFactory) getDefault()).getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return ((SSLSocketFactory) getDefault()).getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
SSLSocketFactory defaultFactory = (SSLSocketFactory) getDefault();
|
||||
if (!Settings.getDF()) {
|
||||
return defaultFactory.createSocket(s, host, port, autoClose);
|
||||
}
|
||||
String address = s.getInetAddress().getHostAddress();
|
||||
SSLSocket socket = (SSLSocket) defaultFactory.createSocket(s, address, port, autoClose);
|
||||
SSLSession sslSession = socket.getSession();
|
||||
Log.d("EhSSLSocketFactory", "Host: " + host + " Address: " + address + " Protocol:" + sslSession.getProtocol() + " CipherSuite:" + sslSession.getCipherSuite());
|
||||
socket.setEnabledProtocols(socket.getSupportedProtocols());
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return getDefault().createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
return getDefault().createSocket(host, port, localHost, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return getDefault().createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return getDefault().createSocket(address, port, localAddress, localPort);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import android.util.Log
|
||||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import javax.net.ssl.SSLSocket
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
|
||||
class EhSSLSocketFactory : SSLSocketFactory() {
|
||||
private val sslSocketFactory = getDefault() as SSLSocketFactory
|
||||
override fun getDefaultCipherSuites(): Array<String> {
|
||||
return sslSocketFactory.defaultCipherSuites
|
||||
}
|
||||
|
||||
override fun getSupportedCipherSuites(): Array<String> {
|
||||
return sslSocketFactory.supportedCipherSuites
|
||||
}
|
||||
|
||||
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
|
||||
val address = s.inetAddress.hostAddress
|
||||
val socket = sslSocketFactory.createSocket(s, address, port, autoClose) as SSLSocket
|
||||
val sslSession = socket.session
|
||||
Log.d(
|
||||
"EhSSLSocketFactory",
|
||||
"Host: " + host + " Address: " + address + " Protocol:" + sslSession.protocol + " CipherSuite:" + sslSession.cipherSuite
|
||||
)
|
||||
return socket
|
||||
}
|
||||
|
||||
override fun createSocket(host: String, port: Int): Socket {
|
||||
return sslSocketFactory.createSocket(host, port)
|
||||
}
|
||||
|
||||
override fun createSocket(
|
||||
host: String,
|
||||
port: Int,
|
||||
localHost: InetAddress,
|
||||
localPort: Int
|
||||
): Socket {
|
||||
return sslSocketFactory.createSocket(host, port, localHost, localPort)
|
||||
}
|
||||
|
||||
override fun createSocket(host: InetAddress, port: Int): Socket {
|
||||
return sslSocketFactory.createSocket(host, port)
|
||||
}
|
||||
|
||||
override fun createSocket(
|
||||
address: InetAddress,
|
||||
port: Int,
|
||||
localAddress: InetAddress,
|
||||
localPort: Int
|
||||
): Socket {
|
||||
return sslSocketFactory.createSocket(address, port, localAddress, localPort)
|
||||
}
|
||||
}
|
||||
@@ -260,7 +260,7 @@ public class EhTagDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
OkHttpClient client = EhApplication.getOkHttpClient(context);
|
||||
OkHttpClient client = EhApplication.getOkHttpClient();
|
||||
|
||||
// Save new sha1
|
||||
File tempSha1File = new File(dir, sha1Name + ".tmp");
|
||||
|
||||
@@ -21,11 +21,6 @@ import androidx.annotation.NonNull;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.network.UrlBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class EhUrl {
|
||||
|
||||
public static final int SITE_E = 0;
|
||||
@@ -59,8 +54,6 @@ public class EhUrl {
|
||||
public static final String ORIGIN_EX = REFERER_EX;
|
||||
public static final String REFERER_E = "https://" + DOMAIN_E;
|
||||
public static final String ORIGIN_E = REFERER_E;
|
||||
private static final String URL_PREFIX_THUMB_E = "https://ehgt.org/";
|
||||
private static final String URL_PREFIX_THUMB_EX = "https://exhentai.org/t/";
|
||||
|
||||
public static String getGalleryDetailUrl(long gid, String token) {
|
||||
return getGalleryDetailUrl(gid, token, 0, false);
|
||||
@@ -200,44 +193,4 @@ public class EhUrl {
|
||||
return URL_WATCHED_EX;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getThumbUrlPrefix() {
|
||||
switch (Settings.getGallerySite()) {
|
||||
default:
|
||||
//case SITE_E:
|
||||
return URL_PREFIX_THUMB_E;
|
||||
//case SITE_EX:
|
||||
// return URL_PREFIX_THUMB_EX;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFixedPreviewThumbUrl(String originUrl) {
|
||||
HttpUrl url = HttpUrl.parse(originUrl);
|
||||
if (url == null) return originUrl;
|
||||
List<String> pathSegments = url.pathSegments();
|
||||
if (pathSegments.size() < 3) return originUrl;
|
||||
|
||||
ListIterator<String> iterator = pathSegments.listIterator(pathSegments.size());
|
||||
// The last segments, like
|
||||
// 317a1a254cd9c3269e71b2aa2671fe8d28c91097-260198-640-480-png_250.jpg
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String lastSegment = iterator.previous();
|
||||
// The second last segments, like
|
||||
// 7a
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String secondLastSegment = iterator.previous();
|
||||
// The third last segments, like
|
||||
// 31
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String thirdLastSegment = iterator.previous();
|
||||
// Check path segments
|
||||
if (lastSegment != null && secondLastSegment != null
|
||||
&& thirdLastSegment != null
|
||||
&& lastSegment.startsWith(thirdLastSegment)
|
||||
&& lastSegment.startsWith(secondLastSegment, thirdLastSegment.length())) {
|
||||
return getThumbUrlPrefix() + thirdLastSegment + "/" + secondLastSegment + "/" + lastSegment;
|
||||
} else {
|
||||
return originUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,8 @@ import java.util.regex.Pattern;
|
||||
public class EhUtils {
|
||||
|
||||
public static final int NONE = -1; // Use it for homepage
|
||||
public static final int UNKNOWN = 0x400;
|
||||
|
||||
public static final int ALL_CATEGORY = EhUtils.UNKNOWN - 1;
|
||||
//DOUJINSHI|MANGA|ARTIST_CG|GAME_CG|WESTERN|NON_H|IMAGE_SET|COSPLAY|ASIAN_PORN|MISC;
|
||||
public static final int PRIVATE = 0x400;
|
||||
public static final int UNKNOWN = 0x800;
|
||||
|
||||
public static final int BG_COLOR_DOUJINSHI = 0xfff44336;
|
||||
public static final int BG_COLOR_MANGA = 0xffff9800;
|
||||
@@ -68,6 +66,7 @@ public class EhUtils {
|
||||
EhConfig.ASIAN_PORN,
|
||||
EhConfig.NON_H,
|
||||
EhConfig.WESTERN,
|
||||
PRIVATE,
|
||||
UNKNOWN};
|
||||
|
||||
private static final String[][] CATEGORY_STRINGS = {
|
||||
@@ -81,6 +80,7 @@ public class EhUtils {
|
||||
new String[]{"asianporn", "Asian Porn"},
|
||||
new String[]{"non-h"},
|
||||
new String[]{"western"},
|
||||
new String[]{"private"},
|
||||
new String[]{"unknown"}
|
||||
};
|
||||
|
||||
@@ -133,14 +133,14 @@ public class EhUtils {
|
||||
}
|
||||
|
||||
public static void signOut(Context context) {
|
||||
EhApplication.getEhCookieStore(context).signOut();
|
||||
EhApplication.getEhCookieStore().signOut();
|
||||
Settings.putAvatar(null);
|
||||
Settings.putDisplayName(null);
|
||||
Settings.putNeedSignIn(true);
|
||||
}
|
||||
|
||||
public static boolean needSignedIn(Context context) {
|
||||
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore(context).hasSignedIn();
|
||||
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore().hasSignedIn();
|
||||
}
|
||||
|
||||
public static String getSuitableTitle(GalleryInfo gi) {
|
||||
|
||||
+10
-17
@@ -15,30 +15,23 @@
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
import android.annotation.SuppressLint
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class EhX509TrustManager implements X509TrustManager {
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
class EhX509TrustManager : X509TrustManager {
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate?> {
|
||||
return arrayOfNulls(0)
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,17 @@
|
||||
|
||||
package com.hippo.ehviewer.client.data;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.network.UrlBuilder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FavListUrlBuilder implements Parcelable {
|
||||
|
||||
@@ -105,17 +106,21 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
ub.addQuery("favcat", "all");
|
||||
}
|
||||
if (!TextUtils.isEmpty(mKeyword)) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"));
|
||||
// Name
|
||||
ub.addQuery("sn", "on");
|
||||
// Tags
|
||||
ub.addQuery("st", "on");
|
||||
// Note
|
||||
ub.addQuery("sf", "on");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "Can't URLEncoder.encode " + mKeyword);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||
} else {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8.displayName()));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// Name
|
||||
ub.addQuery("sn", "on");
|
||||
// Tags
|
||||
ub.addQuery("st", "on");
|
||||
// Note
|
||||
ub.addQuery("sf", "on");
|
||||
}
|
||||
if (mPrev != null && !mPrev.isEmpty()) {
|
||||
ub.addQuery("prev", mPrev);
|
||||
|
||||
@@ -399,6 +399,9 @@ open class ListUrlBuilder : Cloneable, Parcelable {
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
}
|
||||
if (mJumpTo != null) {
|
||||
sb.append("&seek=").append(mJumpTo)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
@@ -416,6 +419,9 @@ open class ListUrlBuilder : Cloneable, Parcelable {
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
}
|
||||
if (mJumpTo != null) {
|
||||
sb.append("&seek=").append(mJumpTo)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ package com.hippo.ehviewer.client.parser;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryComment;
|
||||
import com.hippo.ehviewer.client.data.GalleryCommentList;
|
||||
@@ -48,14 +46,12 @@ import org.jsoup.select.Elements;
|
||||
import org.jsoup.select.NodeTraversor;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -66,9 +62,6 @@ public class GalleryDetailParser {
|
||||
private static final Pattern PATTERN_TORRENT = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Torrent Download[^<]+(\\d+)[^<]+</a");
|
||||
private static final Pattern PATTERN_ARCHIVE = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Archive Download</a>");
|
||||
private static final Pattern PATTERN_COVER = Pattern.compile("width:(\\d+)px; height:(\\d+)px.+?url\\((.+?)\\)");
|
||||
private static final Pattern PATTERN_TAG_GROUP = Pattern.compile("<tr><td[^<>]+>([\\w\\s]+):</td><td>(?:<div[^<>]+><a[^<>]+>[\\w\\s]+</a></div>)+</td></tr>");
|
||||
private static final Pattern PATTERN_TAG = Pattern.compile("<div[^<>]+><a[^<>]+>([\\w\\s]+)</a></div>");
|
||||
private static final Pattern PATTERN_COMMENT = Pattern.compile("<div class=\"c3\">Posted on ([^<>]+) by: <a[^<>]+>([^<>]+)</a>.+?<div class=\"c6\"[^>]*>(.+?)</div><div class=\"c[78]\"");
|
||||
private static final Pattern PATTERN_PAGES = Pattern.compile("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>");
|
||||
private static final Pattern PATTERN_PREVIEW_PAGES = Pattern.compile("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?>(?:</a>)?</td>");
|
||||
private static final Pattern PATTERN_NORMAL_PREVIEW = Pattern.compile("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"");
|
||||
@@ -78,16 +71,13 @@ public class GalleryDetailParser {
|
||||
private static final GalleryTagGroup[] EMPTY_GALLERY_TAG_GROUP_ARRAY = new GalleryTagGroup[0];
|
||||
private static final GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
|
||||
|
||||
private static final DateFormat WEB_COMMENT_DATE_FORMAT = new SimpleDateFormat("dd MMMMM yyyy, HH:mm", Locale.US);
|
||||
private static final DateTimeFormatter WEB_COMMENT_DATE_FORMAT = DateTimeFormatter
|
||||
.ofPattern("dd MMMM yyyy, HH:mm", Locale.US).withZone(ZoneOffset.UTC);
|
||||
private static final String OFFENSIVE_STRING =
|
||||
"<p>(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)</p>";
|
||||
private static final String PINING_STRING =
|
||||
"<p>This gallery is pining for the fjords.</p>";
|
||||
|
||||
static {
|
||||
WEB_COMMENT_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
public static GalleryDetail parse(String body) throws EhException {
|
||||
if (body.contains(OFFENSIVE_STRING)) {
|
||||
throw new OffensiveException();
|
||||
@@ -188,7 +178,7 @@ public class GalleryDetailParser {
|
||||
// Uploader
|
||||
Element gdn = gm.getElementById("gdn");
|
||||
if (null != gdn) {
|
||||
gd.disowned = "opacity:0.5".equals(gdn.attr("style"));
|
||||
gd.disowned = gdn.attr("style").contains("opacity:0.5");
|
||||
gd.uploader = StringUtils.trim(gdn.text());
|
||||
} else {
|
||||
gd.uploader = "";
|
||||
@@ -198,14 +188,13 @@ public class GalleryDetailParser {
|
||||
gd.posted = "";
|
||||
gd.parent = "";
|
||||
gd.visible = "";
|
||||
gd.visible = "";
|
||||
gd.size = "";
|
||||
gd.pages = 0;
|
||||
gd.favoriteCount = 0;
|
||||
try {
|
||||
Elements es = gdd.child(0).child(0).children();
|
||||
for (int i = 0, n = es.size(); i < n; i++) {
|
||||
parseDetailInfo(gd, es.get(i), body);
|
||||
parseDetailInfo(gd, es.get(i));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
@@ -293,7 +282,7 @@ public class GalleryDetailParser {
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseDetailInfo(GalleryDetail gd, Element e, String body) {
|
||||
private static void parseDetailInfo(GalleryDetail gd, Element e) {
|
||||
Elements es = e.children();
|
||||
if (es.size() < 2) {
|
||||
return;
|
||||
@@ -323,20 +312,16 @@ public class GalleryDetailParser {
|
||||
}
|
||||
} else if (key.startsWith("Favorited")) {
|
||||
switch (value) {
|
||||
case "Never":
|
||||
gd.favoriteCount = 0;
|
||||
break;
|
||||
case "Once":
|
||||
gd.favoriteCount = 1;
|
||||
break;
|
||||
default:
|
||||
case "Never" -> gd.favoriteCount = 0;
|
||||
case "Once" -> gd.favoriteCount = 1;
|
||||
default -> {
|
||||
int index = value.indexOf(' ');
|
||||
if (index == -1) {
|
||||
gd.favoriteCount = 0;
|
||||
} else {
|
||||
gd.favoriteCount = NumberUtils.parseIntSafely(value.substring(0, index), 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,6 +362,7 @@ public class GalleryDetailParser {
|
||||
public static GalleryTagGroup[] parseTagGroups(Document document) {
|
||||
try {
|
||||
Element taglist = document.getElementById("taglist");
|
||||
assert taglist != null;
|
||||
Elements tagGroups = taglist.child(0).child(0).children();
|
||||
return parseTagGroups(tagGroups);
|
||||
} catch (Throwable e) {
|
||||
@@ -396,7 +382,7 @@ public class GalleryDetailParser {
|
||||
list.add(group);
|
||||
}
|
||||
}
|
||||
return list.toArray(new GalleryTagGroup[list.size()]);
|
||||
return list.toArray(new GalleryTagGroup[0]);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
@@ -404,31 +390,6 @@ public class GalleryDetailParser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tag groups with regular expressions
|
||||
*/
|
||||
@NonNull
|
||||
private static GalleryTagGroup[] parseTagGroups(String body) throws EhException {
|
||||
List<GalleryTagGroup> list = new LinkedList<>();
|
||||
|
||||
Matcher m = PATTERN_TAG_GROUP.matcher(body);
|
||||
while (m.find()) {
|
||||
GalleryTagGroup tagGroup = new GalleryTagGroup();
|
||||
tagGroup.groupName = ParserUtils.trim(m.group(1));
|
||||
parseGroup(tagGroup, m.group(0));
|
||||
list.add(tagGroup);
|
||||
}
|
||||
|
||||
return list.toArray(new GalleryTagGroup[list.size()]);
|
||||
}
|
||||
|
||||
private static void parseGroup(GalleryTagGroup tagGroup, String body) {
|
||||
Matcher m = PATTERN_TAG.matcher(body);
|
||||
while (m.find()) {
|
||||
tagGroup.addTag(ParserUtils.trim(m.group(1)));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static GalleryComment parseComment(Element element) {
|
||||
@@ -446,17 +407,15 @@ public class GalleryDetailParser {
|
||||
}
|
||||
for (Element e : c4.children()) {
|
||||
switch (e.text()) {
|
||||
case "Vote+":
|
||||
case "Vote+" -> {
|
||||
comment.voteUpAble = true;
|
||||
comment.voteUpEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
||||
break;
|
||||
case "Vote-":
|
||||
}
|
||||
case "Vote-" -> {
|
||||
comment.voteDownAble = true;
|
||||
comment.voteDownEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
||||
break;
|
||||
case "Edit":
|
||||
comment.editable = true;
|
||||
break;
|
||||
}
|
||||
case "Edit" -> comment.editable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,7 +436,7 @@ public class GalleryDetailParser {
|
||||
Element c3 = JsoupUtils.getElementByClass(element, "c3");
|
||||
String temp = c3.ownText();
|
||||
temp = temp.substring("Posted on ".length(), temp.length() - " by:".length());
|
||||
comment.time = WEB_COMMENT_DATE_FORMAT.parse(temp).getTime();
|
||||
comment.time = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli();
|
||||
// user
|
||||
comment.user = c3.child(0).text();
|
||||
// comment
|
||||
@@ -487,7 +446,7 @@ public class GalleryDetailParser {
|
||||
if (c8 != null) {
|
||||
Element e = c8.children().first();
|
||||
if (e != null) {
|
||||
comment.lastEdited = WEB_COMMENT_DATE_FORMAT.parse(temp).getTime();
|
||||
comment.lastEdited = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli();
|
||||
}
|
||||
}
|
||||
return comment;
|
||||
@@ -505,6 +464,7 @@ public class GalleryDetailParser {
|
||||
public static GalleryCommentList parseComments(Document document) {
|
||||
try {
|
||||
Element cdiv = document.getElementById("cdiv");
|
||||
assert cdiv != null;
|
||||
Elements c1s = cdiv.getElementsByClass("c1");
|
||||
|
||||
List<GalleryComment> list = new ArrayList<>(c1s.size());
|
||||
@@ -517,20 +477,21 @@ public class GalleryDetailParser {
|
||||
|
||||
Element chd = cdiv.getElementById("chd");
|
||||
MutableBoolean hasMore = new MutableBoolean(false);
|
||||
assert chd != null;
|
||||
NodeTraversor.traverse(new NodeVisitor() {
|
||||
@Override
|
||||
public void head(Node node, int depth) {
|
||||
public void head(@NonNull Node node, int depth) {
|
||||
if (node instanceof Element && ((Element) node).text().equals("click to show all")) {
|
||||
hasMore.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tail(Node node, int depth) {
|
||||
public void tail(@NonNull Node node, int depth) {
|
||||
}
|
||||
}, chd);
|
||||
|
||||
return new GalleryCommentList(list.toArray(new GalleryComment[list.size()]), hasMore.value);
|
||||
return new GalleryCommentList(list.toArray(new GalleryComment[0]), hasMore.value);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
@@ -538,38 +499,14 @@ public class GalleryDetailParser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse comments with regular expressions
|
||||
*/
|
||||
@NonNull
|
||||
public static GalleryComment[] parseComments(String body) {
|
||||
List<GalleryComment> list = new LinkedList<>();
|
||||
|
||||
Matcher m = PATTERN_COMMENT.matcher(body);
|
||||
while (m.find()) {
|
||||
String webDateString = ParserUtils.trim(m.group(1));
|
||||
Date date;
|
||||
try {
|
||||
date = WEB_COMMENT_DATE_FORMAT.parse(webDateString);
|
||||
} catch (java.text.ParseException e) {
|
||||
date = new Date(0L);
|
||||
}
|
||||
GalleryComment comment = new GalleryComment();
|
||||
comment.time = date.getTime();
|
||||
comment.user = ParserUtils.trim(m.group(2));
|
||||
comment.comment = m.group(3);
|
||||
list.add(comment);
|
||||
}
|
||||
|
||||
return list.toArray(new GalleryComment[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse preview pages with html parser
|
||||
*/
|
||||
public static int parsePreviewPages(Document document, String body) throws ParseException {
|
||||
try {
|
||||
Elements elements = document.getElementsByClass("ptt").first().child(0).child(0).children();
|
||||
Element ptt = document.getElementsByClass("ptt").first();
|
||||
assert ptt != null;
|
||||
Elements elements = ptt.child(0).child(0).children();
|
||||
return Integer.parseInt(elements.get(elements.size() - 2).text());
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
@@ -636,9 +573,10 @@ public class GalleryDetailParser {
|
||||
try {
|
||||
LargePreviewSet largePreviewSet = new LargePreviewSet();
|
||||
Element gdt = d.getElementById("gdt");
|
||||
assert gdt != null;
|
||||
Elements gdtls = gdt.getElementsByClass("gdtl");
|
||||
int n = gdtls.size();
|
||||
if (n <= 0) {
|
||||
if (n == 0) {
|
||||
throw new ParseException("Can't parse large preview", body);
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
@@ -646,9 +584,6 @@ public class GalleryDetailParser {
|
||||
String pageUrl = element.attr("href");
|
||||
element = element.child(0);
|
||||
String imageUrl = element.attr("src");
|
||||
if (Settings.getFixThumbUrl()) {
|
||||
imageUrl = EhUrl.getFixedPreviewThumbUrl(imageUrl);
|
||||
}
|
||||
int index = Integer.parseInt(element.attr("alt")) - 1;
|
||||
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
||||
}
|
||||
@@ -674,9 +609,6 @@ public class GalleryDetailParser {
|
||||
}
|
||||
String imageUrl = ParserUtils.trim(m.group(3));
|
||||
String pageUrl = ParserUtils.trim(m.group(1));
|
||||
if (Settings.getFixThumbUrl()) {
|
||||
imageUrl = EhUrl.getFixedPreviewThumbUrl(imageUrl);
|
||||
}
|
||||
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import androidx.annotation.NonNull;
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup;
|
||||
import com.hippo.ehviewer.client.exception.ParseException;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.JsoupUtils;
|
||||
@@ -67,16 +66,6 @@ public class GalleryListParser {
|
||||
new String[]{"224", "128", "224"},
|
||||
};
|
||||
|
||||
private static int parsePages(Document d, String body) throws ParseException {
|
||||
try {
|
||||
Elements es = d.getElementsByClass("ptt").first().child(0).child(0).children();
|
||||
return Integer.parseInt(es.get(es.size() - 2).text().trim());
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
throw new ParseException("Can't parse gallery list pages", body);
|
||||
}
|
||||
}
|
||||
|
||||
private static String parseRating(String ratingStyle) {
|
||||
Matcher m = PATTERN_RATING.matcher(ratingStyle);
|
||||
int num1 = Integer.MIN_VALUE;
|
||||
@@ -147,23 +136,21 @@ public class GalleryListParser {
|
||||
children = child.children();
|
||||
}
|
||||
gi.title = child.text().trim();
|
||||
|
||||
Element tbody = JsoupUtils.getElementByTag(glname, "tbody");
|
||||
if (tbody != null) {
|
||||
ArrayList<String> tags = new ArrayList<>();
|
||||
GalleryTagGroup[] groups = GalleryDetailParser.parseTagGroups(tbody.children());
|
||||
for (GalleryTagGroup group : groups) {
|
||||
for (int j = 0; j < group.size(); j++) {
|
||||
tags.add(group.groupName + ":" + group.getTagAt(j));
|
||||
}
|
||||
}
|
||||
gi.simpleTags = tags.toArray(new String[tags.size()]);
|
||||
}
|
||||
}
|
||||
if (gi.title == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Tags
|
||||
Elements gts = e.select(".gt, .gtl");
|
||||
if (gts.size() != 0) {
|
||||
ArrayList<String> tags = new ArrayList<>();
|
||||
for (Element gt : gts) {
|
||||
tags.add(gt.attr("title"));
|
||||
}
|
||||
gi.simpleTags = tags.toArray(new String[0]);
|
||||
}
|
||||
|
||||
// Category
|
||||
gi.category = EhUtils.UNKNOWN;
|
||||
Element ce = JsoupUtils.getElementByClass(e, "cn");
|
||||
@@ -267,10 +254,12 @@ public class GalleryListParser {
|
||||
if (children.size() > uploaderIndex) {
|
||||
Element div = children.get(uploaderIndex);
|
||||
if (div != null) {
|
||||
gi.disowned = "opacity:0.5".equals(div.attr("style"));
|
||||
gi.disowned = div.attr("style").contains("opacity:0.5");
|
||||
Element a = div.children().first();
|
||||
if (a != null) {
|
||||
gi.uploader = a.text().trim();
|
||||
} else {
|
||||
gi.uploader = div.text().trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -309,8 +298,10 @@ public class GalleryListParser {
|
||||
assert next != null;
|
||||
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
|
||||
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
|
||||
if (matcherPrev.find()) result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
|
||||
if (matcherNext.find()) result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
|
||||
if (matcherPrev.find())
|
||||
result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
|
||||
if (matcherNext.find())
|
||||
result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.hippo.ehviewer.client.parser;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
@@ -27,6 +28,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class GalleryListUrlParser {
|
||||
|
||||
@@ -91,10 +93,15 @@ public final class GalleryListUrlParser {
|
||||
uploader = path.substring(prefixLength, index);
|
||||
}
|
||||
|
||||
try {
|
||||
uploader = URLDecoder.decode(uploader, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
uploader = URLDecoder.decode(uploader, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
try {
|
||||
uploader = URLDecoder.decode(uploader, StandardCharsets.UTF_8.displayName());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(uploader)) {
|
||||
@@ -120,10 +127,15 @@ public final class GalleryListUrlParser {
|
||||
tag = path.substring(prefixLength, index);
|
||||
}
|
||||
|
||||
try {
|
||||
tag = URLDecoder.decode(tag, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
tag = URLDecoder.decode(tag, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
try {
|
||||
tag = URLDecoder.decode(tag, StandardCharsets.UTF_8.displayName());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(tag)) {
|
||||
|
||||
@@ -19,17 +19,18 @@ package com.hippo.ehviewer.client.parser;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ParserUtils {
|
||||
|
||||
public static final DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US);
|
||||
public static final DateTimeFormatter formatter = DateTimeFormatter
|
||||
.ofPattern("yyyy-MM-dd HH:mm", Locale.US).withZone(ZoneOffset.UTC);
|
||||
|
||||
public static synchronized String formatDate(long time) {
|
||||
return sDateFormat.format(new Date(time));
|
||||
return formatter.format(Instant.ofEpochMilli(time));
|
||||
}
|
||||
|
||||
public static String trim(String str) {
|
||||
@@ -48,7 +49,4 @@ public class ParserUtils {
|
||||
return NumberUtils.parseLongSafely(trim(str).replace(",", ""), defValue);
|
||||
}
|
||||
|
||||
public static float parseFloat(String str, float defValue) {
|
||||
return NumberUtils.parseFloatSafely(trim(str).replace(",", ""), defValue);
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,6 @@ import java.util.List;
|
||||
|
||||
public interface BasicDao<T> {
|
||||
List<T> list();
|
||||
|
||||
long insert(T t);
|
||||
}
|
||||
@@ -58,10 +58,9 @@ public class Filter {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Filter)) {
|
||||
if (!(o instanceof Filter filter)) {
|
||||
return false;
|
||||
}
|
||||
Filter filter = (Filter) o;
|
||||
return filter.mode == mode && ObjectUtils.equal(filter.text, text);
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
|
||||
.setName(getString(R.string.download_service))
|
||||
.build()
|
||||
)
|
||||
mDownloadManager = EhApplication.getDownloadManager(applicationContext)
|
||||
mDownloadManager = EhApplication.downloadManager
|
||||
mDownloadManager!!.setDownloadListener(this)
|
||||
ensureDownloadingBuilder()
|
||||
mDownloadingBuilder!!.setContentTitle(getString(R.string.download_service))
|
||||
@@ -498,6 +498,7 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
|
||||
}
|
||||
mNotifyManager.notify(mId, mBuilder.build())
|
||||
}
|
||||
|
||||
OPS_CANCEL -> mNotifyManager.cancel(mId)
|
||||
OPS_START_FOREGROUND -> mService.startForeground(mId, mBuilder.build())
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.hippo.ehviewer.gallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Process;
|
||||
@@ -37,6 +38,7 @@ import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -51,15 +53,14 @@ 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 Thread archiveThread;
|
||||
private Thread[] decodeThread = new Thread[] {
|
||||
private final LinkedHashMap<Integer, ByteBuffer> streams = new LinkedHashMap<>();
|
||||
private final Thread[] decodeThread = new Thread[]{
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask())
|
||||
};
|
||||
|
||||
private Thread archiveThread;
|
||||
private volatile int size = STATE_WAIT;
|
||||
private String error;
|
||||
|
||||
@@ -161,7 +162,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
int fd;
|
||||
FileOutputStream stream;
|
||||
try {
|
||||
stream = (FileOutputStream)file.openOutputStream();
|
||||
stream = (FileOutputStream) file.openOutputStream();
|
||||
fd = Native.getFd(stream.getFD());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@@ -187,7 +188,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
}
|
||||
|
||||
private class ArchiveHostTask implements Runnable {
|
||||
public final Thread[] archiveThreads = new Thread[] {
|
||||
public final Thread[] archiveThreads = new Thread[]{
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask()),
|
||||
@@ -298,10 +299,10 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
}
|
||||
}
|
||||
|
||||
long addr = archiveAccessor.extractToAddr(index);
|
||||
ByteBuffer buffer = archiveAccessor.extractToByteBuffer(index);
|
||||
|
||||
synchronized (streams) {
|
||||
streams.put(index, addr);
|
||||
streams.put(index, buffer);
|
||||
streams.notify();
|
||||
}
|
||||
}
|
||||
@@ -313,7 +314,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
public void run() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int index;
|
||||
Long addr;
|
||||
ByteBuffer buffer;
|
||||
synchronized (streams) {
|
||||
if (streams.isEmpty()) {
|
||||
try {
|
||||
@@ -324,14 +325,25 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, Long> entry = iterator.next();
|
||||
Iterator<Map.Entry<Integer, ByteBuffer>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, ByteBuffer> entry = iterator.next();
|
||||
iterator.remove();
|
||||
index = entry.getKey();
|
||||
addr = entry.getValue();
|
||||
buffer = entry.getValue();
|
||||
}
|
||||
|
||||
Image image = Image.decodeAddr(addr, true);
|
||||
Image image = null;
|
||||
if (buffer != null) {
|
||||
try {
|
||||
image = Image.decode(buffer, () -> {
|
||||
archiveAccessor.releaseByteBuffer(buffer);
|
||||
return null;
|
||||
});
|
||||
} catch (ImageDecoder.DecodeException e) {
|
||||
archiveAccessor.releaseByteBuffer(buffer);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
|
||||
@@ -259,13 +259,9 @@ 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((FileInputStream) is);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
}
|
||||
notifyPageSucceed(index, image);
|
||||
} catch (IOException e) {
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_reading_failed));
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.preference
|
||||
|
||||
import android.content.*
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.text.Html
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@@ -28,10 +29,9 @@ import com.hippo.ehviewer.ui.SettingsActivity
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
import com.hippo.preference.MessagePreference
|
||||
import com.hippo.util.addTextToClipboard
|
||||
import com.hippo.util.getClipboardManager
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import java.util.*
|
||||
import java.util.LinkedList
|
||||
|
||||
class AccountPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
@@ -40,7 +40,7 @@ class AccountPreference @JvmOverloads constructor(
|
||||
private var message: String? = null
|
||||
|
||||
init {
|
||||
val store = EhApplication.getEhCookieStore(context)
|
||||
val store = EhApplication.ehCookieStore
|
||||
val eCookies = store.getCookies(EhUrl.HOST_E.toHttpUrl())
|
||||
val exCookies = store.getCookies(EhUrl.HOST_EX.toHttpUrl())
|
||||
val cookies: MutableList<Cookie> = LinkedList(eCookies)
|
||||
|
||||
@@ -55,7 +55,7 @@ public class CleanRedundancyPreference extends TaskPreference {
|
||||
|
||||
public ClearTask(@NonNull Context context) {
|
||||
super(context);
|
||||
mManager = EhApplication.getDownloadManager(context.getApplicationContext());
|
||||
mManager = EhApplication.getDownloadManager();
|
||||
}
|
||||
|
||||
// True for cleared
|
||||
|
||||
@@ -161,7 +161,7 @@ class ProxyPreference @JvmOverloads constructor(
|
||||
Settings.putProxyIp(ip)
|
||||
Settings.putProxyPort(port)
|
||||
updateSummary(type, ip, port)
|
||||
EhApplication.getEhProxySelector(getContext()).updateProxy()
|
||||
EhApplication.ehProxySelector.updateProxy()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
@@ -74,8 +74,8 @@ public class RestoreDownloadPreference extends TaskPreference {
|
||||
|
||||
public RestoreTask(@NonNull Context context) {
|
||||
super(context);
|
||||
mManager = EhApplication.getDownloadManager(context.getApplicationContext());
|
||||
mHttpClient = EhApplication.getOkHttpClient(context.getApplicationContext());
|
||||
mManager = EhApplication.getDownloadManager();
|
||||
mHttpClient = EhApplication.getOkHttpClient();
|
||||
}
|
||||
|
||||
private RestoreItem getRestoreItem(UniFile file) {
|
||||
|
||||
@@ -28,7 +28,6 @@ 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.util.WebViewUtil;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
@@ -59,11 +58,7 @@ public class SignInRequiredPreference extends Preference implements Preference.O
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (!WebViewUtil.available(getContext())) {
|
||||
mActivity.showTip(R.string.error_no_webview, BaseScene.LENGTH_SHORT);
|
||||
return true;
|
||||
}
|
||||
EhCookieStore store = EhApplication.getEhCookieStore(getContext());
|
||||
EhCookieStore store = EhApplication.getEhCookieStore();
|
||||
HttpUrl e = HttpUrl.get(EhUrl.HOST_E);
|
||||
HttpUrl ex = HttpUrl.get(EhUrl.HOST_EX);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.spider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
@@ -157,8 +158,8 @@ public final class SpiderQueen implements Runnable {
|
||||
private volatile int mDownloadPage = -1;
|
||||
|
||||
private SpiderQueen(EhApplication application, @NonNull GalleryInfo galleryInfo) {
|
||||
mHttpClient = EhApplication.getOkHttpClient(application);
|
||||
mSpiderInfoCache = EhApplication.getSpiderInfoCache(application);
|
||||
mHttpClient = EhApplication.getOkHttpClient();
|
||||
mSpiderInfoCache = EhApplication.getSpiderInfoCache();
|
||||
mGalleryInfo = galleryInfo;
|
||||
mSpiderDen = new SpiderDen(mGalleryInfo);
|
||||
|
||||
@@ -1678,8 +1679,8 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
image = Image.decode(((FileInputStream) is).getFD(), true);
|
||||
} catch (IOException e) {
|
||||
image = Image.decode(((FileInputStream) is));
|
||||
} catch (ImageDecoder.DecodeException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
|
||||
@@ -53,7 +53,7 @@ public final class CommonOperations {
|
||||
EhDB.putLocalFavorites(galleryInfo);
|
||||
listener.onSuccess(null);
|
||||
} else if (slot >= 0 && slot <= 9) {
|
||||
EhClient client = EhApplication.getEhClient(activity);
|
||||
EhClient client = EhApplication.getEhClient();
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
|
||||
request.setArgs(galleryInfo.gid, galleryInfo.token, slot, "");
|
||||
@@ -100,7 +100,7 @@ public final class CommonOperations {
|
||||
public static void removeFromFavorites(Activity activity, GalleryInfo galleryInfo,
|
||||
final EhClient.Callback<Void> listener) {
|
||||
EhDB.removeLocalFavorites(galleryInfo.gid);
|
||||
EhClient client = EhApplication.getEhClient(activity);
|
||||
EhClient client = EhApplication.getEhClient();
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
|
||||
request.setArgs(galleryInfo.gid, galleryInfo.token, -1, "");
|
||||
@@ -114,13 +114,13 @@ public final class CommonOperations {
|
||||
|
||||
public static void startDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Objects.requireNonNull(EhApplication.getInstance().getTopActivity()).checkAndRequestNotificationPermission();
|
||||
Objects.requireNonNull(EhApplication.getApplication().getTopActivity()).checkAndRequestNotificationPermission();
|
||||
}
|
||||
doStartDownload(activity, galleryInfos, forceDefault);
|
||||
}
|
||||
|
||||
private static void doStartDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
||||
final DownloadManager dm = EhApplication.getDownloadManager(activity);
|
||||
final DownloadManager dm = EhApplication.getDownloadManager();
|
||||
|
||||
LongList toStart = new LongList();
|
||||
List<GalleryInfo> toAdd = new ArrayList<>();
|
||||
|
||||
@@ -22,7 +22,6 @@ 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
|
||||
@@ -32,7 +31,6 @@ 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 rikka.core.res.resolveColor
|
||||
@@ -71,23 +69,15 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
WindowInsetsHelper.LISTENER
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
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.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)
|
||||
@@ -100,7 +90,7 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
val locked_resume_time = System.currentTimeMillis() / 1000
|
||||
val locked_delay_time = locked_resume_time - locked_last_leave_time
|
||||
val locked_delay_time = locked_resume_time - EhApplication.locked_last_leave_time
|
||||
if (locked_delay_time < Settings.getSecurityDelay() * 60) {
|
||||
EhApplication.locked = false
|
||||
} else if (Settings.getSecurity() && isAuthenticationSupported() && EhApplication.locked) {
|
||||
|
||||
@@ -303,16 +303,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"})
|
||||
@@ -404,26 +395,25 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
if (mGalleryView == null) {
|
||||
return false;
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 300;
|
||||
if (scroll < 0.0f) {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
|
||||
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) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
|
||||
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
|
||||
}
|
||||
if (event.getAction() == MotionEvent.ACTION_SCROLL) {
|
||||
float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 300;
|
||||
if (scroll < 0.0f) {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
|
||||
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) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
|
||||
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
@@ -530,7 +520,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
if (mGLRootView != null) {
|
||||
mGLRootView.onPause();
|
||||
}
|
||||
}
|
||||
} 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 onResume() {
|
||||
|
||||
@@ -19,7 +19,6 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -31,7 +30,6 @@ import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.util.addTextToClipboard
|
||||
import com.hippo.util.getClipboardManager
|
||||
|
||||
class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment() {
|
||||
private var mKeys: ArrayList<String> = arrayListOf()
|
||||
@@ -46,11 +44,6 @@ class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment(
|
||||
): View? {
|
||||
parseDetail()
|
||||
val view = inflater.inflate(R.layout.scene_gallery_info, container, false)
|
||||
dialog?.window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
val recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView
|
||||
val adapter = InfoAdapter()
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
@@ -477,8 +477,7 @@ public final class MainActivity extends StageActivity
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
public void createDrawerView(SceneFragment scene) {
|
||||
if (scene instanceof BaseScene && mRightDrawer != null && mDrawerLayout != null) {
|
||||
BaseScene baseScene = (BaseScene) scene;
|
||||
if (scene instanceof BaseScene baseScene && mRightDrawer != null && mDrawerLayout != null) {
|
||||
mRightDrawer.removeAllViews();
|
||||
View drawerView = baseScene.createDrawerView(
|
||||
baseScene.getLayoutInflater(), mRightDrawer, null);
|
||||
@@ -495,8 +494,7 @@ public final class MainActivity extends StageActivity
|
||||
public void onSceneViewDestroyed(SceneFragment scene) {
|
||||
super.onSceneViewDestroyed(scene);
|
||||
|
||||
if (scene instanceof BaseScene) {
|
||||
BaseScene baseScene = (BaseScene) scene;
|
||||
if (scene instanceof BaseScene baseScene) {
|
||||
baseScene.destroyDrawerView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,39 +45,68 @@ class SecurityActivity : AppCompatActivity() {
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun startAuthentication(
|
||||
title: String,
|
||||
subtitle: String? = null,
|
||||
confirmationRequired: Boolean = true,
|
||||
) {
|
||||
if (isAuthenticating) return
|
||||
isAuthenticating = true
|
||||
startClass2BiometricOrCredentialAuthentication(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
confirmationRequired = confirmationRequired,
|
||||
executor = ContextCompat.getMainExecutor(this),
|
||||
callback = AuthenticationCallback()
|
||||
callback = callback(
|
||||
{ _, _, _ -> onFailed() },
|
||||
{ _, _ -> onSuccess() },
|
||||
{ onFailed() }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
inner class AuthenticationCallback : AuthPromptCallback() {
|
||||
override fun onAuthenticationSucceeded(
|
||||
inline fun callback(
|
||||
crossinline onAuthenticationError: (
|
||||
activity: FragmentActivity?,
|
||||
errorCode: Int,
|
||||
errString: CharSequence
|
||||
) -> Unit,
|
||||
crossinline onAuthenticationSucceeded: (
|
||||
activity: FragmentActivity?,
|
||||
result: BiometricPrompt.AuthenticationResult
|
||||
) {
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
) -> Unit,
|
||||
crossinline onAuthenticationFailed: (
|
||||
activity: FragmentActivity?
|
||||
) -> Unit
|
||||
) = object : AuthPromptCallback() {
|
||||
override fun onAuthenticationError(
|
||||
activity: FragmentActivity?,
|
||||
errorCode: Int,
|
||||
errString: CharSequence
|
||||
) {
|
||||
moveTaskToBack(true)
|
||||
}
|
||||
) = onAuthenticationError(activity, errorCode, errString)
|
||||
|
||||
override fun onAuthenticationSucceeded(
|
||||
activity: FragmentActivity?,
|
||||
result: BiometricPrompt.AuthenticationResult
|
||||
) = onAuthenticationSucceeded(activity, result)
|
||||
|
||||
override fun onAuthenticationFailed(activity: FragmentActivity?) =
|
||||
onAuthenticationFailed(activity)
|
||||
}
|
||||
|
||||
private fun onSuccess() {
|
||||
isAuthenticating = false
|
||||
EhApplication.locked = false
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun onFailed() {
|
||||
moveTaskToBack(true)
|
||||
isAuthenticating = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var isAuthenticating = false
|
||||
}
|
||||
}
|
||||
@@ -18,21 +18,29 @@ package com.hippo.ehviewer.ui
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
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() {
|
||||
private var mFab: FloatingActionButton? = null
|
||||
private var mContentText: TextView? = null
|
||||
private var mAppbarLayout: AppBarLayout? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_preference)
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
val bar = supportActionBar
|
||||
bar?.setDisplayHomeAsUpEnabled(true)
|
||||
mContentText = findViewById(R.id.tip)
|
||||
mFab = findViewById(R.id.fab)
|
||||
mAppbarLayout = findViewById(R.id.appbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
|
||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
|
||||
if (savedInstanceState == null) {
|
||||
@@ -62,4 +70,16 @@ class SettingsActivity : EhActivity() {
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
fun getMFab(): FloatingActionButton {
|
||||
return mFab!!
|
||||
}
|
||||
|
||||
fun getMContentText(): TextView {
|
||||
return mContentText!!
|
||||
}
|
||||
|
||||
fun resetAppbarLiftStatus() {
|
||||
mAppbarLayout?.setExpanded(true)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
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;
|
||||
@@ -59,7 +58,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 +69,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
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(),
|
||||
uri -> {
|
||||
@@ -227,6 +224,8 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
private int favTotal;
|
||||
private int favIndex;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
@@ -238,8 +237,8 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
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);
|
||||
Preference openByDefault = findPreference(KEY_OPEN_BY_DEFAULT);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
openByDefault.setVisible(false);
|
||||
@@ -282,6 +281,14 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
return true;
|
||||
} else if (KEY_BACKUP_FAVORITE.equals(key)) {
|
||||
try {
|
||||
backupFavorite();
|
||||
} catch (Exception e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
return true;
|
||||
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
|
||||
try {
|
||||
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
||||
@@ -293,26 +300,15 @@ 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);
|
||||
EhClient mClient = EhApplication.getEhClient();
|
||||
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
|
||||
// favListUrlBuilder.setNext(page.get());
|
||||
favTotal = 0;
|
||||
favIndex = 1;
|
||||
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||
@@ -320,31 +316,53 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
try {
|
||||
FavoritesParser.Result result1 = (FavoritesParser.Result) result;
|
||||
EhDB.putLocalFavorites(result1.galleryInfoList);
|
||||
Log.d("LocalFavorites", "now backup page " + page.get());
|
||||
if (!result1.galleryInfoList.isEmpty()) {
|
||||
// favListUrlBuilder.setNext(page.incrementAndGet());
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
FavoritesParser.Result favResult = (FavoritesParser.Result) result;
|
||||
if (favResult.galleryInfoList.isEmpty()) {
|
||||
showTip(R.string.settings_advanced_backup_favorite_nothing, BaseScene.LENGTH_SHORT);
|
||||
} else {
|
||||
showTip(R.string.backup_favorite_success, BaseScene.LENGTH_SHORT);
|
||||
if (favTotal == 0 && favResult.countArray != null) {
|
||||
int totalFav = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
totalFav = totalFav + favResult.countArray[i];
|
||||
}
|
||||
favTotal = (int) Math.ceil((double) totalFav / favResult.galleryInfoList.size());
|
||||
}
|
||||
|
||||
String status = "(" + favIndex + "/" + favTotal + ")";
|
||||
showTip(GetText.getString(R.string.settings_advanced_backup_favorite_start, status), BaseScene.LENGTH_SHORT);
|
||||
Log.d("LocalFavorites", "now backup page " + status);
|
||||
EhDB.putLocalFavorites(favResult.galleryInfoList);
|
||||
|
||||
if (favResult.nextPage != null) {
|
||||
try {
|
||||
Thread.sleep(Settings.getDownloadDelay());
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
favIndex++;
|
||||
favListUrlBuilder.setNext(favResult.nextPage);
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
} else {
|
||||
showTip(R.string.settings_advanced_backup_favorite_success, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
});
|
||||
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
}
|
||||
|
||||
@@ -1,50 +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/>.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.hippo.ehviewer.ui.SettingsActivity;
|
||||
|
||||
public class BaseFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
setTitle(getFragmentTitle());
|
||||
}
|
||||
|
||||
@StringRes
|
||||
public int getFragmentTitle() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void setTitle(@StringRes int string) {
|
||||
requireActivity().setTitle(string);
|
||||
}
|
||||
|
||||
public void showTip(@StringRes int id, int length) {
|
||||
((SettingsActivity) requireActivity()).showTip(getString(id), length);
|
||||
}
|
||||
|
||||
public void showTip(CharSequence message, int length) {
|
||||
((SettingsActivity) requireActivity()).showTip(message, length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.fragment
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.hippo.ehviewer.ui.SettingsActivity
|
||||
|
||||
abstract class BaseFragment : Fragment() {
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setTitle(getFragmentTitle())
|
||||
(requireActivity() as SettingsActivity).resetAppbarLiftStatus()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
hideFabAndClearTip()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
abstract fun getFragmentTitle(): Int
|
||||
|
||||
private fun setTitle(@StringRes string: Int) {
|
||||
requireActivity().setTitle(string)
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, length: Int) {
|
||||
(requireActivity() as SettingsActivity).showTip(getString(id), length)
|
||||
}
|
||||
|
||||
fun showTip(message: CharSequence?, length: Int) {
|
||||
(requireActivity() as SettingsActivity).showTip(message, length)
|
||||
}
|
||||
|
||||
fun getTipView(@StringRes res: Int? = null): TextView {
|
||||
return (requireActivity() as SettingsActivity).getMContentText().apply {
|
||||
visibility = View.VISIBLE
|
||||
res?.let { text = getString(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getFabViewAndShow(): FloatingActionButton {
|
||||
return (requireActivity() as SettingsActivity).getMFab().apply { visibility = View.VISIBLE }
|
||||
}
|
||||
|
||||
private fun hideFabAndClearTip() {
|
||||
getFabViewAndShow().apply {
|
||||
visibility = View.GONE
|
||||
setOnClickListener(null)
|
||||
}
|
||||
getTipView().apply {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.Preference
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
@@ -78,7 +79,7 @@ class EhFragment : BasePreferenceFragment() {
|
||||
}
|
||||
} else if (Settings.KEY_BLACK_DARK_THEME == key) {
|
||||
if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) {
|
||||
requireActivity().recreate()
|
||||
EhApplication.application.recreateAllActivity()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -20,7 +20,12 @@ package com.hippo.ehviewer.ui.fragment
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
@@ -31,10 +36,8 @@ 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.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
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
@@ -66,26 +69,23 @@ class FilterFragment : BaseFragment() {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.activity_filter, container, false)
|
||||
val recyclerView: RecyclerView =
|
||||
ViewUtils.`$$`(view, R.id.recycler_view) as EasyRecyclerView
|
||||
val tip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
): View {
|
||||
val recyclerView =
|
||||
inflater.inflate(R.layout.rv_layout, container, false) as RecyclerView
|
||||
val tip = getTipView(R.string.filter)
|
||||
mViewTransition = ViewTransition(recyclerView, tip)
|
||||
val fab = view.findViewById<FloatingActionButton>(R.id.fab)
|
||||
val fab = getFabViewAndShow()
|
||||
val drawable = ContextCompat.getDrawable(requireActivity(), R.drawable.big_filter)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
drawable?.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
tip.setCompoundDrawables(null, drawable, null, null)
|
||||
mAdapter.setHasStableIds(true)
|
||||
recyclerView.adapter = mAdapter
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.clipChildren = false
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
recyclerView.setHasFixedSize(true)
|
||||
val defaultItemAnimator = recyclerView.itemAnimator as DefaultItemAnimator?
|
||||
defaultItemAnimator?.supportsChangeAnimations = false
|
||||
fab.setOnClickListener { showAddFilterDialog() }
|
||||
return view
|
||||
return recyclerView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -106,9 +106,9 @@ class FilterFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mViewTransition = null
|
||||
requireActivity().removeMenuProvider(mMenuProvider)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun showTipDialog() {
|
||||
|
||||
@@ -1,283 +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/>.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
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;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class HostsFragment extends BaseFragment
|
||||
implements View.OnClickListener {
|
||||
|
||||
private static final String DIALOG_TAG_ADD_HOST = AddHostDialogFragment.class.getName();
|
||||
private static final String DIALOG_TAG_EDIT_HOST = EditHostDialogFragment.class.getName();
|
||||
|
||||
private static final String KEY_HOST = "com.hippo.ehviewer.ui.fragment.HostsFragment.HOST";
|
||||
private static final String KEY_IP = "com.hippo.ehviewer.ui.fragment.HostsFragment.IP";
|
||||
|
||||
private Hosts hosts;
|
||||
private List<Pair<String, String>> data;
|
||||
|
||||
private ViewTransition mViewTransition;
|
||||
private HostsAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
hosts = EhApplication.getHosts(requireContext());
|
||||
data = hosts.getAll();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.activity_hosts, container, false);
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
|
||||
TextView tip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
mViewTransition = new ViewTransition(recyclerView, tip);
|
||||
FloatingActionButton fab = view.findViewById(R.id.fab);
|
||||
|
||||
adapter = new HostsAdapter();
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false));
|
||||
recyclerView.setHasFixedSize(true);
|
||||
|
||||
fab.setOnClickListener(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
updateView(false);
|
||||
}
|
||||
|
||||
public boolean onItemClick(int position) {
|
||||
Pair<String, String> pair = data.get(position);
|
||||
Bundle args = new Bundle();
|
||||
args.putString(KEY_HOST, pair.first);
|
||||
args.putString(KEY_IP, pair.second);
|
||||
|
||||
DialogFragment fragment = new EditHostDialogFragment();
|
||||
fragment.setArguments(args);
|
||||
fragment.show(getChildFragmentManager(), DIALOG_TAG_EDIT_HOST);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AddHostDialogFragment().show(getChildFragmentManager(), DIALOG_TAG_ADD_HOST);
|
||||
}
|
||||
|
||||
private void updateView(boolean animation) {
|
||||
if (null == mViewTransition) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = hosts.getAll();
|
||||
if (data.isEmpty()) {
|
||||
mViewTransition.showView(1, animation);
|
||||
} else {
|
||||
mViewTransition.showView(0, animation);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFragmentTitle() {
|
||||
return R.string.hosts;
|
||||
}
|
||||
|
||||
public abstract static class HostDialogFragment extends DialogFragment {
|
||||
|
||||
private HostsFragment hostsFragment;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
hostsFragment = (HostsFragment) getParentFragment();
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_hosts, null, false);
|
||||
TextView host = view.findViewById(R.id.host);
|
||||
TextView ip = view.findViewById(R.id.ip);
|
||||
|
||||
Bundle arguments = getArguments();
|
||||
if (savedInstanceState == null && arguments != null) {
|
||||
host.setText(arguments.getString(KEY_HOST));
|
||||
ip.setText(arguments.getString(KEY_IP));
|
||||
}
|
||||
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(requireContext());
|
||||
builder.setView(view);
|
||||
onCreateDialogBuilder(builder);
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
protected abstract void onCreateDialogBuilder(AlertDialog.Builder builder);
|
||||
|
||||
protected abstract void onCreateDialog(AlertDialog dialog);
|
||||
|
||||
protected void put(AlertDialog dialog) {
|
||||
TextView host = dialog.findViewById(R.id.host);
|
||||
TextView ip = dialog.findViewById(R.id.ip);
|
||||
if (host == null || ip == null) {
|
||||
return;
|
||||
}
|
||||
String hostString = host.getText().toString().trim().toLowerCase(Locale.US);
|
||||
String ipString = ip.getText().toString().trim();
|
||||
|
||||
if (!Hosts.isValidHost(hostString)) {
|
||||
TextInputLayout hostInputLayout = dialog.findViewById(R.id.host_input_layout);
|
||||
if (hostInputLayout == null) {
|
||||
return;
|
||||
}
|
||||
hostInputLayout.setError(getString(R.string.invalid_host));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Hosts.isValidIp(ipString)) {
|
||||
TextInputLayout ipInputLayout = dialog.findViewById(R.id.ip_input_layout);
|
||||
if (ipInputLayout == null) {
|
||||
return;
|
||||
}
|
||||
ipInputLayout.setError(getString(R.string.invalid_ip));
|
||||
return;
|
||||
}
|
||||
|
||||
hostsFragment.hosts.put(hostString, ipString);
|
||||
hostsFragment.updateView(true);
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
protected void delete(AlertDialog dialog) {
|
||||
TextView host = dialog.findViewById(R.id.host);
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
String hostString = host.getText().toString().trim().toLowerCase(Locale.US);
|
||||
|
||||
hostsFragment.hosts.delete(hostString);
|
||||
hostsFragment.updateView(true);
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AddHostDialogFragment extends HostDialogFragment {
|
||||
|
||||
@Override
|
||||
protected void onCreateDialogBuilder(AlertDialog.Builder builder) {
|
||||
builder.setTitle(R.string.add_host);
|
||||
builder.setPositiveButton(R.string.add_host_add, null);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateDialog(AlertDialog dialog) {
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog));
|
||||
}
|
||||
}
|
||||
|
||||
public static class EditHostDialogFragment extends HostDialogFragment {
|
||||
|
||||
@Override
|
||||
protected void onCreateDialogBuilder(AlertDialog.Builder builder) {
|
||||
builder.setTitle(R.string.edit_host);
|
||||
builder.setPositiveButton(R.string.edit_host_confirm, null);
|
||||
builder.setNegativeButton(R.string.edit_host_delete, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateDialog(AlertDialog dialog) {
|
||||
TextInputLayout hostInputLayout = dialog.findViewById(R.id.host_input_layout);
|
||||
if (hostInputLayout == null) {
|
||||
return;
|
||||
}
|
||||
hostInputLayout.setEnabled(false);
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog));
|
||||
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> delete(dialog));
|
||||
}
|
||||
}
|
||||
|
||||
private static class HostsHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final TextView host;
|
||||
public final TextView ip;
|
||||
|
||||
public HostsHolder(View itemView) {
|
||||
super(itemView);
|
||||
host = itemView.findViewById(R.id.host);
|
||||
ip = itemView.findViewById(R.id.ip);
|
||||
}
|
||||
}
|
||||
|
||||
private class HostsAdapter extends RecyclerView.Adapter<HostsHolder> {
|
||||
|
||||
private final LayoutInflater inflater = getLayoutInflater();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public HostsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new HostsHolder(inflater.inflate(R.layout.item_hosts, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull HostsHolder holder, int position) {
|
||||
Pair<String, String> pair = data.get(position);
|
||||
holder.host.setText(pair.first);
|
||||
holder.ip.setText(pair.second);
|
||||
holder.itemView.setOnClickListener(v -> onItemClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.fragment
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.util.Pair
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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
|
||||
import com.hippo.view.ViewTransition
|
||||
|
||||
class HostsFragment : BaseFragment(), View.OnClickListener {
|
||||
private var hosts: Hosts? = null
|
||||
private var data: List<Pair<String, String>>? = null
|
||||
private var mViewTransition: ViewTransition? = null
|
||||
private var adapter: HostsAdapter? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
EhApplication.hosts.apply {
|
||||
hosts = this
|
||||
data = all
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val recyclerView = inflater.inflate(R.layout.rv_layout, container, false) as RecyclerView
|
||||
val tip = getTipView(R.string.hosts)
|
||||
mViewTransition = ViewTransition(recyclerView, tip)
|
||||
val fab = getFabViewAndShow()
|
||||
adapter = HostsAdapter()
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.layoutManager =
|
||||
LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false)
|
||||
recyclerView.setHasFixedSize(true)
|
||||
fab.setOnClickListener(this)
|
||||
return recyclerView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
updateView(false)
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
val pair = data!![position]
|
||||
val args = Bundle()
|
||||
args.putString(KEY_HOST, pair.first)
|
||||
args.putString(KEY_IP, pair.second)
|
||||
val fragment: DialogFragment = EditHostDialogFragment()
|
||||
fragment.arguments = args
|
||||
fragment.show(childFragmentManager, DIALOG_TAG_EDIT_HOST)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
AddHostDialogFragment().show(childFragmentManager, DIALOG_TAG_ADD_HOST)
|
||||
}
|
||||
|
||||
private fun updateView(animation: Boolean) {
|
||||
if (null == mViewTransition) {
|
||||
return
|
||||
}
|
||||
data = hosts!!.all
|
||||
if ((data as MutableList<Pair<String, String>>?)?.isEmpty() == true) {
|
||||
mViewTransition!!.showView(1, animation)
|
||||
} else {
|
||||
mViewTransition!!.showView(0, animation)
|
||||
}
|
||||
adapter!!.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getFragmentTitle(): Int {
|
||||
return R.string.hosts
|
||||
}
|
||||
|
||||
abstract class HostDialogFragment : DialogFragment() {
|
||||
private var hostsFragment: HostsFragment? = null
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
hostsFragment = parentFragment as HostsFragment?
|
||||
val view = layoutInflater.inflate(R.layout.dialog_hosts, null, false)
|
||||
val host = view.findViewById<TextView>(R.id.host)
|
||||
val ip = view.findViewById<TextView>(R.id.ip)
|
||||
val arguments = arguments
|
||||
if (savedInstanceState == null && arguments != null) {
|
||||
host.text = arguments.getString(KEY_HOST)
|
||||
ip.text = arguments.getString(KEY_IP)
|
||||
}
|
||||
val builder = BaseDialogBuilder(requireContext())
|
||||
builder.setView(view)
|
||||
onCreateDialogBuilder(builder)
|
||||
val dialog = builder.create()
|
||||
dialog.setOnShowListener { d: DialogInterface -> onCreateDialog(d as AlertDialog) }
|
||||
return dialog
|
||||
}
|
||||
|
||||
protected abstract fun onCreateDialogBuilder(builder: AlertDialog.Builder)
|
||||
protected abstract fun onCreateDialog(dialog: AlertDialog)
|
||||
protected fun put(dialog: AlertDialog) {
|
||||
val host = dialog.findViewById<TextView>(R.id.host)
|
||||
val ip = dialog.findViewById<TextView>(R.id.ip)
|
||||
if (host == null || ip == null) {
|
||||
return
|
||||
}
|
||||
val hostString = host.text.toString().trim { it <= ' ' }.lowercase()
|
||||
val ipString = ip.text.toString().trim { it <= ' ' }
|
||||
if (!Hosts.isValidHost(hostString)) {
|
||||
val hostInputLayout =
|
||||
dialog.findViewById<TextInputLayout>(R.id.host_input_layout) ?: return
|
||||
hostInputLayout.error = getString(R.string.invalid_host)
|
||||
return
|
||||
}
|
||||
if (!Hosts.isValidIp(ipString)) {
|
||||
val ipInputLayout =
|
||||
dialog.findViewById<TextInputLayout>(R.id.ip_input_layout) ?: return
|
||||
ipInputLayout.error = getString(R.string.invalid_ip)
|
||||
return
|
||||
}
|
||||
hostsFragment!!.hosts!!.put(hostString, ipString)
|
||||
hostsFragment!!.updateView(true)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
protected fun delete(dialog: AlertDialog) {
|
||||
val host = dialog.findViewById<TextView>(R.id.host) ?: return
|
||||
val hostString = host.text.toString().trim { it <= ' ' }.lowercase()
|
||||
hostsFragment!!.hosts!!.delete(hostString)
|
||||
hostsFragment!!.updateView(true)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
class AddHostDialogFragment : HostDialogFragment() {
|
||||
override fun onCreateDialogBuilder(builder: AlertDialog.Builder) {
|
||||
builder.setTitle(R.string.add_host)
|
||||
builder.setPositiveButton(R.string.add_host_add, null)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(dialog: AlertDialog) {
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
.setOnClickListener { put(dialog) }
|
||||
}
|
||||
}
|
||||
|
||||
class EditHostDialogFragment : HostDialogFragment() {
|
||||
override fun onCreateDialogBuilder(builder: AlertDialog.Builder) {
|
||||
builder.setTitle(R.string.edit_host)
|
||||
builder.setPositiveButton(R.string.edit_host_confirm, null)
|
||||
builder.setNegativeButton(R.string.edit_host_delete, null)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(dialog: AlertDialog) {
|
||||
val hostInputLayout =
|
||||
dialog.findViewById<TextInputLayout>(R.id.host_input_layout) ?: return
|
||||
hostInputLayout.isEnabled = false
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
.setOnClickListener { put(dialog) }
|
||||
dialog.getButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
.setOnClickListener { delete(dialog) }
|
||||
}
|
||||
}
|
||||
|
||||
private class HostsHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val host: TextView
|
||||
val ip: TextView
|
||||
|
||||
init {
|
||||
host = itemView.findViewById(R.id.host)
|
||||
ip = itemView.findViewById(R.id.ip)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class HostsAdapter : RecyclerView.Adapter<HostsHolder>() {
|
||||
private val inflater = layoutInflater
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HostsHolder {
|
||||
return HostsHolder(inflater.inflate(R.layout.item_hosts, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: HostsHolder, position: Int) {
|
||||
val pair = data!![position]
|
||||
holder.host.text = pair.first
|
||||
holder.ip.text = pair.second
|
||||
holder.itemView.setOnClickListener { onItemClick(position) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return data!!.size
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIALOG_TAG_ADD_HOST = AddHostDialogFragment::class.java.name
|
||||
private val DIALOG_TAG_EDIT_HOST = EditHostDialogFragment::class.java.name
|
||||
private const val KEY_HOST = "com.hippo.ehviewer.ui.fragment.HostsFragment.HOST"
|
||||
private const val KEY_IP = "com.hippo.ehviewer.ui.fragment.HostsFragment.IP"
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class MyTagsFragment extends BaseFragment {
|
||||
|
||||
// Copy cookies from okhttp cookie store to CookieManager
|
||||
url = EhUrl.getMyTagsUrl();
|
||||
EhCookieStore store = EhApplication.getEhCookieStore(requireContext());
|
||||
EhCookieStore store = EhApplication.getEhCookieStore();
|
||||
for (Cookie cookie : store.getCookies(HttpUrl.parse(url))) {
|
||||
cookieManager.setCookie(url, cookie.toString());
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class UConfigFragment extends BaseFragment {
|
||||
|
||||
// Copy cookies from okhttp cookie store to CookieManager
|
||||
url = EhUrl.getUConfigUrl();
|
||||
EhCookieStore store = EhApplication.getEhCookieStore(requireContext());
|
||||
EhCookieStore store = EhApplication.getEhCookieStore();
|
||||
for (Cookie cookie : store.getCookies(HttpUrl.parse(url))) {
|
||||
cookieManager.setCookie(url, cookie.toString());
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public class UConfigFragment extends BaseFragment {
|
||||
String cookiesString = cookieManager.getCookie(url);
|
||||
|
||||
if (cookiesString != null && !cookiesString.isEmpty()) {
|
||||
EhCookieStore store = EhApplication.getEhCookieStore(requireContext());
|
||||
EhCookieStore store = EhApplication.getEhCookieStore();
|
||||
HttpUrl eUrl = HttpUrl.parse(EhUrl.HOST_E);
|
||||
HttpUrl exUrl = HttpUrl.parse(EhUrl.HOST_EX);
|
||||
if (eUrl == null || exUrl == null) {
|
||||
|
||||
@@ -42,8 +42,6 @@ import androidx.fragment.app.FragmentActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
public abstract class BaseScene extends SceneFragment {
|
||||
|
||||
public static final int LENGTH_SHORT = 0;
|
||||
|
||||
@@ -11,10 +11,16 @@ abstract class CollapsingToolbarScene : ToolbarScene() {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.scene_collapsing_toolbar, container, false) as ViewGroup
|
||||
val view =
|
||||
inflater.inflate(R.layout.scene_collapsing_toolbar, container, false) as ViewGroup
|
||||
mToolbar = view.findViewById(R.id.toolbar)
|
||||
mAppBarLayout = view.findViewById(R.id.appbar)
|
||||
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
|
||||
return view.apply { addView(contentView, 0) }
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
mAppBarLayout?.setExpanded(false, true)
|
||||
}
|
||||
}
|
||||
@@ -240,7 +240,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
|
||||
EhUtils.signOut(context);
|
||||
|
||||
EhCookieStore store = EhApplication.getEhCookieStore(context);
|
||||
EhCookieStore store = EhApplication.getEhCookieStore();
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E));
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_EX));
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_E));
|
||||
|
||||
@@ -162,7 +162,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
List<DownloadLabel> listLabel = EhApplication.getDownloadManager(context).getLabelList();
|
||||
List<DownloadLabel> listLabel = EhApplication.getDownloadManager().getLabelList();
|
||||
mLabels = new ArrayList<>(listLabel.size() + 1);
|
||||
// Add default label name
|
||||
mLabels.add(getString(R.string.default_download_label_name));
|
||||
@@ -214,7 +214,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
mDownloadManager = EhApplication.getDownloadManager(context);
|
||||
mDownloadManager = EhApplication.getDownloadManager();
|
||||
mDownloadManager.addDownloadInfoListener(this);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
@@ -233,7 +233,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
if (null == manager) {
|
||||
Context context = getContext();
|
||||
if (null != context) {
|
||||
manager = EhApplication.getDownloadManager(context);
|
||||
manager = EhApplication.getDownloadManager();
|
||||
}
|
||||
} else {
|
||||
mDownloadManager = null;
|
||||
@@ -314,6 +314,11 @@ public class DownloadsScene extends ToolbarScene
|
||||
setLiftOnScrollTargetView(mRecyclerView);
|
||||
mFastScroller = (FastScroller) ViewUtils.$$(content, R.id.fast_scroller);
|
||||
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
|
||||
|
||||
// Workaround
|
||||
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
|
||||
container.addView(mFabLayout);
|
||||
|
||||
mTip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
mViewTransition = new ViewTransition(content, mTip);
|
||||
|
||||
@@ -389,6 +394,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
}
|
||||
if (null != mFabLayout) {
|
||||
removeAboveSnackView(mFabLayout);
|
||||
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
|
||||
mFabLayout = null;
|
||||
}
|
||||
|
||||
@@ -691,7 +697,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
break;
|
||||
}
|
||||
case 4: {// Move
|
||||
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager(context).getLabelList();
|
||||
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager().getLabelList();
|
||||
List<String> labelList = new ArrayList<>(labelRawList.size() + 1);
|
||||
labelList.add(getString(R.string.default_download_label_name));
|
||||
for (int i = 0, n = labelRawList.size(); i < n; i++) {
|
||||
@@ -900,7 +906,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
String label = mLabels.get(position);
|
||||
if (mDownloadManager == null) {
|
||||
if (context != null) {
|
||||
mDownloadManager = EhApplication.getDownloadManager(context);
|
||||
mDownloadManager = EhApplication.getDownloadManager();
|
||||
}
|
||||
}
|
||||
List<DownloadInfo> list = null;
|
||||
@@ -1034,7 +1040,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
} else {
|
||||
label = mLabels[which];
|
||||
}
|
||||
EhApplication.getDownloadManager(context).changeLabel(mDownloadInfoList, label);
|
||||
EhApplication.getDownloadManager().changeLabel(mDownloadInfoList, label);
|
||||
if (mLabelAdapter != null) {
|
||||
mLabelAdapter.notifyDataSetChanged();
|
||||
}
|
||||
@@ -1156,7 +1162,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
return;
|
||||
}
|
||||
DownloadInfo info = mList.get(position);
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb, true);
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(info.gid), info.thumb);
|
||||
holder.title.setText(EhUtils.getSuitableTitle(info));
|
||||
holder.uploader.setText(info.uploader);
|
||||
holder.rating.setRating(info.rating);
|
||||
@@ -1240,12 +1246,12 @@ public class DownloadsScene extends ToolbarScene
|
||||
mBuilder.setError(getString(R.string.label_text_is_empty));
|
||||
} else if (getString(R.string.default_download_label_name).equals(text)) {
|
||||
mBuilder.setError(getString(R.string.label_text_is_invalid));
|
||||
} else if (EhApplication.getDownloadManager(context).containLabel(text)) {
|
||||
} else if (EhApplication.getDownloadManager().containLabel(text)) {
|
||||
mBuilder.setError(getString(R.string.label_text_exist));
|
||||
} else {
|
||||
mBuilder.setError(null);
|
||||
mDialog.dismiss();
|
||||
EhApplication.getDownloadManager(context).renameLabel(mOriginalLabel, text);
|
||||
EhApplication.getDownloadManager().renameLabel(mOriginalLabel, text);
|
||||
if (mLabelAdapter != null) {
|
||||
initLabels();
|
||||
mLabelAdapter.notifyDataSetChanged();
|
||||
@@ -1280,12 +1286,12 @@ public class DownloadsScene extends ToolbarScene
|
||||
mBuilder.setError(getString(R.string.label_text_is_empty));
|
||||
} else if (getString(R.string.default_download_label_name).equals(text)) {
|
||||
mBuilder.setError(getString(R.string.label_text_is_invalid));
|
||||
} else if (EhApplication.getDownloadManager(context).containLabel(text)) {
|
||||
} else if (EhApplication.getDownloadManager().containLabel(text)) {
|
||||
mBuilder.setError(getString(R.string.label_text_exist));
|
||||
} else {
|
||||
mBuilder.setError(null);
|
||||
mDialog.dismiss();
|
||||
EhApplication.getDownloadManager(context).addLabel(text);
|
||||
EhApplication.getDownloadManager().addLabel(text);
|
||||
initLabels();
|
||||
if (mLabelAdapter != null && mLabels != null) {
|
||||
mLabelAdapter.notifyItemInserted(mLabels.size() - 1);
|
||||
@@ -1311,7 +1317,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
return false;
|
||||
}
|
||||
|
||||
EhApplication.getDownloadManager(context).moveLabel(fromPosition - 1, toPosition - 1);
|
||||
EhApplication.getDownloadManager().moveLabel(fromPosition - 1, toPosition - 1);
|
||||
final String item = mLabels.remove(fromPosition);
|
||||
mLabels.add(toPosition, item);
|
||||
mLabelAdapter.notifyDataSetChanged();
|
||||
|
||||
@@ -34,19 +34,20 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsAnimationCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.datepicker.CalendarConstraints;
|
||||
import com.google.android.material.datepicker.CompositeDateValidator;
|
||||
import com.google.android.material.datepicker.DateValidatorPointBackward;
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.annotation.Implemented;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.drawable.AddDeleteDrawable;
|
||||
import com.hippo.drawable.DrawerArrowDrawable;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.FastScroller;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
@@ -66,17 +67,18 @@ import com.hippo.ehviewer.ui.annotation.DrawerLifeCircle;
|
||||
import com.hippo.ehviewer.ui.annotation.ViewLifeCircle;
|
||||
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
|
||||
import com.hippo.ehviewer.widget.GalleryInfoContentHelper;
|
||||
import com.hippo.ehviewer.widget.SearchBar;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.widget.ContentLayout;
|
||||
import com.hippo.widget.FabLayout;
|
||||
import com.hippo.widget.SearchBarMover;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -85,8 +87,8 @@ import rikka.core.res.ResourcesKt;
|
||||
|
||||
// TODO Get favorite, modify favorite, add favorite, what a mess!
|
||||
@SuppressLint("RtlHardcoded")
|
||||
public class FavoritesScene extends BaseScene implements
|
||||
FastScroller.OnDragHandlerListener, SearchBarMover.Helper, SearchBar.Helper,
|
||||
public class FavoritesScene extends SearchBarScene implements
|
||||
FastScroller.OnDragHandlerListener,
|
||||
FabLayout.OnClickFabListener, FabLayout.OnExpandListener,
|
||||
EasyRecyclerView.CustomChoiceListener {
|
||||
|
||||
@@ -107,35 +109,13 @@ public class FavoritesScene extends BaseScene implements
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private SearchBar mSearchBar;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private FabLayout mFabLayout;
|
||||
private final Runnable showNormalFabsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mFabLayout != null) {
|
||||
mFabLayout.setSecondaryFabVisibilityAt(0, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(2, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(3, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(4, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(5, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private FavoritesAdapter mAdapter;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private FavoritesHelper mHelper;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private SearchBarMover mSearchBarMover;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private DrawerArrowDrawable mLeftDrawable;
|
||||
private AddDeleteDrawable mActionFabDrawable;
|
||||
@Nullable
|
||||
private DrawerLayout mDrawerLayout;
|
||||
@@ -154,6 +134,21 @@ public class FavoritesScene extends BaseScene implements
|
||||
@Nullable
|
||||
@WholeLifeCircle
|
||||
private FavListUrlBuilder mUrlBuilder;
|
||||
private final Runnable showNormalFabsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mFabLayout != null) {
|
||||
mFabLayout.setSecondaryFabVisibilityAt(0, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(2, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(3, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(4, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(5, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(6, false);
|
||||
}
|
||||
updateJumpFab();
|
||||
}
|
||||
};
|
||||
private int mFavLocalCount = 0;
|
||||
private int mFavCountSum = 0;
|
||||
private boolean mHasFirstRefresh;
|
||||
@@ -180,7 +175,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
mClient = EhApplication.getEhClient(context);
|
||||
mClient = EhApplication.getEhClient();
|
||||
mFavCatArray = Settings.getFavCat();
|
||||
mFavCountArray = Settings.getFavCount();
|
||||
mFavLocalCount = Settings.getFavLocalCount();
|
||||
@@ -238,17 +233,22 @@ public class FavoritesScene extends BaseScene implements
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_favorites, container, false);
|
||||
mContentLayout = view.findViewById(R.id.content_layout);
|
||||
MainActivity activity = getMainActivity();
|
||||
AssertUtils.assertNotNull(activity);
|
||||
setOnApplySearch((query) -> {
|
||||
onApplySearch(query);
|
||||
return null;
|
||||
});
|
||||
mDrawerLayout = (DrawerLayout) ViewUtils.$$(activity, R.id.draw_view);
|
||||
mRecyclerView = mContentLayout.getRecyclerView();
|
||||
FastScroller fastScroller = mContentLayout.getFastScroller();
|
||||
mSearchBar = (SearchBar) ViewUtils.$$(view, R.id.search_bar);
|
||||
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
container.addView(mFabLayout);
|
||||
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
|
||||
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
|
||||
mFabLayout
|
||||
@@ -274,13 +274,9 @@ public class FavoritesScene extends BaseScene implements
|
||||
fastScroller.setPadding(fastScroller.getPaddingLeft(), fastScroller.getPaddingTop() + paddingTopSB,
|
||||
fastScroller.getPaddingRight(), fastScroller.getPaddingBottom());
|
||||
|
||||
mLeftDrawable = new DrawerArrowDrawable(context, ResourcesKt.resolveColor(getTheme(), android.R.attr.colorControlNormal));
|
||||
mSearchBar.setLeftDrawable(mLeftDrawable);
|
||||
mSearchBar.setRightDrawable(ContextCompat.getDrawable(context, R.drawable.v_magnify_x24));
|
||||
mSearchBar.setHelper(this);
|
||||
mSearchBar.setAllowEmptySearch(false);
|
||||
setAllowEmptySearch(false);
|
||||
updateSearchBar();
|
||||
mSearchBarMover = new SearchBarMover(this, mSearchBar, mRecyclerView);
|
||||
updateJumpFab();
|
||||
|
||||
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
|
||||
mActionFabDrawable = new AddDeleteDrawable(context, colorID);
|
||||
@@ -311,7 +307,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
// They changed, call it
|
||||
private void updateSearchBar() {
|
||||
Context context = getContext();
|
||||
if (null == context || null == mUrlBuilder || null == mSearchBar || null == mFavCatArray) {
|
||||
if (null == context || null == mUrlBuilder || null == mFavCatArray) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -328,21 +324,19 @@ public class FavoritesScene extends BaseScene implements
|
||||
String keyword = mUrlBuilder.getKeyword();
|
||||
if (TextUtils.isEmpty(keyword)) {
|
||||
if (!ObjectUtils.equal(favCatName, mOldFavCat)) {
|
||||
mSearchBar.setTitle(getString(R.string.favorites_title, favCatName));
|
||||
setSearchBarHint(getString(R.string.favorites_title, favCatName));
|
||||
}
|
||||
} else {
|
||||
if (!ObjectUtils.equal(favCatName, mOldFavCat) || !ObjectUtils.equal(keyword, mOldKeyword)) {
|
||||
mSearchBar.setTitle(getString(R.string.favorites_title_2, favCatName, keyword));
|
||||
setSearchBarHint(getString(R.string.favorites_title_2, favCatName, keyword));
|
||||
}
|
||||
}
|
||||
|
||||
// Update hint
|
||||
if (!ObjectUtils.equal(favCatName, mOldFavCat)) {
|
||||
mSearchBar.setEditTextHint(getString(R.string.favorites_search_bar_hint, favCatName));
|
||||
setEditTextHint(getString(R.string.favorites_search_bar_hint, favCatName));
|
||||
}
|
||||
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, favCat != FavListUrlBuilder.FAV_CAT_LOCAL);
|
||||
|
||||
mOldFavCat = favCatName;
|
||||
mOldKeyword = keyword;
|
||||
|
||||
@@ -350,6 +344,13 @@ public class FavoritesScene extends BaseScene implements
|
||||
Settings.putRecentFavCat(mUrlBuilder.getFavCat());
|
||||
}
|
||||
|
||||
// Hide jump fab on local fav cat
|
||||
private void updateJumpFab() {
|
||||
if (mFabLayout != null && mUrlBuilder != null) {
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, mUrlBuilder.getFavCat() != FavListUrlBuilder.FAV_CAT_LOCAL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -365,17 +366,13 @@ public class FavoritesScene extends BaseScene implements
|
||||
mRecyclerView = null;
|
||||
}
|
||||
if (null != mFabLayout) {
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
removeAboveSnackView(mFabLayout);
|
||||
mFabLayout = null;
|
||||
}
|
||||
|
||||
mAdapter = null;
|
||||
|
||||
mSearchBar = null;
|
||||
|
||||
mSearchBarMover = null;
|
||||
mLeftDrawable = null;
|
||||
|
||||
mOldFavCat = null;
|
||||
mOldKeyword = null;
|
||||
}
|
||||
@@ -431,29 +428,27 @@ public class FavoritesScene extends BaseScene implements
|
||||
mFabLayout.toggle();
|
||||
} else if (mSearchMode) {
|
||||
exitSearchMode(true);
|
||||
} else if (isSearchViewShown()) {
|
||||
hideSearchView();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(FastScroller.OnDragHandlerListener.class)
|
||||
public void onStartDragHandler() {
|
||||
// Lock right drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(FastScroller.OnDragHandlerListener.class)
|
||||
public void onEndDragHandler() {
|
||||
// Restore right drawer
|
||||
if (null != mRecyclerView && !mRecyclerView.isInCustomChoice()) {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
|
||||
}
|
||||
|
||||
if (mSearchBarMover != null) {
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
}
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
public boolean onItemClick(View view, int position) {
|
||||
@@ -485,6 +480,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
mUrlBuilder.setKeyword(null);
|
||||
mUrlBuilder.setFavCat(newFavCat);
|
||||
updateSearchBar();
|
||||
updateJumpFab();
|
||||
mHelper.refresh();
|
||||
|
||||
closeDrawer(Gravity.RIGHT);
|
||||
@@ -518,78 +514,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBarMover.Helper.class)
|
||||
public boolean isValidView(RecyclerView recyclerView) {
|
||||
return recyclerView == mRecyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBarMover.Helper.class)
|
||||
public RecyclerView getValidRecyclerView() {
|
||||
return mRecyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBarMover.Helper.class)
|
||||
public boolean forceShowSearchBar() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBar.Helper.class)
|
||||
public void onClickTitle() {
|
||||
// Skip if in search mode
|
||||
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mSearchMode) {
|
||||
enterSearchMode(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBar.Helper.class)
|
||||
public void onClickLeftIcon() {
|
||||
// Skip if in search mode
|
||||
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSearchMode) {
|
||||
exitSearchMode(true);
|
||||
} else {
|
||||
toggleDrawer(Gravity.LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBar.Helper.class)
|
||||
public void onClickRightIcon() {
|
||||
// Skip if in search mode
|
||||
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mSearchMode) {
|
||||
enterSearchMode(true);
|
||||
} else if (mSearchBar != null) {
|
||||
if (mSearchBar.getEditText().length() == 0) {
|
||||
exitSearchMode(true);
|
||||
} else {
|
||||
mSearchBar.applySearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBar.Helper.class)
|
||||
public void onSearchEditTextClick() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBar.Helper.class)
|
||||
public void onApplySearch(String query) {
|
||||
// Skip if in search mode
|
||||
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
|
||||
@@ -612,12 +536,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
mHelper.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBar.Helper.class)
|
||||
public void onSearchEditTextBackPressed() {
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpand(boolean expanded) {
|
||||
if (expanded) {
|
||||
@@ -628,7 +546,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(FabLayout.OnClickFabListener.class)
|
||||
public void onClickPrimaryFab(FabLayout view, FloatingActionButton fab) {
|
||||
if (mRecyclerView != null && mFabLayout != null) {
|
||||
if (mRecyclerView.isInCustomChoice()) {
|
||||
@@ -645,9 +562,23 @@ public class FavoritesScene extends BaseScene implements
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime local = LocalDateTime.of(2007, 3, 21, 0, 0);
|
||||
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
|
||||
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
|
||||
|
||||
ArrayList listValidators = new ArrayList<>();
|
||||
listValidators.add(DateValidatorPointForward.from(fromDate));
|
||||
listValidators.add(DateValidatorPointBackward.before(toDate));
|
||||
|
||||
var constraintsBuilder = new CalendarConstraints.Builder()
|
||||
.setStart(fromDate)
|
||||
.setEnd(toDate)
|
||||
.setValidator(CompositeDateValidator.allOf(listValidators));
|
||||
|
||||
var datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setCalendarConstraints(constraintsBuilder.build())
|
||||
.setTitleText(R.string.go_to)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.setSelection(toDate)
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
@@ -656,7 +587,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(FabLayout.OnClickFabListener.class)
|
||||
public void onClickSecondaryFab(FabLayout view, FloatingActionButton fab, int position) {
|
||||
Context context = getContext();
|
||||
if (null == context || null == mRecyclerView || null == mHelper) {
|
||||
@@ -693,7 +623,11 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
switch (position) {
|
||||
case 3: { // Download
|
||||
case 3: { // Check all
|
||||
mRecyclerView.checkAll();
|
||||
break;
|
||||
}
|
||||
case 4: { // Download
|
||||
Activity activity = getMainActivity();
|
||||
if (activity != null) {
|
||||
CommonOperations.startDownload(getMainActivity(), mModifyGiList, false);
|
||||
@@ -704,7 +638,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: { // Delete
|
||||
case 5: { // Delete
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper();
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.delete_favorites_dialog_title)
|
||||
@@ -714,7 +648,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
case 5: { // Move
|
||||
case 6: { // Move
|
||||
MoveDialogHelper helper = new MoveDialogHelper();
|
||||
// First is local favorite, the other 10 is cloud favorite
|
||||
String[] array = new String[11];
|
||||
@@ -746,11 +680,11 @@ public class FavoritesScene extends BaseScene implements
|
||||
mFabLayout.setSecondaryFabVisibilityAt(3, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(4, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(5, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(6, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(EasyRecyclerView.CustomChoiceListener.class)
|
||||
public void onIntoCustomChoice(EasyRecyclerView view) {
|
||||
if (mFabLayout != null) {
|
||||
showSelectionFabs();
|
||||
@@ -767,7 +701,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(EasyRecyclerView.CustomChoiceListener.class)
|
||||
public void onOutOfCustomChoice(EasyRecyclerView view) {
|
||||
if (mFabLayout != null) {
|
||||
showNormalFabs();
|
||||
@@ -783,7 +716,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(EasyRecyclerView.CustomChoiceListener.class)
|
||||
public void onItemCheckedStateChanged(EasyRecyclerView view, int position, long id, boolean checked) {
|
||||
|
||||
if (view.getCheckedItemCount() == 0) {
|
||||
@@ -792,28 +724,23 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
private void enterSearchMode(boolean animation) {
|
||||
if (mSearchMode || mSearchBar == null || mSearchBarMover == null || mLeftDrawable == null) {
|
||||
if (mSearchMode) {
|
||||
return;
|
||||
}
|
||||
mSearchMode = true;
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
|
||||
mSearchBarMover.returnSearchBarPosition(animation);
|
||||
mLeftDrawable.setArrow(ANIMATE_TIME);
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
private void exitSearchMode(boolean animation) {
|
||||
if (!mSearchMode || mSearchBar == null || mSearchBarMover == null || mLeftDrawable == null) {
|
||||
if (!mSearchMode) {
|
||||
return;
|
||||
}
|
||||
mSearchMode = false;
|
||||
mSearchBar.setState(SearchBar.STATE_NORMAL, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
mLeftDrawable.setMenu(ANIMATE_TIME);
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
private void onGetFavoritesSuccess(FavoritesParser.Result result, int taskId) {
|
||||
if (mHelper != null && mSearchBarMover != null &&
|
||||
mHelper.isCurrentTask(taskId)) {
|
||||
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
|
||||
|
||||
if (mFavCatArray != null) {
|
||||
System.arraycopy(result.catArray, 0, mFavCatArray, 0, 10);
|
||||
@@ -860,8 +787,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
private void onGetFavoritesFailure(Exception e, int taskId) {
|
||||
if (mHelper != null && mSearchBarMover != null &&
|
||||
mHelper.isCurrentTask(taskId)) {
|
||||
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
|
||||
mHelper.onGetException(taskId, e);
|
||||
}
|
||||
}
|
||||
@@ -1297,9 +1223,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
|
||||
@Override
|
||||
public void onShowView(View hiddenView, View shownView) {
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1308,11 +1232,9 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollToPosition(int postion) {
|
||||
if (0 == postion) {
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
protected void onScrollToPosition(int position) {
|
||||
if (0 == position) {
|
||||
showSearchBar();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
|
||||
|
||||
setType(type);
|
||||
|
||||
mDownloadManager = EhApplication.getDownloadManager(inflater.getContext());
|
||||
mDownloadManager = EhApplication.getDownloadManager();
|
||||
}
|
||||
|
||||
private void adjustPaddings() {
|
||||
@@ -126,7 +126,7 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
|
||||
break;
|
||||
}
|
||||
case GalleryAdapter.TYPE_GRID: {
|
||||
int columnWidth = mResources.getDimensionPixelOffset(Settings.getThumbSizeResId());
|
||||
int columnWidth = Settings.getThumbSize();
|
||||
mLayoutManager.setColumnSize(columnWidth);
|
||||
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_SUITABLE_SIZE);
|
||||
if (null != mListDecoration) {
|
||||
|
||||
@@ -278,7 +278,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey, mGalleryDetail.gid, mGalleryDetail.token, id, vote)
|
||||
.setCallback(new VoteCommentListener(context,
|
||||
activity.getStageId(), getTag()));
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@@ -387,8 +387,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
}
|
||||
|
||||
RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
|
||||
if (holder instanceof ActualCommentHolder) {
|
||||
ActualCommentHolder commentHolder = (ActualCommentHolder) holder;
|
||||
if (holder instanceof ActualCommentHolder commentHolder) {
|
||||
ClickableSpan span = commentHolder.comment.getCurrentSpan();
|
||||
commentHolder.comment.clearCurrentSpan();
|
||||
|
||||
@@ -409,7 +408,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
|
||||
.setArgs(url)
|
||||
.setCallback(new RefreshCommentListener(activity, activity.getStageId(), getTag()));
|
||||
EhApplication.getEhClient(activity).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,7 +591,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
.setArgs(url, comment, mCommentId != 0 ? Long.toString(mCommentId) : null)
|
||||
.setCallback(new CommentGalleryListener(context,
|
||||
activity.getStageId(), getTag(), mCommentId));
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
hideSoftInput();
|
||||
hideEditPanel(true);
|
||||
}
|
||||
@@ -710,7 +709,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
|
||||
.setArgs(url)
|
||||
.setCallback(new RefreshCommentListener(activity, activity.getStageId(), getTag()));
|
||||
EhApplication.getEhClient(activity).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -859,8 +858,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
}
|
||||
|
||||
private CharSequence generateComment(Context context, ObservedTextView textView, GalleryComment comment) {
|
||||
Spanned sp = Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY, new URLImageGetter(textView,
|
||||
EhApplication.getConaco(context)), null);
|
||||
Spanned sp = Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY, new URLImageGetter(textView), null);
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder(sp);
|
||||
|
||||
|
||||
@@ -41,15 +41,12 @@ import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewAnimationUtils;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RatingBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -68,7 +65,6 @@ import com.google.android.material.snackbar.Snackbar;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.CheckBoxDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
@@ -119,22 +115,20 @@ import com.hippo.widget.ObservedTextView;
|
||||
import com.hippo.widget.SimpleGridAutoSpanLayout;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import coil.Coil;
|
||||
import okhttp3.HttpUrl;
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
public class GalleryDetailScene extends CollapsingToolbarScene implements View.OnClickListener,
|
||||
com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener,
|
||||
View.OnLongClickListener {
|
||||
@@ -284,6 +278,21 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void deleteFileAsync(UniFile... files) {
|
||||
//noinspection deprecation
|
||||
new AsyncTask<UniFile, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(UniFile... params) {
|
||||
for (UniFile file : params) {
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance(), files);
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private int getRatingText(float rating) {
|
||||
int resId;
|
||||
@@ -403,6 +412,11 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
}
|
||||
}
|
||||
|
||||
// Judging by the uploader to exclude the cooldown period
|
||||
private boolean getDisowned() {
|
||||
return getUploader().equals("(Disowned)");
|
||||
}
|
||||
|
||||
// -1 for error
|
||||
private int getCategory() {
|
||||
if (mGalleryDetail != null) {
|
||||
@@ -424,21 +438,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteFileAsync(UniFile... files) {
|
||||
//noinspection deprecation
|
||||
new AsyncTask<UniFile, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(UniFile... params) {
|
||||
for (UniFile file : params) {
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance(), files);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -562,7 +561,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
if (gid != -1) {
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
mDownloadState = EhApplication.getDownloadManager(context).getDownloadState(gid);
|
||||
mDownloadState = EhApplication.getDownloadManager().getDownloadState(gid);
|
||||
} else {
|
||||
mDownloadState = DownloadInfo.STATE_INVALID;
|
||||
}
|
||||
@@ -639,8 +638,12 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
mNoTags = (TextView) ViewUtils.$$(mTags, R.id.no_tags);
|
||||
|
||||
mComments = (LinearLayout) ViewUtils.$$(belowHeader, R.id.comments);
|
||||
mCommentsText = (TextView) ViewUtils.$$(mComments, R.id.comments_text);
|
||||
mComments.setOnClickListener(this);
|
||||
if (Settings.getShowComments()) {
|
||||
mCommentsText = (TextView) ViewUtils.$$(mComments, R.id.comments_text);
|
||||
mComments.setOnClickListener(this);
|
||||
} else {
|
||||
mComments.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mPreviews = ViewUtils.$$(belowHeader, R.id.previews);
|
||||
mGridLayout = (SimpleGridAutoSpanLayout) ViewUtils.$$(mPreviews, R.id.grid_layout);
|
||||
@@ -666,7 +669,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
adjustViewVisibility(STATE_FAILED, false);
|
||||
}
|
||||
|
||||
EhApplication.getDownloadManager(context).addDownloadInfoListener(this);
|
||||
EhApplication.getDownloadManager().addDownloadInfoListener(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -677,7 +680,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
EhApplication.getDownloadManager(context).removeDownloadInfoListener(this);
|
||||
EhApplication.getDownloadManager().removeDownloadInfoListener(this);
|
||||
|
||||
mTip = null;
|
||||
mViewTransition = null;
|
||||
@@ -740,7 +743,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
}
|
||||
|
||||
// Get from cache
|
||||
mGalleryDetail = EhApplication.getGalleryDetailCache(context).get(gid);
|
||||
mGalleryDetail = EhApplication.getGalleryDetailCache().get(gid);
|
||||
if (mGalleryDetail != null) {
|
||||
return true;
|
||||
}
|
||||
@@ -770,7 +773,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
|
||||
.setArgs(url)
|
||||
.setCallback(callback);
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -881,7 +884,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
|
||||
Resources resources = getResources();
|
||||
|
||||
mThumb.load(EhCacheKeyFactory.getThumbKey(gd.gid), gd.thumb);
|
||||
mThumb.load(EhCacheKeyFactory.getThumbKey(gd.gid), gd.thumb, true);
|
||||
setTitle(EhUtils.getSuitableTitle(gd));
|
||||
mUploader.setText(gd.uploader);
|
||||
mUploader.setAlpha(gd.disowned ? .5f : 1f);
|
||||
@@ -1002,7 +1005,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
ObservedTextView c = v.findViewById(R.id.comment);
|
||||
c.setMaxLines(5);
|
||||
c.setText(Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY,
|
||||
new URLImageGetter(c, EhApplication.getConaco(context)), null));
|
||||
new URLImageGetter(c), null));
|
||||
v.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
}
|
||||
@@ -1011,6 +1014,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
private void bindPreviews(GalleryDetail gd) {
|
||||
LayoutInflater inflater = getLayoutInflater();
|
||||
Resources resources = getResourcesOrNull();
|
||||
int previewNum = Settings.getPreviewNum();
|
||||
if (null == inflater || null == resources || null == mGridLayout || null == mPreviewText) {
|
||||
return;
|
||||
}
|
||||
@@ -1020,16 +1024,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
if (gd.previewPages <= 0 || previewSet == null || previewSet.size() == 0) {
|
||||
mPreviewText.setText(R.string.no_previews);
|
||||
return;
|
||||
} else if (gd.previewPages == 1 && previewSet.size() <= 60) {
|
||||
} else if (gd.previewPages == 1 && previewSet.size() <= previewNum) {
|
||||
mPreviewText.setText(R.string.no_more_previews);
|
||||
} else {
|
||||
mPreviewText.setText(R.string.more_previews);
|
||||
}
|
||||
|
||||
int columnWidth = resources.getDimensionPixelOffset(Settings.getThumbSizeResId());
|
||||
int columnWidth = Settings.getThumbSize();
|
||||
mGridLayout.setColumnSize(columnWidth);
|
||||
mGridLayout.setStrategy(SimpleGridAutoSpanLayout.STRATEGY_SUITABLE_SIZE);
|
||||
int size = Math.min(60, previewSet.size());
|
||||
int size = Math.min(previewNum, previewSet.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
View view = inflater.inflate(R.layout.item_gallery_preview, mGridLayout, false);
|
||||
mGridLayout.addView(view);
|
||||
@@ -1095,26 +1099,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
if (-1L == gid) {
|
||||
return;
|
||||
}
|
||||
File temp = AppConfig.createTempFile();
|
||||
if (null == temp) {
|
||||
return;
|
||||
}
|
||||
BeerBelly beerBelly = EhApplication.getConaco(context).getBeerBelly();
|
||||
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = new FileOutputStream(temp);
|
||||
if (beerBelly.pullFromDiskCache(EhCacheKeyFactory.getThumbKey(gid), os)) {
|
||||
ListUrlBuilder lub = new ListUrlBuilder();
|
||||
lub.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
||||
lub.setImagePath(temp.getPath());
|
||||
lub.setUseSimilarityScan(true);
|
||||
GalleryListScene.startScene(this, lub);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// Ignore
|
||||
} finally {
|
||||
IOUtils.closeQuietly(os);
|
||||
var path = Coil.imageLoader(context).getDiskCache().get(EhCacheKeyFactory.getThumbKey(gid)).getData();
|
||||
ListUrlBuilder lub = new ListUrlBuilder();
|
||||
lub.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
||||
lub.setImagePath(path.toString());
|
||||
lub.setUseSimilarityScan(true);
|
||||
GalleryListScene.startScene(this, lub);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1132,7 +1126,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
}
|
||||
} else if (mUploader == v) {
|
||||
String uploader = getUploader();
|
||||
if (TextUtils.isEmpty(uploader)) {
|
||||
if (TextUtils.isEmpty(uploader) || getDisowned()) {
|
||||
return;
|
||||
}
|
||||
ListUrlBuilder lub = new ListUrlBuilder();
|
||||
@@ -1141,7 +1135,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
GalleryListScene.startScene(this, lub);
|
||||
} else if (mCategory == v) {
|
||||
int category = getCategory();
|
||||
if (category == -1) {
|
||||
if (category == EhUtils.NONE || category == EhUtils.PRIVATE || category == EhUtils.UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
ListUrlBuilder lub = new ListUrlBuilder();
|
||||
@@ -1150,7 +1144,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
} else if (mDownload == v) {
|
||||
GalleryInfo galleryInfo = getGalleryInfo();
|
||||
if (galleryInfo != null) {
|
||||
if (EhApplication.getDownloadManager(context).getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
|
||||
if (EhApplication.getDownloadManager().getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
|
||||
CommonOperations.startDownload(activity, galleryInfo, false);
|
||||
} else {
|
||||
CheckBoxDialogBuilder builder = new CheckBoxDialogBuilder(context,
|
||||
@@ -1158,7 +1152,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
getString(R.string.download_remove_dialog_check_text),
|
||||
Settings.getRemoveImageFiles());
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper(
|
||||
EhApplication.getDownloadManager(context), galleryInfo, builder);
|
||||
EhApplication.getDownloadManager(), galleryInfo, builder);
|
||||
builder.setTitle(R.string.download_remove_dialog_title)
|
||||
.setPositiveButton(android.R.string.ok, helper)
|
||||
.show();
|
||||
@@ -1294,8 +1288,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
}
|
||||
} else {
|
||||
Object o = v.getTag(R.id.tag);
|
||||
if (o instanceof String) {
|
||||
String tag = (String) o;
|
||||
if (o instanceof String tag) {
|
||||
ListUrlBuilder lub = new ListUrlBuilder();
|
||||
lub.setMode(ListUrlBuilder.MODE_TAG);
|
||||
lub.setKeyword(tag);
|
||||
@@ -1427,7 +1420,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey, mGalleryDetail.gid, mGalleryDetail.token, tag, vote)
|
||||
.setCallback(new VoteTagListener(context,
|
||||
activity.getStageId(), getTag()));
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1438,6 +1431,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
}
|
||||
|
||||
if (mUploader == v) {
|
||||
if (TextUtils.isEmpty(getUploader()) || getDisowned()) {
|
||||
return false;
|
||||
}
|
||||
showFilterUploaderDialog();
|
||||
} else if (mDownload == v) {
|
||||
GalleryInfo galleryInfo = getGalleryInfo();
|
||||
@@ -1530,7 +1526,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
return;
|
||||
}
|
||||
|
||||
int downloadState = EhApplication.getDownloadManager(context).getDownloadState(gid);
|
||||
int downloadState = EhApplication.getDownloadManager().getDownloadState(gid);
|
||||
if (downloadState == mDownloadState) {
|
||||
return;
|
||||
}
|
||||
@@ -1648,7 +1644,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
getApplication().removeGlobalStuff(this);
|
||||
|
||||
// Put gallery detail to cache
|
||||
EhApplication.getGalleryDetailCache(getApplication()).put(result.gid, result);
|
||||
EhApplication.getGalleryDetailCache().put(result.gid, result);
|
||||
|
||||
// Add history
|
||||
EhDB.putHistoryInfo(result);
|
||||
@@ -1728,7 +1724,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
scene.onRateGallerySuccess(result);
|
||||
} else {
|
||||
// Update rating in cache
|
||||
GalleryDetail gd = EhApplication.getGalleryDetailCache(getApplication()).get(mGid);
|
||||
GalleryDetail gd = EhApplication.getGalleryDetailCache().get(mGid);
|
||||
if (gd != null) {
|
||||
gd.rating = result.rating;
|
||||
gd.ratingCount = result.ratingCount;
|
||||
@@ -1857,7 +1853,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
mRequest = new EhRequest().setMethod(EhClient.METHOD_ARCHIVE_LIST)
|
||||
.setArgs(url, mGid, mToken)
|
||||
.setCallback(this);
|
||||
EhApplication.getEhClient(context).execute(mRequest);
|
||||
EhApplication.getEhClient().execute(mRequest);
|
||||
} else {
|
||||
bind(mArchiveList);
|
||||
}
|
||||
@@ -1896,7 +1892,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
request.setMethod(EhClient.METHOD_DOWNLOAD_ARCHIVE);
|
||||
request.setArgs(mGalleryDetail.gid, mGalleryDetail.token, mArchiveFormParamOr, res);
|
||||
request.setCallback(new DownloadArchiveListener(context, activity.getStageId(), getTag()));
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
}
|
||||
|
||||
if (mDialog != null) {
|
||||
@@ -1974,7 +1970,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
mRequest = new EhRequest().setMethod(EhClient.METHOD_GET_TORRENT_LIST)
|
||||
.setArgs(url, mGid, mToken)
|
||||
.setCallback(this);
|
||||
EhApplication.getEhClient(context).execute(mRequest);
|
||||
EhApplication.getEhClient().execute(mRequest);
|
||||
} else {
|
||||
bind(mTorrentList);
|
||||
}
|
||||
@@ -2015,7 +2011,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
FileUtils.sanitizeFilename(name + ".torrent"));
|
||||
r.allowScanningByMediaScanner();
|
||||
r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
r.addRequestHeader("Cookie", EhApplication.getEhCookieStore(context).getCookieHeader(HttpUrl.get(url)));
|
||||
r.addRequestHeader("Cookie", EhApplication.getEhCookieStore().getCookieHeader(HttpUrl.get(url)));
|
||||
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
if (dm != null) {
|
||||
try {
|
||||
@@ -2110,10 +2106,11 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
mGalleryDetail.gid, mGalleryDetail.token, mRatingBar.getRating())
|
||||
.setCallback(new RateGalleryListener(context,
|
||||
activity.getStageId(), getTag(), mGalleryDetail.gid));
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
}
|
||||
}
|
||||
private class DeleteDialogHelper implements DialogInterface.OnClickListener{
|
||||
|
||||
private class DeleteDialogHelper implements DialogInterface.OnClickListener {
|
||||
private final com.hippo.ehviewer.download.DownloadManager mDownloadManager;
|
||||
private final GalleryInfo mGalleryInfo;
|
||||
private final CheckBoxDialogBuilder mBuilder;
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.text.TextUtils;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
@@ -55,12 +56,15 @@ import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.datepicker.CalendarConstraints;
|
||||
import com.google.android.material.datepicker.CompositeDateValidator;
|
||||
import com.google.android.material.datepicker.DateValidatorPointBackward;
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.drawable.AddDeleteDrawable;
|
||||
import com.hippo.drawable.DrawerArrowDrawable;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.FastScroller;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
@@ -88,7 +92,6 @@ import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
|
||||
import com.hippo.ehviewer.widget.GalleryInfoContentHelper;
|
||||
import com.hippo.ehviewer.widget.SearchBar;
|
||||
import com.hippo.ehviewer.widget.SearchLayout;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
@@ -98,7 +101,6 @@ import com.hippo.view.BringOutTransition;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.widget.ContentLayout;
|
||||
import com.hippo.widget.FabLayout;
|
||||
import com.hippo.widget.SearchBarMover;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
@@ -109,6 +111,9 @@ import com.hippo.yorozuya.ViewUtils;
|
||||
import java.io.File;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -116,9 +121,9 @@ import java.util.List;
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
public final class GalleryListScene extends BaseScene
|
||||
implements SearchBar.Helper, SearchBar.OnStateChangeListener, FastScroller.OnDragHandlerListener,
|
||||
SearchLayout.Helper, SearchBarMover.Helper, View.OnClickListener, FabLayout.OnClickFabListener,
|
||||
public final class GalleryListScene extends SearchBarScene
|
||||
implements FastScroller.OnDragHandlerListener,
|
||||
SearchLayout.Helper, FabLayout.OnClickFabListener,
|
||||
FabLayout.OnExpandListener {
|
||||
|
||||
public final static String KEY_ACTION = "action";
|
||||
@@ -130,12 +135,13 @@ public final class GalleryListScene extends BaseScene
|
||||
public final static String KEY_LIST_URL_BUILDER = "list_url_builder";
|
||||
public final static String KEY_HAS_FIRST_REFRESH = "has_first_refresh";
|
||||
public final static String KEY_STATE = "state";
|
||||
private static final int BACK_PRESSED_INTERVAL = 2000;
|
||||
private final static int STATE_NORMAL = 0;
|
||||
private final static int STATE_SIMPLE_SEARCH = 1;
|
||||
private final static int STATE_SEARCH = 2;
|
||||
private final static int STATE_SEARCH_SHOW_LIST = 3;
|
||||
private static final long ANIMATE_TIME = 300L;
|
||||
// Double click back exit
|
||||
private final long mPressBackTime = 0;
|
||||
/*---------------
|
||||
Whole life cycle
|
||||
---------------*/
|
||||
@@ -156,8 +162,6 @@ public final class GalleryListScene extends BaseScene
|
||||
new ActivityResultContracts.PickVisualMedia(),
|
||||
result -> mSearchLayout.setImageUri(result));
|
||||
@Nullable
|
||||
private SearchBar mSearchBar;
|
||||
@Nullable
|
||||
private View mSearchFab;
|
||||
@Nullable
|
||||
private final Animator.AnimatorListener mSearchFabAnimatorListener = new SimpleAnimatorListener() {
|
||||
@@ -188,12 +192,6 @@ public final class GalleryListScene extends BaseScene
|
||||
@Nullable
|
||||
private GalleryListHelper mHelper;
|
||||
@Nullable
|
||||
private DrawerArrowDrawable mLeftDrawable;
|
||||
@Nullable
|
||||
private AddDeleteDrawable mRightDrawable;
|
||||
@Nullable
|
||||
private SearchBarMover mSearchBarMover;
|
||||
@Nullable
|
||||
private AddDeleteDrawable mActionFabDrawable;
|
||||
@Nullable
|
||||
private List<QuickSearch> mQuickSearchList;
|
||||
@@ -216,8 +214,6 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
};
|
||||
// Double click back exit
|
||||
private final long mPressBackTime = 0;
|
||||
private boolean mHasFirstRefresh = false;
|
||||
private int mNavCheckedId = 0;
|
||||
private DownloadManager mDownloadManager;
|
||||
@@ -271,6 +267,11 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuResId() {
|
||||
return R.menu.scene_gallery_list_searchbar_menu;
|
||||
}
|
||||
|
||||
public static void startScene(SceneFragment scene, ListUrlBuilder lub) {
|
||||
scene.startScene(getStartAnnouncer(lub));
|
||||
}
|
||||
@@ -321,9 +322,7 @@ public final class GalleryListScene extends BaseScene
|
||||
mHelper.refresh();
|
||||
}
|
||||
setState(STATE_NORMAL);
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -332,9 +331,9 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
mClient = EhApplication.getEhClient(context);
|
||||
mDownloadManager = EhApplication.getDownloadManager(context);
|
||||
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context);
|
||||
mClient = EhApplication.getEhClient();
|
||||
mDownloadManager = EhApplication.getDownloadManager();
|
||||
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter();
|
||||
|
||||
mDownloadInfoListener = new DownloadManager.DownloadInfoListener() {
|
||||
@Override
|
||||
@@ -434,14 +433,14 @@ public final class GalleryListScene extends BaseScene
|
||||
mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener);
|
||||
}
|
||||
|
||||
private void setSearchBarHint(SearchBar searchBar) {
|
||||
searchBar.setEditTextHint(getString(EhUrl.SITE_EX == Settings.getGallerySite() ?
|
||||
private void setSearchBarHint() {
|
||||
setEditTextHint(getString(EhUrl.SITE_EX == Settings.getGallerySite() ?
|
||||
R.string.gallery_list_search_bar_hint_exhentai :
|
||||
R.string.gallery_list_search_bar_hint_e_hentai));
|
||||
}
|
||||
|
||||
private void setSearchBarSuggestionProvider(SearchBar searchBar) {
|
||||
searchBar.setSuggestionProvider(text -> {
|
||||
private void setSearchBarSuggestionProvider() {
|
||||
setSuggestionProvider(text -> {
|
||||
GalleryDetailUrlParser.Result result1 = GalleryDetailUrlParser.parse(text, false);
|
||||
if (result1 != null) {
|
||||
return Collections.singletonList(new GalleryDetailUrlSuggestion(result1.gid, result1.token));
|
||||
@@ -501,12 +500,11 @@ public final class GalleryListScene extends BaseScene
|
||||
: R.id.search_normal_search);
|
||||
|
||||
// Update search edit text
|
||||
if (!TextUtils.isEmpty(keyword) && null != mSearchBar && !mIsTopList) {
|
||||
if (!TextUtils.isEmpty(keyword) && !mIsTopList) {
|
||||
if (builder.getMode() == ListUrlBuilder.MODE_TAG) {
|
||||
keyword = wrapTagKeyword(keyword);
|
||||
}
|
||||
mSearchBar.setText(keyword);
|
||||
mSearchBar.cursorToEnd();
|
||||
setSearchBarText(keyword);
|
||||
}
|
||||
|
||||
// Update title
|
||||
@@ -514,9 +512,7 @@ public final class GalleryListScene extends BaseScene
|
||||
if (null == title) {
|
||||
title = resources.getString(R.string.search);
|
||||
}
|
||||
if (null != mSearchBar) {
|
||||
mSearchBar.setTitle(title);
|
||||
}
|
||||
setSearchBarHint(title);
|
||||
|
||||
// Update nav checked item
|
||||
int checkedItemId;
|
||||
@@ -539,8 +535,8 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_gallery_list, container, false);
|
||||
|
||||
Context context = getContext();
|
||||
@@ -555,14 +551,15 @@ public final class GalleryListScene extends BaseScene
|
||||
mRecyclerView = mContentLayout.getRecyclerView();
|
||||
FastScroller fastScroller = mContentLayout.getFastScroller();
|
||||
mSearchLayout = (SearchLayout) ViewUtils.$$(mainLayout, R.id.search_layout);
|
||||
mSearchBar = (SearchBar) ViewUtils.$$(mainLayout, R.id.search_bar);
|
||||
mFabLayout = (FabLayout) ViewUtils.$$(mainLayout, R.id.fab_layout);
|
||||
mSearchFab = ViewUtils.$$(mainLayout, R.id.search_fab);
|
||||
mSearchFab = ViewUtils.$$(container, R.id.search_fab);
|
||||
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
|
||||
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
|
||||
mFabLayout,
|
||||
(View) mSearchFab.getParent()
|
||||
));
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
container.addView(mFabLayout);
|
||||
|
||||
int paddingTopSB = resources.getDimensionPixelOffset(R.dimen.gallery_padding_top_search_bar);
|
||||
int paddingBottomFab = resources.getDimensionPixelOffset(R.dimen.gallery_padding_bottom_fab);
|
||||
@@ -583,14 +580,12 @@ public final class GalleryListScene extends BaseScene
|
||||
fastScroller.setPadding(fastScroller.getPaddingLeft(), fastScroller.getPaddingTop() + paddingTopSB,
|
||||
fastScroller.getPaddingRight(), fastScroller.getPaddingBottom());
|
||||
|
||||
mLeftDrawable = new DrawerArrowDrawable(context, ResourcesKt.resolveColor(getTheme(), android.R.attr.colorControlNormal));
|
||||
mRightDrawable = new AddDeleteDrawable(context, ResourcesKt.resolveColor(getTheme(), android.R.attr.colorControlNormal));
|
||||
mSearchBar.setLeftDrawable(mLeftDrawable);
|
||||
mSearchBar.setRightDrawable(mRightDrawable);
|
||||
mSearchBar.setHelper(this);
|
||||
mSearchBar.setOnStateChangeListener(this);
|
||||
setSearchBarHint(mSearchBar);
|
||||
setSearchBarSuggestionProvider(mSearchBar);
|
||||
setOnApplySearch((query) -> {
|
||||
onApplySearch(query);
|
||||
return null;
|
||||
});
|
||||
setSearchBarHint();
|
||||
setSearchBarSuggestionProvider();
|
||||
|
||||
mSearchLayout.setHelper(this);
|
||||
mSearchLayout.setPadding(mSearchLayout.getPaddingLeft(), mSearchLayout.getPaddingTop() + paddingTopSB,
|
||||
@@ -606,10 +601,12 @@ public final class GalleryListScene extends BaseScene
|
||||
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
|
||||
mActionFabDrawable = new AddDeleteDrawable(context, colorID);
|
||||
mFabLayout.getPrimaryFab().setImageDrawable(mActionFabDrawable);
|
||||
|
||||
mSearchFab.setOnClickListener(this);
|
||||
|
||||
mSearchBarMover = new SearchBarMover(this, mSearchBar, mRecyclerView, mSearchLayout);
|
||||
mSearchFab.setOnClickListener( (v) -> {
|
||||
if (STATE_NORMAL != mState) {
|
||||
onApplySearch();
|
||||
hideSoftInput();
|
||||
}
|
||||
});
|
||||
|
||||
// Update list url builder
|
||||
onUpdateUrlBuilder();
|
||||
@@ -632,10 +629,6 @@ public final class GalleryListScene extends BaseScene
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.cancelAnimation();
|
||||
mSearchBarMover = null;
|
||||
}
|
||||
if (null != mHelper) {
|
||||
mHelper.destroy();
|
||||
if (1 == mHelper.getShownViewIndex()) {
|
||||
@@ -647,17 +640,14 @@ public final class GalleryListScene extends BaseScene
|
||||
mRecyclerView = null;
|
||||
}
|
||||
if (null != mFabLayout) {
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
removeAboveSnackView(mFabLayout);
|
||||
mFabLayout = null;
|
||||
}
|
||||
|
||||
mAdapter = null;
|
||||
mSearchLayout = null;
|
||||
mSearchBar = null;
|
||||
mSearchFab = null;
|
||||
mViewTransition = null;
|
||||
mLeftDrawable = null;
|
||||
mRightDrawable = null;
|
||||
mActionFabDrawable = null;
|
||||
}
|
||||
|
||||
@@ -786,6 +776,8 @@ public final class GalleryListScene extends BaseScene
|
||||
if (null != mFabLayout && mFabLayout.isExpanded()) {
|
||||
return false;
|
||||
}
|
||||
if (isSearchViewShown())
|
||||
return false;
|
||||
switch (mState) {
|
||||
case STATE_NORMAL:
|
||||
return getStackIndex() == 0;
|
||||
@@ -799,6 +791,10 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (isSearchViewShown()) {
|
||||
hideSearchView();
|
||||
return;
|
||||
}
|
||||
if (null != mFabLayout && mFabLayout.isExpanded()) {
|
||||
mFabLayout.setExpanded(false);
|
||||
return;
|
||||
@@ -839,14 +835,6 @@ public final class GalleryListScene extends BaseScene
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (STATE_NORMAL != mState && null != mSearchBar) {
|
||||
mSearchBar.applySearch();
|
||||
hideSoftInput();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickPrimaryFab(FabLayout view, FloatingActionButton fab) {
|
||||
if (STATE_NORMAL == mState) {
|
||||
@@ -892,9 +880,23 @@ public final class GalleryListScene extends BaseScene
|
||||
dialog.dismiss();
|
||||
});
|
||||
} else {
|
||||
LocalDateTime local = LocalDateTime.of(2007, 3, 21, 0, 0);
|
||||
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
|
||||
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
|
||||
|
||||
ArrayList listValidators = new ArrayList<>();
|
||||
listValidators.add(DateValidatorPointForward.from(fromDate));
|
||||
listValidators.add(DateValidatorPointBackward.before(toDate));
|
||||
|
||||
var constraintsBuilder = new CalendarConstraints.Builder()
|
||||
.setStart(fromDate)
|
||||
.setEnd(toDate)
|
||||
.setValidator(CompositeDateValidator.allOf(listValidators));
|
||||
|
||||
var datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setCalendarConstraints(constraintsBuilder.build())
|
||||
.setTitleText(R.string.go_to)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.setSelection(toDate)
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
@@ -943,6 +945,18 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchViewExpanded() {
|
||||
super.onSearchViewExpanded();
|
||||
selectSearchFab(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchViewHidden() {
|
||||
super.onSearchViewHidden();
|
||||
if (mState == STATE_NORMAL) selectActionFab(true);
|
||||
}
|
||||
|
||||
public boolean onItemLongClick(int position) {
|
||||
final Context context = getContext();
|
||||
final MainActivity activity = getMainActivity();
|
||||
@@ -1009,7 +1023,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
break;
|
||||
case 3: // Move
|
||||
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager(context).getLabelList();
|
||||
List<DownloadLabel> labelRawList = EhApplication.getDownloadManager().getLabelList();
|
||||
List<String> labelList = new ArrayList<>(labelRawList.size() + 1);
|
||||
labelList.add(getString(R.string.default_download_label_name));
|
||||
for (int i = 0, n = labelRawList.size(); i < n; i++) {
|
||||
@@ -1136,8 +1150,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
private void setState(@State int state, boolean animation) {
|
||||
if (null == mSearchBar || null == mSearchBarMover ||
|
||||
null == mViewTransition || null == mSearchLayout) {
|
||||
if (null == mViewTransition || null == mSearchLayout) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1145,79 +1158,56 @@ public final class GalleryListScene extends BaseScene
|
||||
int oldState = mState;
|
||||
mState = state;
|
||||
|
||||
showSearchBar();
|
||||
onStateChange(state);
|
||||
switch (oldState) {
|
||||
case STATE_NORMAL:
|
||||
if (state == STATE_SIMPLE_SEARCH) {
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
selectSearchFab(animation);
|
||||
} else if (state == STATE_SEARCH) {
|
||||
mViewTransition.showView(1, animation);
|
||||
mSearchLayout.scrollSearchContainerToTop();
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
selectSearchFab(animation);
|
||||
} else if (state == STATE_SEARCH_SHOW_LIST) {
|
||||
mViewTransition.showView(1, animation);
|
||||
mSearchLayout.scrollSearchContainerToTop();
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
selectSearchFab(animation);
|
||||
}
|
||||
break;
|
||||
case STATE_SIMPLE_SEARCH:
|
||||
if (state == STATE_NORMAL) {
|
||||
mSearchBar.setState(SearchBar.STATE_NORMAL, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
selectActionFab(animation);
|
||||
} else if (state == STATE_SEARCH) {
|
||||
mViewTransition.showView(1, animation);
|
||||
mSearchLayout.scrollSearchContainerToTop();
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
} else if (state == STATE_SEARCH_SHOW_LIST) {
|
||||
mViewTransition.showView(1, animation);
|
||||
mSearchLayout.scrollSearchContainerToTop();
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
}
|
||||
break;
|
||||
case STATE_SEARCH:
|
||||
if (state == STATE_NORMAL) {
|
||||
mViewTransition.showView(0, animation);
|
||||
mSearchBar.setState(SearchBar.STATE_NORMAL, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
selectActionFab(animation);
|
||||
} else if (state == STATE_SIMPLE_SEARCH) {
|
||||
mViewTransition.showView(0, animation);
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
} else if (state == STATE_SEARCH_SHOW_LIST) {
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
}
|
||||
break;
|
||||
case STATE_SEARCH_SHOW_LIST:
|
||||
if (state == STATE_NORMAL) {
|
||||
mViewTransition.showView(0, animation);
|
||||
mSearchBar.setState(SearchBar.STATE_NORMAL, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
selectActionFab(animation);
|
||||
} else if (state == STATE_SIMPLE_SEARCH) {
|
||||
mViewTransition.showView(0, animation);
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
} else if (state == STATE_SEARCH) {
|
||||
mSearchBar.setState(SearchBar.STATE_SEARCH, animation);
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
((StageActivity)requireActivity()).updateBackPressCallBackStatus();
|
||||
((StageActivity) requireActivity()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickTitle() {
|
||||
if (mState == STATE_NORMAL) {
|
||||
setState(STATE_SIMPLE_SEARCH);
|
||||
@@ -1225,44 +1215,15 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickLeftIcon() {
|
||||
if (null == mSearchBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSearchBar.getState() == SearchBar.STATE_NORMAL) {
|
||||
toggleDrawer(Gravity.LEFT);
|
||||
public boolean onMenuItemClick(@NonNull MenuItem item) {
|
||||
if (mState == STATE_NORMAL) {
|
||||
setState(STATE_SEARCH);
|
||||
} else {
|
||||
setState(STATE_NORMAL);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickRightIcon() {
|
||||
if (null == mSearchBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSearchBar.getState() == SearchBar.STATE_NORMAL) {
|
||||
setState(STATE_SEARCH);
|
||||
} else {
|
||||
if (mSearchBar.getEditText().length() == 0) {
|
||||
setState(STATE_NORMAL);
|
||||
} else {
|
||||
// Clear
|
||||
mSearchBar.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchEditTextClick() {
|
||||
if (mState == STATE_SEARCH) {
|
||||
setState(STATE_SEARCH_SHOW_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplySearch(String query) {
|
||||
if (null == mUrlBuilder || null == mHelper || null == mSearchLayout) {
|
||||
return;
|
||||
@@ -1290,12 +1251,6 @@ public final class GalleryListScene extends BaseScene
|
||||
setState(STATE_NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchEditTextBackPressed() {
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveContent(Uri uri) {
|
||||
if (mSearchLayout == null || uri == null) {
|
||||
return;
|
||||
@@ -1315,38 +1270,10 @@ public final class GalleryListScene extends BaseScene
|
||||
public void onEndDragHandler() {
|
||||
// Restore right drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
|
||||
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.returnSearchBarPosition();
|
||||
}
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChange(SearchBar searchBar, int newState, int oldState, boolean animation) {
|
||||
if (null == mLeftDrawable || null == mRightDrawable) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (oldState) {
|
||||
default:
|
||||
case SearchBar.STATE_NORMAL:
|
||||
mLeftDrawable.setArrow(animation ? ANIMATE_TIME : 0);
|
||||
mRightDrawable.setDelete(animation ? ANIMATE_TIME : 0);
|
||||
break;
|
||||
case SearchBar.STATE_SEARCH:
|
||||
if (newState == SearchBar.STATE_NORMAL) {
|
||||
mLeftDrawable.setMenu(animation ? ANIMATE_TIME : 0);
|
||||
mRightDrawable.setAdd(animation ? ANIMATE_TIME : 0);
|
||||
}
|
||||
break;
|
||||
case SearchBar.STATE_SEARCH_LIST:
|
||||
if (newState == STATE_NORMAL) {
|
||||
mLeftDrawable.setMenu(animation ? ANIMATE_TIME : 0);
|
||||
mRightDrawable.setAdd(animation ? ANIMATE_TIME : 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
public void onStateChange(int newState) {
|
||||
if (newState == STATE_NORMAL || newState == STATE_SIMPLE_SEARCH) {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
|
||||
@@ -1358,9 +1285,7 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
@Override
|
||||
public void onChangeSearchMode() {
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1375,32 +1300,9 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
|
||||
// SearchBarMover.Helper
|
||||
@Override
|
||||
public boolean isValidView(RecyclerView recyclerView) {
|
||||
return (mState == STATE_NORMAL && recyclerView == mRecyclerView) ||
|
||||
(mState == STATE_SEARCH && recyclerView == mSearchLayout);
|
||||
}
|
||||
|
||||
// SearchBarMover.Helper
|
||||
@Override
|
||||
public RecyclerView getValidRecyclerView() {
|
||||
if (mState == STATE_NORMAL || mState == STATE_SIMPLE_SEARCH) {
|
||||
return mRecyclerView;
|
||||
} else {
|
||||
return mSearchLayout;
|
||||
}
|
||||
}
|
||||
|
||||
// SearchBarMover.Helper
|
||||
@Override
|
||||
public boolean forceShowSearchBar() {
|
||||
return mState == STATE_SIMPLE_SEARCH || mState == STATE_SEARCH_SHOW_LIST;
|
||||
}
|
||||
|
||||
private void onGetGalleryListSuccess(GalleryListParser.Result result, int taskId) {
|
||||
if (mHelper != null && mSearchBarMover != null &&
|
||||
mHelper.isCurrentTask(taskId)) {
|
||||
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
|
||||
String emptyString = getString(mUrlBuilder.getMode() == ListUrlBuilder.MODE_SUBSCRIPTION && result.noWatchedTags
|
||||
? R.string.gallery_list_empty_hit_subscription
|
||||
: R.string.gallery_list_empty_hit);
|
||||
@@ -1419,8 +1321,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
private void onGetGalleryListFailure(Exception e, int taskId) {
|
||||
if (mHelper != null && mSearchBarMover != null &&
|
||||
mHelper.isCurrentTask(taskId)) {
|
||||
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
|
||||
mHelper.onGetException(taskId, e);
|
||||
}
|
||||
}
|
||||
@@ -1550,7 +1451,7 @@ public final class GalleryListScene extends BaseScene
|
||||
mRecyclerView.outOfCustomChoiceMode();
|
||||
}
|
||||
|
||||
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
|
||||
DownloadManager downloadManager = EhApplication.getDownloadManager();
|
||||
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
|
||||
if (downloadInfo == null) {
|
||||
return;
|
||||
@@ -1630,7 +1531,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class UrlSuggestion extends SearchBar.Suggestion {
|
||||
private abstract class UrlSuggestion extends Suggestion {
|
||||
@Override
|
||||
public CharSequence getText(TextView textView) {
|
||||
if (textView.getId() == android.R.id.text1) {
|
||||
@@ -1744,23 +1645,24 @@ public final class GalleryListScene extends BaseScene
|
||||
return;
|
||||
}
|
||||
|
||||
int prevGid = 0, nextGid = 0;
|
||||
if (mIsTopList) {
|
||||
mUrlBuilder.setNextGid(page);
|
||||
if (jumpTo != null) {
|
||||
pgCounter = Integer.parseInt(jumpTo);
|
||||
mUrlBuilder.setNextGid(pgCounter);
|
||||
nextGid = pgCounter;
|
||||
} else {
|
||||
nextGid = page;
|
||||
}
|
||||
} else {
|
||||
int prevGid = 0, nextGid = 0;
|
||||
if (page != 0) {
|
||||
if (page >= mHelper.getPageForTop())
|
||||
nextGid = minGid;
|
||||
else
|
||||
prevGid = maxGid;
|
||||
}
|
||||
mUrlBuilder.setPrevGid(prevGid);
|
||||
mUrlBuilder.setNextGid(nextGid);
|
||||
} else if (jumpTo != null) {
|
||||
nextGid = minGid;
|
||||
} else if (page != 0) {
|
||||
if (page >= mHelper.getPageForTop())
|
||||
nextGid = minGid;
|
||||
else
|
||||
prevGid = maxGid;
|
||||
}
|
||||
mUrlBuilder.setPrevGid(prevGid);
|
||||
mUrlBuilder.setNextGid(nextGid);
|
||||
|
||||
mUrlBuilder.setJumpTo(jumpTo);
|
||||
jumpTo = null;
|
||||
@@ -1813,9 +1715,7 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
@Override
|
||||
public void onShowView(View hiddenView, View shownView) {
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
showSearchBar();
|
||||
showActionFab();
|
||||
}
|
||||
|
||||
@@ -1825,11 +1725,9 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onScrollToPosition(int postion) {
|
||||
if (0 == postion) {
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
protected void onScrollToPosition(int position) {
|
||||
if (0 == position) {
|
||||
showSearchBar();
|
||||
showActionFab();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
mClient = EhApplication.getEhClient(context);
|
||||
mClient = EhApplication.getEhClient();
|
||||
if (savedInstanceState == null) {
|
||||
onInit();
|
||||
} else {
|
||||
@@ -148,7 +148,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
|
||||
mAdapter = new GalleryPreviewAdapter();
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
int columnWidth = resources.getDimensionPixelOffset(Settings.getThumbSizeResId());
|
||||
int columnWidth = Settings.getThumbSize();
|
||||
AutoGridLayoutManager layoutManager = new AutoGridLayoutManager(context, columnWidth, LayoutUtils.dp2pix(context, 16));
|
||||
layoutManager.setStrategy(AutoGridLayoutManager.STRATEGY_SUITABLE_SIZE);
|
||||
mRecyclerView.setLayoutManager(layoutManager);
|
||||
@@ -191,7 +191,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setTitle(R.string.gallery_previews);
|
||||
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
|
||||
if (((GalleryDetail)mGalleryInfo).previewPages > 2)
|
||||
if (((GalleryDetail) mGalleryInfo).previewPages > 2)
|
||||
showMenu(R.menu.scene_gallery_previews);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ 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
|
||||
@@ -59,6 +58,7 @@ import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import rikka.core.res.resolveColor
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
class HistoryScene : ToolbarScene() {
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
private val mAdapter: HistoryAdapter by lazy {
|
||||
@@ -72,9 +72,7 @@ class HistoryScene : ToolbarScene() {
|
||||
}
|
||||
})
|
||||
}
|
||||
private val mDownloadManager: DownloadManager by lazy {
|
||||
EhApplication.getDownloadManager(requireContext())
|
||||
}
|
||||
private val mDownloadManager = EhApplication.downloadManager
|
||||
private val mDownloadInfoListener: DownloadInfoListener by lazy {
|
||||
object : DownloadInfoListener {
|
||||
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
@@ -99,9 +97,7 @@ class HistoryScene : ToolbarScene() {
|
||||
override fun onUpdateLabels() {}
|
||||
}
|
||||
}
|
||||
private val mFavouriteStatusRouter: FavouriteStatusRouter by lazy {
|
||||
EhApplication.getFavouriteStatusRouter(requireContext())
|
||||
}
|
||||
private val mFavouriteStatusRouter = EhApplication.favouriteStatusRouter
|
||||
private val mFavouriteStatusRouterListener: FavouriteStatusRouter.Listener by lazy {
|
||||
FavouriteStatusRouter.Listener { _: Long, _: Int ->
|
||||
mAdapter.notifyDataSetChanged()
|
||||
@@ -135,7 +131,7 @@ class HistoryScene : ToolbarScene() {
|
||||
setLiftOnScrollTargetView(recyclerView)
|
||||
val mFastScroller = ViewUtils.`$$`(content, R.id.fast_scroller) as FastScroller
|
||||
val mTip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
ViewTransition(content, mTip)
|
||||
val mViewTransition = ViewTransition(content, mTip)
|
||||
val resources = requireContext().resources
|
||||
val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.big_history)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
@@ -168,6 +164,15 @@ class HistoryScene : ToolbarScene() {
|
||||
)
|
||||
}
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
mAdapter.onPagesUpdatedFlow.collectLatest {
|
||||
if (mAdapter.itemCount == 0) {
|
||||
mViewTransition.showView(1, true)
|
||||
} else {
|
||||
mViewTransition.showView(0, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
@@ -302,7 +307,7 @@ class HistoryScene : ToolbarScene() {
|
||||
}
|
||||
|
||||
3 -> {
|
||||
val labelRawList = EhApplication.getDownloadManager(context).labelList
|
||||
val labelRawList = EhApplication.downloadManager.labelList
|
||||
val labelList: MutableList<String> = ArrayList(labelRawList.size + 1)
|
||||
labelList.add(getString(R.string.default_download_label_name))
|
||||
var i = 0
|
||||
@@ -373,7 +378,7 @@ class HistoryScene : ToolbarScene() {
|
||||
private val mGi: GalleryInfo
|
||||
) : DialogInterface.OnClickListener {
|
||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||
val downloadManager = EhApplication.getDownloadManager(requireContext())
|
||||
val downloadManager = EhApplication.downloadManager
|
||||
val downloadInfo = downloadManager.getDownloadInfo(mGi.gid) ?: return
|
||||
val label = if (which == 0) null else mLabels[which]
|
||||
downloadManager.changeLabel(listOf(downloadInfo), label)
|
||||
|
||||
@@ -99,7 +99,7 @@ public final class ProgressScene extends BaseScene implements View.OnClickListen
|
||||
.setArgs(mGid, mPToken, mPage)
|
||||
.setCallback(new GetGalleryTokenListener(context,
|
||||
activity.getStageId(), getTag()));
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.search.SearchBar
|
||||
import com.google.android.material.search.SearchView
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.widget.SearchDatabase
|
||||
import com.hippo.scene.StageActivity
|
||||
|
||||
|
||||
abstract class SearchBarScene : ToolbarScene() {
|
||||
private var mSearchView: SearchView? = null
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
private var mSuggestionList: List<Suggestion>? = null
|
||||
private var mSuggestionAdapter: SuggestionAdapter? = null
|
||||
private var mSuggestionProvider: SuggestionProvider? = null
|
||||
private var mAllowEmptySearch = true
|
||||
private val mSearchDatabase by lazy { SearchDatabase.getInstance(context) }
|
||||
private var onApplySearch: (String) -> Unit = {}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view =
|
||||
inflater.inflate(R.layout.scene_searchbar, container, false) as ViewGroup
|
||||
mToolbar = view.findViewById(R.id.toolbar)
|
||||
mAppBarLayout = view.findViewById(R.id.appbar)
|
||||
mAppBarLayout?.statusBarForeground = MaterialShapeDrawable.createWithElevationOverlay(context)
|
||||
|
||||
mSearchView = view.findViewById(R.id.searchview)
|
||||
mRecyclerView = view.findViewById(R.id.search_bar_list)
|
||||
mSearchView?.editText?.addTextChangedListener {
|
||||
updateSuggestions()
|
||||
}
|
||||
mSearchView?.editText?.setOnEditorActionListener { _, _, _ ->
|
||||
(mToolbar as SearchBar).text = mSearchView!!.text
|
||||
mSearchView?.hide()
|
||||
onApplySearch()
|
||||
false
|
||||
}
|
||||
mSuggestionList = ArrayList()
|
||||
mSuggestionAdapter = SuggestionAdapter(LayoutInflater.from(context))
|
||||
mRecyclerView?.adapter = mSuggestionAdapter
|
||||
val layoutManager = LinearLayoutManager(context)
|
||||
mRecyclerView?.layoutManager = layoutManager
|
||||
mSearchView?.addTransitionListener { _, _, newState ->
|
||||
if (newState == SearchView.TransitionState.SHOWING)
|
||||
onSearchViewExpanded()
|
||||
else if (newState == SearchView.TransitionState.HIDING)
|
||||
onSearchViewHidden()
|
||||
}
|
||||
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
|
||||
return view.apply { addView(contentView, 0) }
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
open fun onSearchViewExpanded() {
|
||||
(requireActivity() as StageActivity).updateBackPressCallBackStatus()
|
||||
updateSuggestions()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
open fun onSearchViewHidden() {
|
||||
(requireActivity() as StageActivity).updateBackPressCallBackStatus()
|
||||
}
|
||||
|
||||
fun setSearchBarHint(hint: String?) {
|
||||
(mToolbar as? SearchBar)?.hint = hint
|
||||
}
|
||||
|
||||
fun setSearchBarText(text: String?) {
|
||||
(mToolbar as? SearchBar)?.text = text
|
||||
}
|
||||
|
||||
fun setEditTextHint(hint: String?) {
|
||||
mSearchView?.editText?.hint = hint
|
||||
}
|
||||
|
||||
fun setEditTextText(text: String?) {
|
||||
mSearchView?.setText(text)
|
||||
}
|
||||
|
||||
fun setNavigationDrawable(drawable: Drawable?) {
|
||||
(mToolbar as? SearchBar)?.navigationIcon = drawable
|
||||
}
|
||||
|
||||
override fun onNavigationClick() {
|
||||
toggleDrawer(Gravity.START)
|
||||
}
|
||||
|
||||
fun setOnApplySearch(lambda: (String) -> Unit) {
|
||||
onApplySearch = lambda
|
||||
}
|
||||
|
||||
fun onApplySearch() {
|
||||
val query = mSearchView?.text.toString().trim()
|
||||
if (!mAllowEmptySearch && query.isEmpty()) {
|
||||
return
|
||||
}
|
||||
mSearchDatabase.addQuery(query)
|
||||
onApplySearch(query)
|
||||
}
|
||||
|
||||
fun setAllowEmptySearch(allowEmptySearch: Boolean) {
|
||||
mAllowEmptySearch = allowEmptySearch
|
||||
}
|
||||
|
||||
fun isSearchViewShown(): Boolean {
|
||||
return mSearchView?.isShowing ?: false
|
||||
}
|
||||
|
||||
fun hideSearchView() {
|
||||
mSearchView?.hide()
|
||||
}
|
||||
|
||||
private fun wrapTagKeyword(keyword: String): String {
|
||||
var keyword = keyword
|
||||
keyword = keyword.trim { it <= ' ' }
|
||||
|
||||
val index1 = keyword.indexOf(':')
|
||||
if (index1 == -1 || index1 >= keyword.length - 1) {
|
||||
// Can't find :, or : is the last char
|
||||
return keyword
|
||||
}
|
||||
if (keyword[index1 + 1] == '"') {
|
||||
// The char after : is ", the word must be quoted
|
||||
return keyword
|
||||
}
|
||||
val index2 = keyword.indexOf(' ')
|
||||
return if (index2 <= index1) {
|
||||
// Can't find space, or space is before :
|
||||
keyword
|
||||
} else keyword.substring(0, index1 + 1) + "\"" + keyword.substring(index1 + 1) + "$\""
|
||||
|
||||
}
|
||||
|
||||
interface SuggestionProvider {
|
||||
|
||||
fun providerSuggestions(text: String): List<Suggestion>?
|
||||
}
|
||||
|
||||
abstract class Suggestion {
|
||||
|
||||
abstract fun getText(textView: TextView): CharSequence?
|
||||
|
||||
abstract fun onClick()
|
||||
|
||||
open fun onLongClick(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class SuggestionHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val text1 = itemView.findViewById(android.R.id.text1) as TextView
|
||||
val text2 = itemView.findViewById(android.R.id.text2) as TextView
|
||||
}
|
||||
|
||||
private inner class SuggestionAdapter constructor(private val mInflater: LayoutInflater) :
|
||||
RecyclerView.Adapter<SuggestionHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SuggestionHolder {
|
||||
return SuggestionHolder(mInflater.inflate(R.layout.item_simple_list_2, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SuggestionHolder, position: Int) {
|
||||
val suggestion = mSuggestionList?.get(position)
|
||||
val text1 = suggestion?.getText(holder.text1)
|
||||
val text2 = suggestion?.getText(holder.text2)
|
||||
holder.text1.text = text1
|
||||
if (text2 == null) {
|
||||
holder.text2.visibility = View.GONE
|
||||
holder.text2.text = ""
|
||||
} else {
|
||||
holder.text2.visibility = View.VISIBLE
|
||||
holder.text2.text = text2
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
mSuggestionList?.run {
|
||||
if (position < size) {
|
||||
this[position].onClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
holder.itemView.setOnLongClickListener {
|
||||
mSuggestionList?.run {
|
||||
if (position < size) {
|
||||
return@setOnLongClickListener this[position].onLongClick()
|
||||
}
|
||||
}
|
||||
return@setOnLongClickListener false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mSuggestionList?.size ?: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class TagSuggestion constructor(var mHint: String, var mKeyword: String) : Suggestion() {
|
||||
|
||||
override fun getText(textView: TextView): CharSequence {
|
||||
return if (textView.id == android.R.id.text1) {
|
||||
mKeyword
|
||||
} else {
|
||||
mHint
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
val edittext = mSearchView?.editText
|
||||
edittext?.let {
|
||||
val text = it.text.toString()
|
||||
var temp = wrapTagKeyword(mKeyword) + " "
|
||||
if (text.contains(" ")) {
|
||||
temp = text.substring(0, text.lastIndexOf(" ")) + " " + temp
|
||||
}
|
||||
it.setText(temp)
|
||||
it.setSelection(text.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class KeywordSuggestion constructor(private val mKeyword: String) : Suggestion() {
|
||||
|
||||
override fun getText(textView: TextView): CharSequence? {
|
||||
return if (textView.id == android.R.id.text1) {
|
||||
mKeyword
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
mSearchView?.editText?.run {
|
||||
setText(mKeyword)
|
||||
setSelection(length())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(): Boolean {
|
||||
BaseDialogBuilder(requireContext())
|
||||
.setMessage(requireContext().getString(R.string.delete_search_history, mKeyword))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
mSearchDatabase.deleteQuery(mKeyword)
|
||||
updateSuggestions(false)
|
||||
}
|
||||
.show()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun updateSuggestions(scrollToTop: Boolean = true) {
|
||||
val suggestions = arrayListOf<Suggestion>()
|
||||
val text = mSearchView?.editText?.text?.toString() ?: return
|
||||
mSuggestionProvider?.run {
|
||||
providerSuggestions(text)?.let { suggestions.addAll(it) }
|
||||
}
|
||||
mSearchDatabase.getSuggestions(text, 128).forEach { suggestions.add(KeywordSuggestion(it)) }
|
||||
val ehTagDatabase = EhTagDatabase.getInstance(context)
|
||||
if (!TextUtils.isEmpty(text) && ehTagDatabase != null && !text.endsWith(" ")) {
|
||||
val s = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (s.isNotEmpty()) {
|
||||
val keyword = s[s.size - 1]
|
||||
val searchHints = ehTagDatabase.suggest(keyword)
|
||||
|
||||
for (searchHint in searchHints) {
|
||||
suggestions.add(TagSuggestion(searchHint.first, searchHint.second))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
mSuggestionList = suggestions
|
||||
mSuggestionAdapter?.notifyDataSetChanged()
|
||||
if (scrollToTop) {
|
||||
mRecyclerView?.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSuggestionProvider(suggestionProvider: SuggestionProvider) {
|
||||
mSuggestionProvider = suggestionProvider
|
||||
}
|
||||
|
||||
fun showSearchBar() {
|
||||
mAppBarLayout?.setExpanded(true)
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class SelectSiteScene extends SolidScene implements View.OnClickListener
|
||||
View view = inflater.inflate(R.layout.scene_select_site, container, false);
|
||||
|
||||
mButtonGroup = (MaterialButtonToggleGroup) ViewUtils.$$(view, R.id.button_group);
|
||||
if (EhApplication.getEhCookieStore(requireContext()).hasSignedIn()) {
|
||||
if (EhApplication.getEhCookieStore().hasSignedIn()) {
|
||||
((MaterialButton) view.findViewById(R.id.site_ex)).setChecked(true);
|
||||
} else {
|
||||
((MaterialButton) view.findViewById(R.id.site_e)).setChecked(true);
|
||||
|
||||
@@ -45,7 +45,6 @@ import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.WebViewUtil;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
@@ -132,7 +131,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
mSignIn = ViewUtils.$$(loginForm, R.id.sign_in);
|
||||
mSignInViaWebView = (TextView) ViewUtils.$$(loginForm, R.id.sign_in_via_webview);
|
||||
mSignInViaCookies = (TextView) ViewUtils.$$(loginForm, R.id.sign_in_via_cookies);
|
||||
mSkipSigningIn = (TextView) ViewUtils.$$(loginForm, R.id.skip_signing_in);
|
||||
mSkipSigningIn = (TextView) ViewUtils.$$(loginForm, R.id.tourist_mode);
|
||||
|
||||
mSignInViaWebView.setPaintFlags(mSignInViaWebView.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
mSignInViaCookies.setPaintFlags(mSignInViaCookies.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
|
||||
@@ -226,10 +225,6 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
} else if (mSignIn == v) {
|
||||
signIn();
|
||||
} else if (mSignInViaWebView == v) {
|
||||
if (!WebViewUtil.available(getContext())) {
|
||||
showTip(R.string.error_no_webview, BaseScene.LENGTH_SHORT);
|
||||
return;
|
||||
}
|
||||
startScene(new Announcer(WebViewSignInScene.class).setRequestCode(this, REQUEST_CODE_WEBVIEW));
|
||||
} else if (mSignInViaCookies == v) {
|
||||
startScene(new Announcer(CookieSignInScene.class).setRequestCode(this, REQUEST_CODE_COOKIE));
|
||||
@@ -294,7 +289,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
.setMethod(EhClient.METHOD_SIGN_IN)
|
||||
.setArgs(username, password)
|
||||
.setCallback(callback);
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
|
||||
mSigningIn = true;
|
||||
}
|
||||
@@ -315,7 +310,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
EhRequest request = new EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_PROFILE)
|
||||
.setCallback(callback);
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
}
|
||||
|
||||
private void redirectTo() {
|
||||
@@ -346,7 +341,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
return;
|
||||
}
|
||||
|
||||
if (EhApplication.getEhCookieStore(context).hasSignedIn()) {
|
||||
if (EhApplication.getEhCookieStore().hasSignedIn()) {
|
||||
getProfile();
|
||||
} else {
|
||||
mSigningIn = false;
|
||||
|
||||
@@ -118,7 +118,7 @@ public class WebViewSignInScene extends SolidScene {
|
||||
}
|
||||
|
||||
private void addCookie(Context context, String domain, Cookie cookie) {
|
||||
EhApplication.getEhCookieStore(context).addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
|
||||
EhApplication.getEhCookieStore().addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,12 @@ import android.util.AttributeSet
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.yorozuya.NumberUtils
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
package com.hippo.ehviewer.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.Calendar
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
@@ -25,15 +23,22 @@ import com.hippo.ehviewer.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.widget.ContentLayout.ContentHelper
|
||||
import com.hippo.yorozuya.IntIdGenerator
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
@JvmField
|
||||
var maxGid = -1
|
||||
|
||||
@JvmField
|
||||
var minGid = -1
|
||||
|
||||
@JvmField
|
||||
var jumpTo : String? = null
|
||||
var jumpTo: String? = null
|
||||
private val listener: FavouriteStatusRouter.Listener
|
||||
|
||||
@SuppressLint("UseSparseArrays")
|
||||
@@ -46,11 +51,11 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
info.favoriteSlot = slot
|
||||
}
|
||||
}
|
||||
EhApplication.getFavouriteStatusRouter().addListener(listener)
|
||||
EhApplication.favouriteStatusRouter.addListener(listener)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
EhApplication.getFavouriteStatusRouter().removeListener(listener)
|
||||
EhApplication.favouriteStatusRouter.removeListener(listener)
|
||||
}
|
||||
|
||||
override fun onAddData(data: GalleryInfo?) {
|
||||
@@ -62,8 +67,8 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
info?.let {
|
||||
if (maxGid == -1) maxGid = info.gid.toInt()
|
||||
if (minGid == -1) minGid = info.gid.toInt()
|
||||
maxGid = Math.max(maxGid.toLong(), info.gid).toInt()
|
||||
minGid = Math.min(minGid.toLong(), info.gid).toInt()
|
||||
maxGid = max(maxGid.toLong(), info.gid).toInt()
|
||||
minGid = min(minGid.toLong(), info.gid).toInt()
|
||||
map[info.gid] = info
|
||||
}
|
||||
}
|
||||
@@ -95,7 +100,7 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
val bundle = super.saveInstanceState(superState) as Bundle
|
||||
|
||||
// TODO It's a bad design
|
||||
val router = EhApplication.getFavouriteStatusRouter()
|
||||
val router = EhApplication.favouriteStatusRouter
|
||||
val id = router.saveDataMap(map)
|
||||
bundle.putInt(KEY_DATA_MAP, id)
|
||||
bundle.putInt(KEY_MIN_GID, minGid)
|
||||
@@ -107,7 +112,7 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
val bundle = state as Bundle
|
||||
val id = bundle.getInt(KEY_DATA_MAP, IntIdGenerator.INVALID_ID)
|
||||
if (id != IntIdGenerator.INVALID_ID) {
|
||||
val router = EhApplication.getFavouriteStatusRouter()
|
||||
val router = EhApplication.favouriteStatusRouter
|
||||
val map = router.restoreDataMap(id)
|
||||
if (map != null) {
|
||||
this.map = map
|
||||
@@ -119,11 +124,9 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
}
|
||||
|
||||
fun goTo(time: Long) {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = time
|
||||
val date = calendar.time
|
||||
val format = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
jumpTo = format.format(date)
|
||||
val formatter = DateTimeFormatter
|
||||
.ofPattern("yyyy-MM-dd", Locale.US).withZone(ZoneOffset.UTC)
|
||||
jumpTo = formatter.format(Instant.ofEpochMilli(time))
|
||||
doGoToPage(Int.MAX_VALUE / 2)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,682 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhTagDatabase;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.SimpleAnimatorListener;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchBar extends MaterialCardView implements View.OnClickListener,
|
||||
TextView.OnEditorActionListener, TextWatcher,
|
||||
SearchEditText.SearchEditTextListener {
|
||||
|
||||
public static final int STATE_NORMAL = 0;
|
||||
public static final int STATE_SEARCH = 1;
|
||||
public static final int STATE_SEARCH_LIST = 2;
|
||||
private static final String STATE_KEY_SUPER = "super";
|
||||
private static final String STATE_KEY_STATE = "state";
|
||||
private static final long ANIMATE_TIME = 300L;
|
||||
private final Rect mRect = new Rect();
|
||||
private int mState = STATE_NORMAL;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mBaseHeight;
|
||||
private float mProgress;
|
||||
|
||||
private ImageView mMenuButton;
|
||||
private TextView mTitleTextView;
|
||||
private ImageView mActionButton;
|
||||
private SearchEditText mEditText;
|
||||
private EasyRecyclerView mListView;
|
||||
private View mListContainer;
|
||||
private View mListHeader;
|
||||
|
||||
private ViewTransition mViewTransition;
|
||||
|
||||
private SearchDatabase mSearchDatabase;
|
||||
private List<Suggestion> mSuggestionList;
|
||||
private SuggestionAdapter mSuggestionAdapter;
|
||||
|
||||
private Helper mHelper;
|
||||
private OnStateChangeListener mOnStateChangeListener;
|
||||
private SuggestionProvider mSuggestionProvider;
|
||||
|
||||
private boolean mAllowEmptySearch = true;
|
||||
|
||||
private boolean mInAnimation;
|
||||
|
||||
public SearchBar(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public SearchBar(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public SearchBar(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mSearchDatabase = SearchDatabase.getInstance(getContext());
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
inflater.inflate(R.layout.widget_search_bar, this);
|
||||
mMenuButton = (ImageView) ViewUtils.$$(this, R.id.search_menu);
|
||||
mTitleTextView = (TextView) ViewUtils.$$(this, R.id.search_title);
|
||||
mActionButton = (ImageView) ViewUtils.$$(this, R.id.search_action);
|
||||
mEditText = (SearchEditText) ViewUtils.$$(this, R.id.search_edit_text);
|
||||
mListContainer = ViewUtils.$$(this, R.id.list_container);
|
||||
mListView = (EasyRecyclerView) ViewUtils.$$(mListContainer, R.id.search_bar_list);
|
||||
mListHeader = ViewUtils.$$(mListContainer, R.id.list_header);
|
||||
|
||||
mViewTransition = new ViewTransition(mTitleTextView, mEditText);
|
||||
|
||||
mTitleTextView.setOnClickListener(this);
|
||||
mMenuButton.setOnClickListener(this);
|
||||
mActionButton.setOnClickListener(this);
|
||||
mEditText.setSearchEditTextListener(this);
|
||||
mEditText.setOnEditorActionListener(this);
|
||||
mEditText.addTextChangedListener(this);
|
||||
|
||||
// Get base height
|
||||
ViewUtils.measureView(this, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mBaseHeight = getMeasuredHeight();
|
||||
|
||||
mSuggestionList = new ArrayList<>();
|
||||
mSuggestionAdapter = new SuggestionAdapter(LayoutInflater.from(getContext()));
|
||||
mListView.setAdapter(mSuggestionAdapter);
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
mListView.setLayoutManager(layoutManager);
|
||||
}
|
||||
|
||||
private void addListHeader() {
|
||||
mListHeader.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void removeListHeader() {
|
||||
mListHeader.setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void updateSuggestions() {
|
||||
updateSuggestions(true);
|
||||
}
|
||||
|
||||
private void updateSuggestions(boolean scrollToTop) {
|
||||
List<Suggestion> suggestions = new ArrayList<>();
|
||||
|
||||
String text = mEditText.getText().toString();
|
||||
|
||||
if (mSuggestionProvider != null) {
|
||||
List<Suggestion> providerSuggestions = mSuggestionProvider.providerSuggestions(text);
|
||||
if (providerSuggestions != null && !providerSuggestions.isEmpty()) {
|
||||
suggestions.addAll(providerSuggestions);
|
||||
}
|
||||
}
|
||||
|
||||
String[] keywords = mSearchDatabase.getSuggestions(text, 128);
|
||||
for (String keyword : keywords) {
|
||||
suggestions.add(new KeywordSuggestion(keyword));
|
||||
}
|
||||
|
||||
EhTagDatabase ehTagDatabase = EhTagDatabase.getInstance(getContext());
|
||||
if (!TextUtils.isEmpty(text) && ehTagDatabase != null && !text.endsWith(" ")) {
|
||||
String[] s = text.split(" ");
|
||||
if (s.length > 0) {
|
||||
String keyword = s[s.length - 1];
|
||||
ArrayList<Pair<String, String>> searchHints = ehTagDatabase.suggest(keyword);
|
||||
|
||||
for (Pair<String, String> searchHint : searchHints) {
|
||||
suggestions.add(new TagSuggestion(searchHint.first, searchHint.second));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
mSuggestionList = suggestions;
|
||||
if (mSuggestionList.size() == 0) {
|
||||
removeListHeader();
|
||||
} else {
|
||||
addListHeader();
|
||||
}
|
||||
mSuggestionAdapter.notifyDataSetChanged();
|
||||
|
||||
if (scrollToTop) {
|
||||
mListView.scrollToPosition(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAllowEmptySearch(boolean allowEmptySearch) {
|
||||
mAllowEmptySearch = allowEmptySearch;
|
||||
}
|
||||
|
||||
public void setEditTextHint(CharSequence hint) {
|
||||
mEditText.setHint(hint);
|
||||
}
|
||||
|
||||
public void setHelper(Helper helper) {
|
||||
mHelper = helper;
|
||||
}
|
||||
|
||||
public void setOnStateChangeListener(OnStateChangeListener listener) {
|
||||
mOnStateChangeListener = listener;
|
||||
}
|
||||
|
||||
public void setSuggestionProvider(SuggestionProvider suggestionProvider) {
|
||||
mSuggestionProvider = suggestionProvider;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return mEditText.getText().toString();
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
mEditText.setText(text);
|
||||
}
|
||||
|
||||
public void cursorToEnd() {
|
||||
mEditText.setSelection(mEditText.getText().length());
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
mTitleTextView.setText(title);
|
||||
}
|
||||
|
||||
public void setSearch(String search) {
|
||||
mTitleTextView.setText(search);
|
||||
mEditText.setText(search);
|
||||
}
|
||||
|
||||
public void setLeftDrawable(Drawable drawable) {
|
||||
mMenuButton.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
public void setRightDrawable(Drawable drawable) {
|
||||
mActionButton.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
public void applySearch() {
|
||||
String query = mEditText.getText().toString().trim();
|
||||
|
||||
if (!mAllowEmptySearch && TextUtils.isEmpty(query)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Put it into db
|
||||
mSearchDatabase.addQuery(query);
|
||||
// Callback
|
||||
mHelper.onApplySearch(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v == mTitleTextView) {
|
||||
mHelper.onClickTitle();
|
||||
} else if (v == mMenuButton) {
|
||||
v.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
|
||||
mHelper.onClickLeftIcon();
|
||||
} else if (v == mActionButton) {
|
||||
v.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
|
||||
mHelper.onClickRightIcon();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (v == mEditText) {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_NULL) {
|
||||
applySearch();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
setState(state, true);
|
||||
}
|
||||
|
||||
public void setState(int state, boolean animation) {
|
||||
if (mState != state) {
|
||||
int oldState = mState;
|
||||
mState = state;
|
||||
|
||||
switch (oldState) {
|
||||
default:
|
||||
case STATE_NORMAL:
|
||||
mViewTransition.showView(1, animation);
|
||||
mEditText.requestFocus();
|
||||
|
||||
if (state == STATE_SEARCH_LIST) {
|
||||
showImeAndSuggestionsList(animation);
|
||||
}
|
||||
if (mOnStateChangeListener != null) {
|
||||
mOnStateChangeListener.onStateChange(this, state, oldState, animation);
|
||||
}
|
||||
break;
|
||||
case STATE_SEARCH:
|
||||
if (state == STATE_NORMAL) {
|
||||
mViewTransition.showView(0, animation);
|
||||
} else if (state == STATE_SEARCH_LIST) {
|
||||
showImeAndSuggestionsList(animation);
|
||||
}
|
||||
if (mOnStateChangeListener != null) {
|
||||
mOnStateChangeListener.onStateChange(this, state, oldState, animation);
|
||||
}
|
||||
break;
|
||||
case STATE_SEARCH_LIST:
|
||||
hideImeAndSuggestionsList(animation);
|
||||
if (state == STATE_NORMAL) {
|
||||
mViewTransition.showView(0, animation);
|
||||
}
|
||||
if (mOnStateChangeListener != null) {
|
||||
mOnStateChangeListener.onStateChange(this, state, oldState, animation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void showImeAndSuggestionsList(boolean animation) {
|
||||
// Show ime
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(mEditText, 0);
|
||||
// update suggestion for show suggestions list
|
||||
updateSuggestions();
|
||||
// Show suggestions list
|
||||
if (animation) {
|
||||
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", 1f);
|
||||
oa.setDuration(ANIMATE_TIME);
|
||||
oa.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
|
||||
oa.addListener(new SimpleAnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mListContainer.setVisibility(View.VISIBLE);
|
||||
mInAnimation = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mInAnimation = false;
|
||||
}
|
||||
});
|
||||
oa.setAutoCancel(true);
|
||||
oa.start();
|
||||
} else {
|
||||
mListContainer.setVisibility(View.VISIBLE);
|
||||
setProgress(1f);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideImeAndSuggestionsList(boolean animation) {
|
||||
// Hide ime
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(this.getWindowToken(), 0);
|
||||
// Hide suggestions list
|
||||
if (animation) {
|
||||
ObjectAnimator oa = ObjectAnimator.ofFloat(this, "progress", 0f);
|
||||
oa.setDuration(ANIMATE_TIME);
|
||||
oa.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR);
|
||||
oa.addListener(new SimpleAnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mInAnimation = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mListContainer.setVisibility(View.GONE);
|
||||
mInAnimation = false;
|
||||
}
|
||||
});
|
||||
oa.setAutoCancel(true);
|
||||
oa.start();
|
||||
} else {
|
||||
setProgress(0f);
|
||||
mListContainer.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
if (mListContainer.getVisibility() == View.VISIBLE && (!mInAnimation || mHeight == 0)) {
|
||||
mWidth = right - left;
|
||||
mHeight = bottom - top;
|
||||
}
|
||||
}
|
||||
|
||||
public float getProgress() {
|
||||
return mProgress;
|
||||
}
|
||||
|
||||
@Keep
|
||||
public void setProgress(float progress) {
|
||||
mProgress = progress;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public SearchEditText getEditText() {
|
||||
return mEditText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (mInAnimation && mHeight != 0) {
|
||||
final int state = canvas.save();
|
||||
int bottom = MathUtils.lerp(mBaseHeight, mHeight, mProgress);
|
||||
mRect.set(0, 0, mWidth, bottom);
|
||||
setClipBounds(mRect);
|
||||
canvas.clipRect(mRect);
|
||||
super.draw(canvas);
|
||||
canvas.restoreToCount(state);
|
||||
} else {
|
||||
setClipBounds(null);
|
||||
super.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
updateSuggestions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
mHelper.onSearchEditTextClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
mHelper.onSearchEditTextBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceiveContent(Uri uri) {
|
||||
mHelper.onReceiveContent(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parcelable onSaveInstanceState() {
|
||||
final Bundle state = new Bundle();
|
||||
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
|
||||
state.putInt(STATE_KEY_STATE, mState);
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
if (state instanceof Bundle) {
|
||||
final Bundle savedState = (Bundle) state;
|
||||
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
|
||||
setState(savedState.getInt(STATE_KEY_STATE), false);
|
||||
}
|
||||
}
|
||||
|
||||
private String wrapTagKeyword(String keyword) {
|
||||
keyword = keyword.trim();
|
||||
|
||||
int index1 = keyword.indexOf(':');
|
||||
if (index1 == -1 || index1 >= keyword.length() - 1) {
|
||||
// Can't find :, or : is the last char
|
||||
return keyword;
|
||||
}
|
||||
if (keyword.charAt(index1 + 1) == '"') {
|
||||
// The char after : is ", the word must be quoted
|
||||
return keyword;
|
||||
}
|
||||
int index2 = keyword.indexOf(' ');
|
||||
if (index2 <= index1) {
|
||||
// Can't find space, or space is before :
|
||||
return keyword;
|
||||
}
|
||||
|
||||
return keyword.substring(0, index1 + 1) + "\"" + keyword.substring(index1 + 1) + "$\"";
|
||||
}
|
||||
|
||||
public interface Helper {
|
||||
void onClickTitle();
|
||||
|
||||
void onClickLeftIcon();
|
||||
|
||||
void onClickRightIcon();
|
||||
|
||||
void onSearchEditTextClick();
|
||||
|
||||
void onApplySearch(String query);
|
||||
|
||||
void onSearchEditTextBackPressed();
|
||||
|
||||
default void onReceiveContent(Uri uri) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface OnStateChangeListener {
|
||||
|
||||
void onStateChange(SearchBar searchBar, int newState, int oldState, boolean animation);
|
||||
}
|
||||
|
||||
public interface SuggestionProvider {
|
||||
|
||||
List<Suggestion> providerSuggestions(String text);
|
||||
}
|
||||
|
||||
public abstract static class Suggestion {
|
||||
|
||||
public abstract CharSequence getText(TextView textView);
|
||||
|
||||
public abstract void onClick();
|
||||
|
||||
public boolean onLongClick() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class SuggestionHolder extends RecyclerView.ViewHolder {
|
||||
TextView text1;
|
||||
TextView text2;
|
||||
|
||||
public SuggestionHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
text1 = itemView.findViewById(android.R.id.text1);
|
||||
text2 = itemView.findViewById(android.R.id.text2);
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionAdapter extends RecyclerView.Adapter<SuggestionHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
private SuggestionAdapter(LayoutInflater inflater) {
|
||||
mInflater = inflater;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SuggestionHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new SuggestionHolder(mInflater.inflate(R.layout.item_simple_list_2, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SuggestionHolder holder, int position) {
|
||||
Suggestion suggestion = mSuggestionList.get(position);
|
||||
CharSequence text1 = suggestion.getText(holder.text1);
|
||||
CharSequence text2 = suggestion.getText(holder.text2);
|
||||
holder.text1.setText(text1);
|
||||
if (text2 == null) {
|
||||
holder.text2.setVisibility(View.GONE);
|
||||
holder.text2.setText("");
|
||||
} else {
|
||||
holder.text2.setVisibility(View.VISIBLE);
|
||||
holder.text2.setText(text2);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (position < mSuggestionList.size()) {
|
||||
mSuggestionList.get(position).onClick();
|
||||
}
|
||||
});
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
if (position < mSuggestionList.size()) {
|
||||
return mSuggestionList.get(position).onLongClick();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mSuggestionList.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class TagSuggestion extends SearchBar.Suggestion {
|
||||
public String mHint;
|
||||
public String mKeyword;
|
||||
|
||||
private TagSuggestion(String hint, String keyword) {
|
||||
mHint = hint;
|
||||
mKeyword = keyword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getText(TextView textView) {
|
||||
if (textView.getId() == android.R.id.text1) {
|
||||
return mKeyword;
|
||||
} else {
|
||||
return mHint;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
Editable editable = mEditText.getText();
|
||||
if (editable != null) {
|
||||
String text = editable.toString();
|
||||
String temp = wrapTagKeyword(mKeyword) + " ";
|
||||
if (text.contains(" ")) {
|
||||
temp = text.substring(0, text.lastIndexOf(" ")) + " " + temp;
|
||||
}
|
||||
mEditText.setText(temp);
|
||||
mEditText.setSelection(editable.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class KeywordSuggestion extends Suggestion {
|
||||
|
||||
private final String mKeyword;
|
||||
|
||||
private KeywordSuggestion(String keyword) {
|
||||
mKeyword = keyword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getText(TextView textView) {
|
||||
if (textView.getId() == android.R.id.text1) {
|
||||
return mKeyword;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
mEditText.setText(mKeyword);
|
||||
mEditText.setSelection(mEditText.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick() {
|
||||
new BaseDialogBuilder(getContext())
|
||||
.setMessage(getContext().getString(R.string.delete_search_history, mKeyword))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
mSearchDatabase.deleteQuery(mKeyword);
|
||||
updateSuggestions(false);
|
||||
})
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ public final class SearchDatabase {
|
||||
*
|
||||
* @param maxEntries Max entries to leave in the table. 0 means remove all entries.
|
||||
*/
|
||||
protected void truncateHistory(int maxEntries) {
|
||||
private void truncateHistory(int maxEntries) {
|
||||
if (maxEntries < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ContentInfo;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
|
||||
public class SearchEditText extends AppCompatEditText {
|
||||
|
||||
private SearchEditTextListener mListener;
|
||||
|
||||
public SearchEditText(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SearchEditText(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SearchEditText(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setSearchEditTextListener(SearchEditTextListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
// special case for the back key, we do not even try to send it
|
||||
// to the drop down list but instead, consume it immediately
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
|
||||
KeyEvent.DispatcherState state = getKeyDispatcherState();
|
||||
if (state != null) {
|
||||
state.startTracking(event, this);
|
||||
}
|
||||
return true;
|
||||
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
KeyEvent.DispatcherState state = getKeyDispatcherState();
|
||||
if (state != null) {
|
||||
state.handleUpEvent(event);
|
||||
}
|
||||
if (event.isTracking() && !event.isCanceled()) {
|
||||
// TODO stopSelectionActionMode
|
||||
if (mListener != null) {
|
||||
mListener.onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onKeyPreIme(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP && mListener != null) {
|
||||
mListener.onClick();
|
||||
}
|
||||
try {
|
||||
return super.onTouchEvent(event);
|
||||
} catch (Throwable t) {
|
||||
// Some devices crash here.
|
||||
// I don't why.
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||
@Nullable
|
||||
@Override
|
||||
public ContentInfo onReceiveContent(@NonNull ContentInfo payload) {
|
||||
ClipData clipData = payload.getClip();
|
||||
if (clipData.getItemCount() == 1) {
|
||||
if (mListener != null) {
|
||||
mListener.onReceiveContent(clipData.getItemAt(0).getUri());
|
||||
}
|
||||
}
|
||||
return super.onReceiveContent(payload);
|
||||
}
|
||||
|
||||
public interface SearchEditTextListener {
|
||||
void onClick();
|
||||
|
||||
void onBackPressed();
|
||||
|
||||
void onReceiveContent(Uri uri);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
@@ -187,12 +188,15 @@ class SearchLayout @JvmOverloads constructor(
|
||||
R.id.search_subscription_search -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_SUBSCRIPTION
|
||||
}
|
||||
|
||||
R.id.search_specify_uploader -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_UPLOADER
|
||||
}
|
||||
|
||||
R.id.search_specify_tag -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_TAG
|
||||
}
|
||||
|
||||
else -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_NORMAL
|
||||
}
|
||||
@@ -208,6 +212,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
urlBuilder.pageTo = mTableAdvanceSearch.pageTo
|
||||
}
|
||||
}
|
||||
|
||||
SEARCH_MODE_IMAGE -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
|
||||
mImageView.formatListUrlBuilder(urlBuilder)
|
||||
@@ -242,7 +247,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||
|
||||
@IntDef(SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE)
|
||||
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class SearchMode
|
||||
interface Helper {
|
||||
fun onChangeSearchMode()
|
||||
@@ -291,11 +296,13 @@ class SearchLayout @JvmOverloads constructor(
|
||||
ViewUtils.removeFromParent(mNormalView)
|
||||
content.addView(mNormalView)
|
||||
}
|
||||
|
||||
ITEM_TYPE_NORMAL_ADVANCE -> {
|
||||
title.setText(R.string.search_advance)
|
||||
ViewUtils.removeFromParent(mAdvanceView)
|
||||
content.addView(mAdvanceView)
|
||||
}
|
||||
|
||||
ITEM_TYPE_IMAGE -> {
|
||||
title.setText(R.string.search_image)
|
||||
ViewUtils.removeFromParent(mImageView)
|
||||
@@ -323,7 +330,12 @@ class SearchLayout @JvmOverloads constructor(
|
||||
|
||||
inner class IdentifiedChip @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Chip(context, attrs, R.attr.filterChipStyle) {
|
||||
) : Chip(
|
||||
ContextThemeWrapper(
|
||||
context,
|
||||
com.google.android.material.R.style.Widget_Material3_Chip_Filter
|
||||
), attrs
|
||||
) {
|
||||
var idt = 0
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ public abstract class GalleryProvider {
|
||||
public void stop() {
|
||||
OSUtils.checkMainLoop();
|
||||
mImageCache.evictAll();
|
||||
Image.lazyEvictAll();
|
||||
}
|
||||
|
||||
public void setGLRoot(GLRoot glRoot) {
|
||||
@@ -238,15 +237,14 @@ public abstract class GalleryProvider {
|
||||
|
||||
private static class ImageCache extends LruCache<Integer, ImageWrapper> {
|
||||
|
||||
private static final long MAX_CACHE_SIZE = 128 * 1024 * 1024;
|
||||
private static final long MIN_CACHE_SIZE = 32 * 1024 * 1024;
|
||||
private static final int CACHE_SIZE = 512 * 1024 * 1024;
|
||||
|
||||
public ImageCache() {
|
||||
super((int) MathUtils.clamp(OSUtils.getTotalMemory() / 16, MIN_CACHE_SIZE, MAX_CACHE_SIZE));
|
||||
super(CACHE_SIZE);
|
||||
}
|
||||
|
||||
public void add(Integer key, ImageWrapper value) {
|
||||
if (value.getFormat() != Image.FORMAT_ANIMATED)
|
||||
if (!value.getAnimated())
|
||||
if (value.obtain())
|
||||
put(key, value);
|
||||
}
|
||||
|
||||
@@ -983,15 +983,13 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste
|
||||
|
||||
private final Context mContext;
|
||||
private final Adapter mAdapter;
|
||||
private final int mEdgeColor = Color.WHITE;
|
||||
private Listener mListener;
|
||||
|
||||
private int mLayoutMode = LAYOUT_LEFT_TO_RIGHT;
|
||||
private int mScaleMode = SCALE_FIT;
|
||||
private int mStartPosition = START_POSITION_TOP_LEFT;
|
||||
private int mStartPage = 0;
|
||||
|
||||
private int mBackgroundColor = Color.BLACK;
|
||||
private int mEdgeColor = Color.WHITE;
|
||||
private int mPagerInterval = 48;
|
||||
private int mScrollInterval = 24;
|
||||
private int mPageMinHeight = 256;
|
||||
|
||||
Loaded 100 of 218 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user