Compare commits
128
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dbe24ab55 | ||
|
|
25f7b005cb | ||
|
|
23f55ea208 | ||
|
|
5f19fa0f1f | ||
|
|
58fbdcfcdd | ||
|
|
e33cca9fd9 | ||
|
|
b25e3d5454 | ||
|
|
583f272daa | ||
|
|
2a83ad63be | ||
|
|
6fdc908ad1 | ||
|
|
5213caac49 | ||
|
|
ca0518c747 | ||
|
|
298cd29537 | ||
|
|
1a963a3239 | ||
|
|
683c8dc5c5 | ||
|
|
2087970de6 | ||
|
|
5bd270fe6a | ||
|
|
0992fcfcb0 | ||
|
|
fc12395f7e | ||
|
|
57b291a4e8 | ||
|
|
3adf391478 | ||
|
|
a859941845 | ||
|
|
8d1fc85638 | ||
|
|
51ece1c472 | ||
|
|
45f67ec37f | ||
|
|
85b559c989 | ||
|
|
1382050b5f | ||
|
|
fb970752fd | ||
|
|
3d256a402f | ||
|
|
a677d95d34 | ||
|
|
452c0f2846 | ||
|
|
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 |
No files matched your search
@@ -78,6 +78,7 @@ with Material Design 3 and Dynamic Color Support
|
||||
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)
|
||||
|
||||
+29
-18
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
buildToolsVersion "33.0.1"
|
||||
ndkVersion "25.1.8937393"
|
||||
|
||||
splits {
|
||||
@@ -31,8 +31,8 @@ android {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 33
|
||||
versionCode 180022
|
||||
versionName "1.8.3.1"
|
||||
versionCode 180027
|
||||
versionName "1.8.5.0-preview"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -50,6 +50,12 @@ android {
|
||||
kotlinOptions {
|
||||
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"
|
||||
]
|
||||
@@ -86,7 +92,10 @@ android {
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
compose true
|
||||
dataBinding true
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
@@ -116,17 +125,16 @@ dependencies {
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||
|
||||
def compose_version = "1.3.1"
|
||||
implementation "androidx.compose.animation:animation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime:$compose_version"
|
||||
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
|
||||
implementation "androidx.compose.ui:ui:$compose_version"
|
||||
implementation platform("androidx.compose:compose-bom:2022.12.00")
|
||||
implementation "androidx.compose.animation:animation"
|
||||
implementation "androidx.compose.foundation:foundation"
|
||||
implementation "androidx.compose.foundation:foundation-layout"
|
||||
implementation "androidx.compose.material3:material3"
|
||||
implementation "androidx.compose.runtime:runtime"
|
||||
implementation "androidx.compose.runtime:runtime-saveable"
|
||||
implementation "androidx.compose.ui:ui"
|
||||
|
||||
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"
|
||||
@@ -136,7 +144,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"
|
||||
|
||||
@@ -161,7 +169,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"
|
||||
@@ -173,22 +181,25 @@ 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-beta01'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps'
|
||||
|
||||
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
|
||||
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
|
||||
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
||||
implementation 'io.coil-kt:coil:2.2.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.3'
|
||||
|
||||
// Tachiyomi Reader
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
implementation 'dev.chrisbanes.insetter:insetter:0.6.1'
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
|
||||
Vendored
+1
-3
@@ -14,9 +14,7 @@
|
||||
-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
|
||||
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
|
||||
|
||||
-renamesourcefileattribute
|
||||
-repackageclasses
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
android:maxSdkVersion="28" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
@@ -102,9 +102,10 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.hippo.ehviewer.ui.GalleryActivity"
|
||||
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
|
||||
android:configChanges="screenSize"
|
||||
android:exported="true"
|
||||
android:colorMode="wideColorGamut"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
|
||||
add_subdirectory(libarchive)
|
||||
|
||||
# Build and link our app's native lib
|
||||
add_library(${PROJECT_NAME} SHARED archive.c image.c fdutils.c)
|
||||
add_library(${PROJECT_NAME} SHARED archive.c fdutils.c)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
|
||||
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
|
||||
@@ -52,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",
|
||||
@@ -199,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;
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/bitmap.h>
|
||||
#include <android/data_space.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
#include <jni.h>
|
||||
|
||||
#define TAG "ImageDecoder_wrapper"
|
||||
|
||||
#include "ehviewer.h"
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
|
||||
|
||||
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
int left;
|
||||
int line;
|
||||
size_t line_stride;
|
||||
int src_stride;
|
||||
int src_pos;
|
||||
int dst_pos;
|
||||
size_t dst_blank_length;
|
||||
|
||||
// Sanitize
|
||||
if (src_x < 0) {
|
||||
width -= src_x;
|
||||
dst_x -= src_x;
|
||||
src_x = 0;
|
||||
}
|
||||
if (dst_x < 0) {
|
||||
width -= dst_x;
|
||||
src_x -= dst_x;
|
||||
dst_x = 0;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (src_y < 0) {
|
||||
height -= src_y;
|
||||
dst_y -= src_y;
|
||||
src_y = 0;
|
||||
}
|
||||
if (dst_y < 0) {
|
||||
height -= dst_y;
|
||||
src_y -= dst_y;
|
||||
dst_y = 0;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_x + width - src_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
left = dst_x + width - dst_w;
|
||||
if (left > 0) {
|
||||
width -= left;
|
||||
}
|
||||
if (width <= 0) {
|
||||
return false;
|
||||
}
|
||||
left = src_y + height - src_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
left = dst_y + height - dst_h;
|
||||
if (left > 0) {
|
||||
height -= left;
|
||||
}
|
||||
if (height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init
|
||||
line_stride = (size_t) (width * 4);
|
||||
src_stride = src_w * 4;
|
||||
src_pos = src_y * src_stride + src_x * 4;
|
||||
dst_pos = 0;
|
||||
|
||||
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
|
||||
|
||||
// First line
|
||||
dst_pos += (int) dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int) line_stride;
|
||||
src_pos += src_stride;
|
||||
|
||||
// Other lines
|
||||
dst_blank_length = (size_t) ((dst_w - width) * 4);
|
||||
for (line = 1; line < height; line++) {
|
||||
dst_pos += (int) dst_blank_length;
|
||||
memcpy(dst + dst_pos, src + src_pos, line_stride);
|
||||
dst_pos += (int) line_stride;
|
||||
src_pos += src_stride;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jobject srcBitmap, jint src_x,
|
||||
jint src_y, jobject dst, jint dst_x, jint dst_y, jint width,
|
||||
jint height) {
|
||||
AndroidBitmapInfo dstInfo;
|
||||
AndroidBitmapInfo srcInfo;
|
||||
void *srcPixels = NULL;
|
||||
void *dstPixels = NULL;
|
||||
|
||||
AndroidBitmap_lockPixels(env, srcBitmap, &srcPixels);
|
||||
AndroidBitmap_lockPixels(env, dst, &dstPixels);
|
||||
AndroidBitmap_getInfo(env, srcBitmap, &srcInfo);
|
||||
AndroidBitmap_getInfo(env, dst, &dstInfo);
|
||||
|
||||
copy_pixels(srcPixels, srcInfo.width, srcInfo.height, src_x, src_y, dstPixels, dstInfo.width,
|
||||
dstInfo.height, dst_x, dst_y,
|
||||
width,
|
||||
height);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, dst);
|
||||
AndroidBitmap_unlockPixels(env, srcBitmap);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
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;
|
||||
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);
|
||||
} else {
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
}
|
||||
}
|
||||
Submodule app/src/main/cpp/libarchive updated: 37d2cf6063...c9298fd6dc.
+1
-1
Submodule app/src/main/cpp/liblzma updated: 2327a461e1...b35079d589.
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,160 @@
|
||||
package com.davemorrissey.labs.subscaleview
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import com.davemorrissey.labs.subscaleview.provider.AssetInputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.OpenStreamProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.ResourceInputProvider
|
||||
import com.davemorrissey.labs.subscaleview.provider.UriInputProvider
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Helper class used to set the source and additional attributes from a variety of sources. Supports
|
||||
* use of a bitmap, asset, resource, external file or any other URI.
|
||||
*
|
||||
*
|
||||
* When you are using a preview image, you must set the dimensions of the full size image on the
|
||||
* ImageSource object for the full size image using the [.dimensions] method.
|
||||
*/
|
||||
class ImageSource {
|
||||
val bitmap: Bitmap?
|
||||
val provider: InputProvider?
|
||||
|
||||
private var sWidth = 0
|
||||
private var sHeight = 0
|
||||
|
||||
var sRegion = Rect(0, 0, 0, 0)
|
||||
private set
|
||||
var isCached = false
|
||||
private set
|
||||
|
||||
private constructor(bitmap: Bitmap, cached: Boolean) {
|
||||
this.bitmap = bitmap
|
||||
provider = null
|
||||
sWidth = bitmap.width
|
||||
sHeight = bitmap.height
|
||||
isCached = cached
|
||||
}
|
||||
|
||||
private constructor(provider: InputProvider) {
|
||||
bitmap = null
|
||||
this.provider = provider
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a region of the source image. Region must be set independently for the full size image and the preview if
|
||||
* you are using one.
|
||||
*
|
||||
* @param sRegion the region of the source image to be displayed.
|
||||
* @return this instance for chaining.
|
||||
*/
|
||||
fun region(sRegion: Rect): ImageSource {
|
||||
this.sRegion = sRegion
|
||||
setInvariants()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
|
||||
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
|
||||
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
|
||||
*
|
||||
* @param sWidth width of the source image.
|
||||
* @param sHeight height of the source image.
|
||||
* @return this instance for chaining.
|
||||
*/
|
||||
fun dimensions(sWidth: Int, sHeight: Int): ImageSource {
|
||||
if (bitmap == null) {
|
||||
this.sWidth = sWidth
|
||||
this.sHeight = sHeight
|
||||
}
|
||||
setInvariants()
|
||||
return this
|
||||
}
|
||||
|
||||
private fun setInvariants() {
|
||||
if (!sRegion.isEmpty) {
|
||||
sWidth = sRegion.width()
|
||||
sHeight = sRegion.height()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create an instance from a resource. The correct resource for the device screen resolution will be used.
|
||||
*
|
||||
* @param resId resource ID.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun resource(context: Context, resId: Int): ImageSource {
|
||||
return ImageSource(ResourceInputProvider(context, resId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an asset name.
|
||||
*
|
||||
* @param assetName asset name.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun asset(context: Context, assetName: String): ImageSource {
|
||||
return ImageSource(AssetInputProvider(context, assetName))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a URI.
|
||||
*
|
||||
* @param uri image URI.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun uri(context: Context, uri: Uri): ImageSource {
|
||||
return ImageSource(UriInputProvider(context, uri))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an input provider.
|
||||
*
|
||||
* @param provider input stream provider.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun provider(provider: InputProvider): ImageSource {
|
||||
return ImageSource(provider)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an input stream.
|
||||
*
|
||||
* @param stream open input stream.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun inputStream(stream: InputStream): ImageSource {
|
||||
return ImageSource(OpenStreamProvider(stream))
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a loaded bitmap for display.
|
||||
*
|
||||
* @param bitmap bitmap to be displayed.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun bitmap(bitmap: Bitmap): ImageSource {
|
||||
return ImageSource(bitmap, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
|
||||
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
|
||||
* or Volley.
|
||||
*
|
||||
* @param bitmap bitmap to be displayed.
|
||||
* @return an [ImageSource] instance.
|
||||
*/
|
||||
fun cachedBitmap(bitmap: Bitmap): ImageSource {
|
||||
return ImageSource(bitmap, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.davemorrissey.labs.subscaleview
|
||||
|
||||
import android.graphics.PointF
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
|
||||
*/
|
||||
class ImageViewState(val scale: Float, center: PointF) : Serializable {
|
||||
private val centerX: Float = center.x
|
||||
private val centerY: Float = center.y
|
||||
|
||||
val center: PointF
|
||||
get() = PointF(centerX, centerY)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,53 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import kotlin.Throws
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* Interface for image decoding classes.
|
||||
*/
|
||||
interface Decoder {
|
||||
/**
|
||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||
* dimensions of the image must be returned.
|
||||
*
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param provider Provider of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
fun init(context: Context, provider: InputProvider): Point
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||
* thread so it can safely load the image on the current thread. It is called from
|
||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||
* called concurrently.
|
||||
*
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
*
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
val isReady: Boolean
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any
|
||||
* resources still in use.
|
||||
*/
|
||||
fun recycle()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.davemorrissey.labs.subscaleview.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
import kotlin.Throws
|
||||
import com.davemorrissey.labs.subscaleview.provider.InputProvider
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import java.lang.Exception
|
||||
|
||||
class ImageDecoder @JvmOverloads constructor(
|
||||
private val cropBorders: Boolean,
|
||||
bitmapConfig: Bitmap.Config? = null
|
||||
) : Decoder {
|
||||
init {
|
||||
val globalBitmapConfig = SubsamplingScaleImageView.preferredBitmapConfig
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the decoder. When possible, perform initial setup work once in this method. The
|
||||
* dimensions of the image must be returned.
|
||||
*
|
||||
* @param context Application context. A reference may be held, but must be cleared on recycle.
|
||||
* @param provider Provider of the image.
|
||||
* @return Dimensions of the image.
|
||||
* @throws Exception if initialisation fails.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
override fun init(context: Context, provider: InputProvider): Point {
|
||||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a region of the image with the given sample size. This method is called off the UI
|
||||
* thread so it can safely load the image on the current thread. It is called from
|
||||
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
|
||||
* implementations must be thread safe. Adding `synchronized` to the method signature
|
||||
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
|
||||
* called concurrently.
|
||||
*
|
||||
* @param sRect Source image rectangle to decode.
|
||||
* @param sampleSize Sample size.
|
||||
* @return The decoded region. It is safe to return null if decoding fails.
|
||||
*/
|
||||
override fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap {
|
||||
TODO()
|
||||
}
|
||||
|
||||
/**
|
||||
* Status check. Should return false before initialisation and after recycle.
|
||||
*
|
||||
* @return true if the decoder is ready to be used.
|
||||
*/
|
||||
override val isReady: Boolean = TODO()
|
||||
|
||||
/**
|
||||
* This method will be called when the decoder is no longer required. It should clean up any
|
||||
* resources still in use.
|
||||
*/
|
||||
override fun recycle() {
|
||||
TODO()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import java.io.IOException
|
||||
|
||||
class AssetInputProvider(context: Context, val assetName: String) : InputProvider {
|
||||
private val assets = context.assets
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = assets.open(assetName)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.Throws
|
||||
|
||||
interface InputProvider {
|
||||
@Throws(IOException::class)
|
||||
fun openStream(): InputStream?
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import kotlin.Throws
|
||||
|
||||
/**
|
||||
* Provider from an already open input stream.
|
||||
*
|
||||
*
|
||||
* Please keep in mind this provider is not reusable.
|
||||
*/
|
||||
class OpenStreamProvider(private val stream: InputStream) : InputProvider {
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = stream
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import java.io.IOException
|
||||
|
||||
class ResourceInputProvider(context: Context, val resource: Int) : InputProvider {
|
||||
private val resources = context.resources
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = resources.openRawResource(resource)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.davemorrissey.labs.subscaleview.provider
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import java.io.IOException
|
||||
import kotlin.Throws
|
||||
|
||||
class UriInputProvider(context: Context, val uri: Uri) : InputProvider {
|
||||
private val resolver = context.contentResolver
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun openStream() = resolver.openInputStream(uri)
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package com.hippo
|
||||
import java.io.FileDescriptor
|
||||
|
||||
object Native {
|
||||
@JvmStatic
|
||||
fun initialize() {
|
||||
System.loadLibrary("ehviewer")
|
||||
}
|
||||
|
||||
@@ -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,23 +1,8 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
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 {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,21 +246,7 @@ public final class DiskLruCache implements Closeable {
|
||||
if (!from.renameTo(to)) {
|
||||
throw new IOException();
|
||||
}
|
||||
} 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 String inputStreamToString(InputStream in) throws IOException {
|
||||
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
|
||||
@@ -343,7 +329,21 @@ public final class DiskLruCache implements Closeable {
|
||||
} else {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the initial size and collects garbage as a part of opening the
|
||||
|
||||
@@ -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,484 +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.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.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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,31 +18,23 @@ 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.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;
|
||||
|
||||
private ImageBitmap imageBitmap;
|
||||
|
||||
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
||||
public UnikeryDrawable(ObservedTextView textView) {
|
||||
mTextView = textView;
|
||||
mTextView.setOnWindowAttachListener(this);
|
||||
mConaco = conaco;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,23 +44,29 @@ 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() {
|
||||
setDrawable(null);
|
||||
if (imageBitmap != null) {
|
||||
imageBitmap.release();
|
||||
imageBitmap = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,65 +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) {
|
||||
Drawable drawable;
|
||||
try {
|
||||
drawable = value.getDrawable();
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "The ImageBitmap is recycled", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onGetValue(Drawable drawable) {
|
||||
clearDrawable();
|
||||
|
||||
setDrawable(drawable);
|
||||
imageBitmap = value;
|
||||
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable)
|
||||
animatedImageDrawable.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
@@ -1,593 +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.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 trustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||
}
|
||||
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));
|
||||
}
|
||||
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,353 @@
|
||||
/*
|
||||
* 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.app.ActivityManager
|
||||
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.content.getSystemService
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
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.ReadableTime
|
||||
import com.hippo.yorozuya.FileUtils
|
||||
import com.hippo.yorozuya.IntIdGenerator
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Cache
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.File
|
||||
import java.net.Proxy
|
||||
import java.security.KeyStore
|
||||
import java.util.Arrays
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderFactory {
|
||||
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() }
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
override fun onCreate() {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
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<SceneApplication>.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)
|
||||
|
||||
// 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())
|
||||
|
||||
GlobalScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
GlobalScope.launch {
|
||||
theDawnOfNewDay()
|
||||
}
|
||||
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0))
|
||||
initialized = true
|
||||
}
|
||||
|
||||
private suspend 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
|
||||
)
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val referer = EhUrl.REFERER_E
|
||||
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
|
||||
val call = okHttpClient.newCall(request)
|
||||
try {
|
||||
call.execute().use { response ->
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
if (!locked)
|
||||
locked_last_leave_time = System.currentTimeMillis() / 1000
|
||||
locked = true
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun newImageLoader(): ImageLoader {
|
||||
return ImageLoader.Builder(this).apply {
|
||||
okHttpClient(okHttpClient)
|
||||
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
|
||||
// Coil spawns a new thread for every image load by default
|
||||
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8))
|
||||
decoderDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||
transformationDispatcher(Dispatchers.IO.limitedParallelism(2))
|
||||
}.build()
|
||||
}
|
||||
|
||||
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() }
|
||||
|
||||
@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.proxy(Proxy.NO_PROXY)
|
||||
}
|
||||
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() }
|
||||
}
|
||||
}
|
||||
@@ -234,7 +234,7 @@ public class EhDB {
|
||||
|
||||
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
|
||||
LocalFavoritesDao dao = db.localFavoritesDao();
|
||||
List<LocalFavoriteInfo> list = dao.list();
|
||||
List<LocalFavoriteInfo> list = dao.list("%" + query + "%");
|
||||
return new ArrayList<>(list);
|
||||
}
|
||||
|
||||
@@ -293,11 +293,6 @@ public class EhDB {
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
dao.update(quickSearch);
|
||||
}
|
||||
|
||||
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
|
||||
QuickSearchDao dao = db.quickSearchDao();
|
||||
dao.delete(quickSearch);
|
||||
@@ -327,25 +322,22 @@ public class EhDB {
|
||||
dao.update(list);
|
||||
}
|
||||
|
||||
public static synchronized List<HistoryInfo> getHistoryList() {
|
||||
return db.historyDao().list();
|
||||
}
|
||||
|
||||
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
|
||||
return db.historyDao().listLazy();
|
||||
}
|
||||
|
||||
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
|
||||
HistoryDao dao = db.historyDao();
|
||||
HistoryInfo info = dao.load(galleryInfo.gid);
|
||||
if (null != info) {
|
||||
// Update time
|
||||
info.time = System.currentTimeMillis();
|
||||
HistoryInfo info;
|
||||
if (galleryInfo instanceof HistoryInfo) {
|
||||
info = (HistoryInfo) galleryInfo;
|
||||
} else {
|
||||
info = new HistoryInfo(galleryInfo);
|
||||
}
|
||||
info.time = System.currentTimeMillis();
|
||||
if (null != dao.load(info.gid)) {
|
||||
dao.update(info);
|
||||
} else {
|
||||
// New history
|
||||
info = new HistoryInfo(galleryInfo);
|
||||
info.time = System.currentTimeMillis();
|
||||
dao.insert(info);
|
||||
}
|
||||
}
|
||||
@@ -398,11 +390,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 +405,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 +436,7 @@ public class EhDB {
|
||||
IOUtils.closeQuietly(is);
|
||||
IOUtils.closeQuietly(os);
|
||||
}
|
||||
|
||||
// Delete failed file
|
||||
return false;
|
||||
} finally {
|
||||
@@ -460,6 +450,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 +466,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,16 +475,17 @@ 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);
|
||||
@@ -565,6 +559,12 @@ public class EhDB {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Delete temp database
|
||||
if (oldRoomDatabase.isOpen()) {
|
||||
oldRoomDatabase.close();
|
||||
}
|
||||
context.deleteDatabase(tmpDBName);
|
||||
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(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);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,19 +27,13 @@ import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.hippo.ehviewer.client.EhConfig;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.glgallery.GalleryView;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -53,18 +47,15 @@ public class Settings {
|
||||
|
||||
public static final String KEY_ACCOUNT = "account";
|
||||
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
|
||||
public static final int THEME_LIGHT = 1;
|
||||
public static final int THEME_SYSTEM = -1;
|
||||
public static final int THEME_BLACK = 2;
|
||||
public static final String KEY_APPLY_NAV_BAR_THEME_COLOR = "apply_nav_bar_theme_color";
|
||||
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";
|
||||
/********************
|
||||
@@ -78,7 +69,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";
|
||||
@@ -86,47 +76,42 @@ public class Settings {
|
||||
public static final String KEY_IMAGE_RESOLUTION = "image_size";
|
||||
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
|
||||
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
|
||||
public static final String KEY_ENABLE_ANALYTICS = "enable_analytics";
|
||||
/********************
|
||||
****** Advanced
|
||||
********************/
|
||||
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
|
||||
public static final String KEY_SECURITY = "require_unlock";
|
||||
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
|
||||
public static final String DEFAULT_SECURITY = "";
|
||||
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
|
||||
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
|
||||
public static final int DEFAULT_READ_CACHE_SIZE = 320;
|
||||
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_HIDE_HV_EVENTS = "hide_hv_events";
|
||||
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
|
||||
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;
|
||||
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
|
||||
private static final String KEY_AVATAR = "avatar";
|
||||
private static final String DEFAULT_AVATAR = null;
|
||||
private static final String KEY_SHOW_WARNING = "show_warning";
|
||||
private static final boolean DEFAULT_SHOW_WARNING = true;
|
||||
private static final String KEY_REMOVE_IMAGE_FILES = "include_pic";
|
||||
private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true;
|
||||
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
|
||||
private static final boolean DEFAULT_NEED_SIGN_IN = true;
|
||||
private static final String KEY_SELECT_SITE = "select_site";
|
||||
private static final boolean DEFAULT_SELECT_SITE = true;
|
||||
private static final String KEY_QUICK_SEARCH_TIP = "quick_search_tip";
|
||||
private static final boolean DEFAULT_QUICK_SEARCH_TIP = true;
|
||||
private static final int DEFAULT_THEME = THEME_SYSTEM;
|
||||
private static final boolean DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
|
||||
private static final int DEFAULT_GALLERY_SITE = 1;
|
||||
private static final String KEY_LAUNCH_PAGE = "launch_page";
|
||||
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_SHOW_JPN_TITLE = "show_jpn_title";
|
||||
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
|
||||
@@ -139,21 +124,14 @@ public class Settings {
|
||||
private static final boolean DEFAULT_METERED_NETWORK_WARNING = false;
|
||||
private static final String KEY_APP_LINK_VERIFY_TIP = "app_link_verify_tip";
|
||||
private static final boolean DEFAULT_APP_LINK_VERIFY_TIP = false;
|
||||
private static final String KEY_NIGHT_MODE = "night_mode";
|
||||
private static final String DEFAULT_NIGHT_MODE = "-1";
|
||||
private static final String KEY_E_INK_MODE = "e_ink_mode_2";
|
||||
private static final boolean DEFAULT_E_INK_MODE = false;
|
||||
/********************
|
||||
****** Read
|
||||
********************/
|
||||
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
|
||||
private static final int DEFAULT_SCREEN_ROTATION = 0;
|
||||
private static final String KEY_READING_DIRECTION = "reading_direction";
|
||||
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
|
||||
private static final String KEY_PAGE_SCALING = "page_scaling";
|
||||
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
|
||||
private static final String KEY_START_POSITION = "start_position";
|
||||
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
|
||||
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
|
||||
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
|
||||
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
|
||||
@@ -230,22 +208,6 @@ public class Settings {
|
||||
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
|
||||
private static final String KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
|
||||
private static final int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
|
||||
/********************
|
||||
****** Analytics
|
||||
********************/
|
||||
private static final String KEY_ASK_ANALYTICS = "ask_analytics";
|
||||
private static final boolean DEFAULT_ASK_ANALYTICS = true;
|
||||
private static final boolean DEFAULT_ENABLE_ANALYTICS = false;
|
||||
private static final String KEY_USER_ID = "user_id";
|
||||
private static final String FILENAME_USER_ID = ".user_id";
|
||||
private static final int LENGTH_USER_ID = 32;
|
||||
/********************
|
||||
****** 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;
|
||||
private static final String KEY_SAVE_CRASH_LOG = "save_crash_log";
|
||||
private static final boolean DEFAULT_SAVE_CRASH_LOG = true;
|
||||
@@ -258,17 +220,6 @@ public class Settings {
|
||||
private static final String DEFAULT_PROXY_IP = null;
|
||||
private static final String KEY_PROXY_PORT = "proxy_port";
|
||||
private static final int DEFAULT_PROXY_PORT = -1;
|
||||
/********************
|
||||
****** Guide
|
||||
********************/
|
||||
private static final String KEY_GUIDE_QUICK_SEARCH = "guide_quick_search";
|
||||
private static final boolean DEFAULT_GUIDE_QUICK_SEARCH = true;
|
||||
private static final String KEY_GUIDE_COLLECTIONS = "guide_collections";
|
||||
private static final boolean DEFAULT_GUIDE_COLLECTIONS = true;
|
||||
private static final String KEY_GUIDE_DOWNLOAD_THUMB = "guide_download_thumb";
|
||||
private static final boolean DEFAULT_GUIDE_DOWNLOAD_THUMB = true;
|
||||
private static final String KEY_GUIDE_DOWNLOAD_LABELS = "guide_download_labels";
|
||||
private static final boolean DEFAULT_GUIDE_DOWNLOAD_LABELS = true;
|
||||
private static final String KEY_GUIDE_GALLERY = "guide_gallery";
|
||||
private static final boolean DEFAULT_GUIDE_GALLERY = true;
|
||||
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
|
||||
@@ -279,19 +230,19 @@ public class Settings {
|
||||
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;
|
||||
public static SharedPreferences sSettingsPre;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
private static Context sContext;
|
||||
private static SharedPreferences sSettingsPre;
|
||||
private static EhConfig sEhConfig;
|
||||
|
||||
public static void initialize(Context context) {
|
||||
sContext = context.getApplicationContext();
|
||||
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
|
||||
sEhConfig = loadEhConfig();
|
||||
fixDefaultValue(context);
|
||||
fixDefaultValue();
|
||||
}
|
||||
|
||||
private static void fixDefaultValue(Context context) {
|
||||
private static void fixDefaultValue() {
|
||||
if ("CN".equals(Locale.getDefault().getCountry())) {
|
||||
// Enable domain fronting if the country is CN
|
||||
if (!sSettingsPre.contains(KEY_BUILT_IN_HOSTS)) {
|
||||
@@ -309,7 +260,6 @@ public class Settings {
|
||||
}
|
||||
|
||||
public static Locale getLocale() {
|
||||
Locale locale = null;
|
||||
String language = Settings.getAppLanguage();
|
||||
if (language != null && !language.equals("system")) {
|
||||
return Locale.forLanguageTag(language);
|
||||
@@ -354,32 +304,6 @@ public class Settings {
|
||||
sSettingsPre.edit().putInt(key, value).apply();
|
||||
}
|
||||
|
||||
public static long getLong(String key, long defValue) {
|
||||
try {
|
||||
return sSettingsPre.getLong(key, defValue);
|
||||
} catch (ClassCastException e) {
|
||||
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static void putLong(String key, long value) {
|
||||
sSettingsPre.edit().putLong(key, value).apply();
|
||||
}
|
||||
|
||||
public static float getFloat(String key, float defValue) {
|
||||
try {
|
||||
return sSettingsPre.getFloat(key, defValue);
|
||||
} catch (ClassCastException e) {
|
||||
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
|
||||
return defValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static void putFloat(String key, float value) {
|
||||
sSettingsPre.edit().putFloat(key, value).apply();
|
||||
}
|
||||
|
||||
public static String getString(String key, String defValue) {
|
||||
try {
|
||||
return sSettingsPre.getString(key, defValue);
|
||||
@@ -421,14 +345,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);
|
||||
@@ -438,22 +354,10 @@ public class Settings {
|
||||
putString(KEY_DISPLAY_NAME, value);
|
||||
}
|
||||
|
||||
public static String getAvatar() {
|
||||
return getString(KEY_AVATAR, DEFAULT_AVATAR);
|
||||
}
|
||||
|
||||
public static void putAvatar(String value) {
|
||||
putString(KEY_AVATAR, value);
|
||||
}
|
||||
|
||||
public static boolean getShowWarning() {
|
||||
return getBoolean(KEY_SHOW_WARNING, DEFAULT_SHOW_WARNING);
|
||||
}
|
||||
|
||||
public static void putShowWarning(boolean value) {
|
||||
putBoolean(KEY_SHOW_WARNING, value);
|
||||
}
|
||||
|
||||
public static boolean getRemoveImageFiles() {
|
||||
return getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES);
|
||||
}
|
||||
@@ -486,14 +390,6 @@ public class Settings {
|
||||
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
|
||||
}
|
||||
|
||||
public static void putTheme(int theme) {
|
||||
putIntToStr(KEY_THEME, theme);
|
||||
}
|
||||
|
||||
public static boolean getApplyNavBarThemeColor() {
|
||||
return getBoolean(KEY_APPLY_NAV_BAR_THEME_COLOR, DEFAULT_APPLY_NAV_BAR_THEME_COLOR);
|
||||
}
|
||||
|
||||
public static int getGallerySite() {
|
||||
return getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE);
|
||||
}
|
||||
@@ -504,17 +400,13 @@ public class Settings {
|
||||
|
||||
public static String getLaunchPageGalleryListSceneAction() {
|
||||
int value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE);
|
||||
switch (value) {
|
||||
default:
|
||||
case 0:
|
||||
return GalleryListScene.ACTION_HOMEPAGE;
|
||||
case 1:
|
||||
return GalleryListScene.ACTION_SUBSCRIPTION;
|
||||
case 2:
|
||||
return GalleryListScene.ACTION_WHATS_HOT;
|
||||
case 3:
|
||||
return GalleryListScene.ACTION_TOP_LIST;
|
||||
}
|
||||
return switch (value) {
|
||||
case 0 -> GalleryListScene.ACTION_HOMEPAGE;
|
||||
case 1 -> GalleryListScene.ACTION_SUBSCRIPTION;
|
||||
case 2 -> GalleryListScene.ACTION_WHATS_HOT;
|
||||
case 3 -> GalleryListScene.ACTION_TOP_LIST;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + value);
|
||||
};
|
||||
}
|
||||
|
||||
public static int getListMode() {
|
||||
@@ -527,40 +419,38 @@ public class Settings {
|
||||
|
||||
@DimenRes
|
||||
public static int getDetailSizeResId() {
|
||||
switch (getDetailSize()) {
|
||||
default:
|
||||
case 0:
|
||||
return R.dimen.gallery_list_column_width_long;
|
||||
case 1:
|
||||
return R.dimen.gallery_list_column_width_short;
|
||||
}
|
||||
return switch (getDetailSize()) {
|
||||
case 0 -> R.dimen.gallery_list_column_width_long;
|
||||
case 1 -> R.dimen.gallery_list_column_width_short;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + getDetailSize());
|
||||
};
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
@@ -577,36 +467,22 @@ public class Settings {
|
||||
putBoolean(KEY_SHOW_TAG_TRANSLATIONS, value);
|
||||
}
|
||||
|
||||
public static int getDefaultCategories() {
|
||||
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
|
||||
public static int getPreviewNum() {
|
||||
return getInt(KEY_PREVIEW_NUM, DEFAULT_PREVIEW_NUM);
|
||||
}
|
||||
|
||||
public static void putDefaultCategories(int value) {
|
||||
sEhConfig.defaultCategories = value;
|
||||
sEhConfig.setDirty();
|
||||
putInt(KEY_DEFAULT_CATEGORIES, value);
|
||||
public static int getDefaultCategories() {
|
||||
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
|
||||
}
|
||||
|
||||
public static int getExcludedTagNamespaces() {
|
||||
return getInt(KEY_EXCLUDED_TAG_NAMESPACES, DEFAULT_EXCLUDED_TAG_NAMESPACES);
|
||||
}
|
||||
|
||||
public static void putExcludedTagNamespaces(int value) {
|
||||
sEhConfig.excludedNamespaces = value;
|
||||
sEhConfig.setDirty();
|
||||
putInt(KEY_EXCLUDED_TAG_NAMESPACES, value);
|
||||
}
|
||||
|
||||
public static String getExcludedLanguages() {
|
||||
return getString(KEY_EXCLUDED_LANGUAGES, DEFAULT_EXCLUDED_LANGUAGES);
|
||||
}
|
||||
|
||||
public static void putExcludedLanguages(String value) {
|
||||
sEhConfig.excludedLanguages = value;
|
||||
sEhConfig.setDirty();
|
||||
putString(KEY_EXCLUDED_LANGUAGES, value);
|
||||
}
|
||||
|
||||
public static boolean getMeteredNetworkWarning() {
|
||||
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
|
||||
}
|
||||
@@ -627,29 +503,14 @@ public class Settings {
|
||||
putIntToStr(KEY_SCREEN_ROTATION, value);
|
||||
}
|
||||
|
||||
@GalleryView.LayoutMode
|
||||
public static int getReadingDirection() {
|
||||
return GalleryView.sanitizeLayoutMode(getIntFromStr(KEY_READING_DIRECTION, DEFAULT_READING_DIRECTION));
|
||||
}
|
||||
|
||||
public static void putReadingDirection(int value) {
|
||||
putIntToStr(KEY_READING_DIRECTION, value);
|
||||
}
|
||||
|
||||
@GalleryView.ScaleMode
|
||||
public static int getPageScaling() {
|
||||
return GalleryView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
|
||||
}
|
||||
|
||||
public static void putPageScaling(int value) {
|
||||
putIntToStr(KEY_PAGE_SCALING, value);
|
||||
}
|
||||
|
||||
@GalleryView.StartPosition
|
||||
public static int getStartPosition() {
|
||||
return GalleryView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
|
||||
}
|
||||
|
||||
public static void putStartPosition(int value) {
|
||||
putIntToStr(KEY_START_POSITION, value);
|
||||
}
|
||||
@@ -746,25 +607,16 @@ public class Settings {
|
||||
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
|
||||
}
|
||||
|
||||
public static void putEnabledSecurity(boolean value) {
|
||||
putBoolean(KEY_READING_FULLSCREEN, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static UniFile getDownloadLocation() {
|
||||
UniFile dir = null;
|
||||
try {
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
|
||||
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
|
||||
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
|
||||
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
|
||||
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
|
||||
dir = UniFile.fromUri(sContext, builder.build());
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
UniFile dir;
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
|
||||
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
|
||||
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
|
||||
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
|
||||
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
|
||||
dir = UniFile.fromUri(sContext, builder.build());
|
||||
return dir != null ? dir : UniFile.fromFile(AppConfig.getDefaultDownloadDir());
|
||||
}
|
||||
|
||||
@@ -815,26 +667,14 @@ public class Settings {
|
||||
return getIntFromStr(KEY_MULTI_THREAD_DOWNLOAD, DEFAULT_MULTI_THREAD_DOWNLOAD);
|
||||
}
|
||||
|
||||
public static void putMultiThreadDownload(int value) {
|
||||
putIntToStr(KEY_MULTI_THREAD_DOWNLOAD, value);
|
||||
}
|
||||
|
||||
public static int getDownloadDelay() {
|
||||
return getIntFromStr(KEY_DOWNLOAD_DELAY, DEFAULT_DOWNLOAD_DELAY);
|
||||
}
|
||||
|
||||
public static void putDownloadDelay(int value) {
|
||||
putIntToStr(KEY_DOWNLOAD_DELAY, value);
|
||||
}
|
||||
|
||||
public static int getPreloadImage() {
|
||||
return getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE);
|
||||
}
|
||||
|
||||
public static void putPreloadImage(int value) {
|
||||
putIntToStr(KEY_PRELOAD_IMAGE, value);
|
||||
}
|
||||
|
||||
public static String getImageResolution() {
|
||||
return getString(KEY_IMAGE_RESOLUTION, DEFAULT_IMAGE_RESOLUTION);
|
||||
}
|
||||
@@ -849,10 +689,6 @@ public class Settings {
|
||||
return getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE);
|
||||
}
|
||||
|
||||
public static void putDownloadOriginImage(boolean value) {
|
||||
putBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, value);
|
||||
}
|
||||
|
||||
public static String[] getFavCat() {
|
||||
String[] favCat = new String[10];
|
||||
favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0);
|
||||
@@ -947,104 +783,10 @@ public class Settings {
|
||||
putInt(KEY_DEFAULT_FAV_SLOT, value);
|
||||
}
|
||||
|
||||
public static boolean getAskAnalytics() {
|
||||
return getBoolean(KEY_ASK_ANALYTICS, DEFAULT_ASK_ANALYTICS);
|
||||
}
|
||||
|
||||
public static void putAskAnalytics(boolean value) {
|
||||
putBoolean(KEY_ASK_ANALYTICS, value);
|
||||
}
|
||||
|
||||
public static boolean getEnableAnalytics() {
|
||||
return getBoolean(KEY_ENABLE_ANALYTICS, DEFAULT_ENABLE_ANALYTICS);
|
||||
}
|
||||
|
||||
public static void putEnableAnalytics(boolean value) {
|
||||
putBoolean(KEY_ENABLE_ANALYTICS, value);
|
||||
}
|
||||
|
||||
public static String getUserID() {
|
||||
boolean writeXml = false;
|
||||
boolean writeFile = false;
|
||||
String userID = getString(KEY_USER_ID, null);
|
||||
File file = AppConfig.getFileInExternalAppDir(FILENAME_USER_ID);
|
||||
if (!isValidUserID(userID)) {
|
||||
writeXml = true;
|
||||
// Get use ID from out sd card file
|
||||
userID = FileUtils.read(file);
|
||||
if (!isValidUserID(userID)) {
|
||||
writeFile = true;
|
||||
userID = generateUserID();
|
||||
}
|
||||
} else {
|
||||
writeFile = true;
|
||||
}
|
||||
|
||||
if (writeXml) {
|
||||
putString(KEY_USER_ID, userID);
|
||||
}
|
||||
if (writeFile) {
|
||||
FileUtils.write(file, userID);
|
||||
}
|
||||
|
||||
return userID;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String generateUserID() {
|
||||
int length = LENGTH_USER_ID;
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (MathUtils.random(0, ('9' - '0' + 1) + ('z' - 'a' + 1)) <= '9' - '0') {
|
||||
sb.append((char) MathUtils.random('0', '9' + 1));
|
||||
} else {
|
||||
sb.append((char) MathUtils.random('a', 'z' + 1));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean isValidUserID(@Nullable String userID) {
|
||||
if (null == userID || LENGTH_USER_ID != userID.length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < LENGTH_USER_ID; i++) {
|
||||
char ch = userID.charAt(i);
|
||||
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static int getSkipUpdateVersion() {
|
||||
return getInt(KEY_SKIP_UPDATE_VERSION, DEFAULT_SKIP_UPDATE_VERSION);
|
||||
}
|
||||
|
||||
public static void putSkipUpdateVersion(int value) {
|
||||
putInt(KEY_SKIP_UPDATE_VERSION, value);
|
||||
}
|
||||
|
||||
public static boolean getSaveParseErrorBody() {
|
||||
return getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY);
|
||||
}
|
||||
|
||||
public static void putSaveParseErrorBody(boolean value) {
|
||||
putBoolean(KEY_SAVE_PARSE_ERROR_BODY, value);
|
||||
}
|
||||
|
||||
public static boolean getSaveCrashLog() {
|
||||
return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG);
|
||||
}
|
||||
@@ -1061,10 +803,6 @@ public class Settings {
|
||||
return getInt(KEY_SECURITY_DELAY, 0);
|
||||
}
|
||||
|
||||
public static boolean getEnableFingerprint() {
|
||||
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
|
||||
}
|
||||
|
||||
public static int getReadCacheSize() {
|
||||
return getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE);
|
||||
}
|
||||
@@ -1089,10 +827,6 @@ public class Settings {
|
||||
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
|
||||
}
|
||||
|
||||
public static void putAppLanguage(String value) {
|
||||
putString(KEY_APP_LANGUAGE, value);
|
||||
}
|
||||
|
||||
public static int getProxyType() {
|
||||
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
|
||||
}
|
||||
@@ -1117,38 +851,6 @@ public class Settings {
|
||||
putInt(KEY_PROXY_PORT, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideQuickSearch() {
|
||||
return getBoolean(KEY_GUIDE_QUICK_SEARCH, DEFAULT_GUIDE_QUICK_SEARCH);
|
||||
}
|
||||
|
||||
public static void putGuideQuickSearch(boolean value) {
|
||||
putBoolean(KEY_GUIDE_QUICK_SEARCH, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideCollections() {
|
||||
return getBoolean(KEY_GUIDE_COLLECTIONS, DEFAULT_GUIDE_COLLECTIONS);
|
||||
}
|
||||
|
||||
public static void putGuideCollections(boolean value) {
|
||||
putBoolean(KEY_GUIDE_COLLECTIONS, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideDownloadThumb() {
|
||||
return getBoolean(KEY_GUIDE_DOWNLOAD_THUMB, DEFAULT_GUIDE_DOWNLOAD_THUMB);
|
||||
}
|
||||
|
||||
public static void putGuideDownloadThumb(boolean value) {
|
||||
putBoolean(KEY_GUIDE_DOWNLOAD_THUMB, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideDownloadLabels() {
|
||||
return getBoolean(KEY_GUIDE_DOWNLOAD_LABELS, DEFAULT_GUIDE_DOWNLOAD_LABELS);
|
||||
}
|
||||
|
||||
public static void puttGuideDownloadLabels(boolean value) {
|
||||
putBoolean(KEY_GUIDE_DOWNLOAD_LABELS, value);
|
||||
}
|
||||
|
||||
public static boolean getGuideGallery() {
|
||||
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
|
||||
}
|
||||
|
||||
@@ -29,11 +29,12 @@ import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import com.hippo.ehviewer.client.EhUrlOpener;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.StageActivity;
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
|
||||
public final class UrlOpener {
|
||||
|
||||
private UrlOpener() {
|
||||
@@ -56,19 +57,19 @@ public final class UrlOpener {
|
||||
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
|
||||
if (result != null) {
|
||||
if (result.gid == galleryDetail.gid) {
|
||||
intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, result.page);
|
||||
intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, result.page);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
}
|
||||
} else if (url.startsWith("#c")) {
|
||||
try {
|
||||
intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
||||
intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
|
||||
context.startActivity(intent);
|
||||
return;
|
||||
} catch (NumberFormatException e) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
@@ -55,18 +54,18 @@ public class EhClient {
|
||||
public static final int METHOD_ARCHIVE_LIST = 17;
|
||||
public static final int METHOD_DOWNLOAD_ARCHIVE = 18;
|
||||
public static final int METHOD_VOTE_TAG = 19;
|
||||
|
||||
public static final int METHOD_GET_UCONFIG = 20;
|
||||
private static final OkHttpClient mOkHttpClient = EhApplication.getOkHttpClient();
|
||||
private final ThreadPoolExecutor mRequestThreadPool;
|
||||
private final OkHttpClient mOkHttpClient;
|
||||
|
||||
public EhClient(Context context) {
|
||||
public EhClient() {
|
||||
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
|
||||
mOkHttpClient = EhApplication.getOkHttpClient(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void execute(EhRequest request) {
|
||||
if (!request.isCancelled()) {
|
||||
Task task = new Task(request.getMethod(), request.getCallback(), request.getEhConfig());
|
||||
Task task = new Task(request.getMethod(), request.getCallback());
|
||||
task.executeOnExecutor(mRequestThreadPool, request.getArgs());
|
||||
request.task = task;
|
||||
} else {
|
||||
@@ -83,18 +82,17 @@ public class EhClient {
|
||||
void onCancel();
|
||||
}
|
||||
|
||||
public class Task extends AsyncTask<Object, Void, Object> {
|
||||
@SuppressWarnings("deprecation")
|
||||
public static class Task extends AsyncTask<Object, Void, Object> {
|
||||
|
||||
private final int mMethod;
|
||||
private final AtomicReference<Call> mCall = new AtomicReference<>();
|
||||
private final AtomicBoolean mStop = new AtomicBoolean();
|
||||
private Callback mCallback;
|
||||
private EhConfig mEhConfig;
|
||||
private Callback<Object> mCallback;
|
||||
|
||||
public Task(int method, Callback callback, EhConfig ehConfig) {
|
||||
public Task(int method, Callback<Object> callback) {
|
||||
mMethod = method;
|
||||
mCallback = callback;
|
||||
mEhConfig = ehConfig;
|
||||
}
|
||||
|
||||
// Called in Job thread
|
||||
@@ -107,10 +105,6 @@ public class EhClient {
|
||||
}
|
||||
}
|
||||
|
||||
public EhConfig getEhConfig() {
|
||||
return mEhConfig;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (!mStop.get()) {
|
||||
mStop.lazySet(true);
|
||||
@@ -133,7 +127,6 @@ public class EhClient {
|
||||
|
||||
// Clear
|
||||
mCallback = null;
|
||||
mEhConfig = null;
|
||||
mCall.lazySet(null);
|
||||
}
|
||||
}
|
||||
@@ -141,53 +134,51 @@ public class EhClient {
|
||||
@Override
|
||||
protected Object doInBackground(Object... params) {
|
||||
try {
|
||||
switch (mMethod) {
|
||||
case METHOD_SIGN_IN:
|
||||
return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
|
||||
case METHOD_GET_GALLERY_LIST:
|
||||
return EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_GALLERY_DETAIL:
|
||||
return EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_PREVIEW_SET:
|
||||
return EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_RATE_GALLERY:
|
||||
return EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
|
||||
case METHOD_GET_COMMENT_GALLERY:
|
||||
return EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
|
||||
case METHOD_GET_GALLERY_TOKEN:
|
||||
return EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
|
||||
case METHOD_GET_FAVORITES:
|
||||
return EhEngine.getFavorites(this, mOkHttpClient, (String) params[0], (Boolean) params[1]);
|
||||
case METHOD_ADD_FAVORITES:
|
||||
return EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
|
||||
case METHOD_ADD_FAVORITES_RANGE:
|
||||
return EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
|
||||
case METHOD_MODIFY_FAVORITES:
|
||||
return EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2], (Boolean) params[3]);
|
||||
case METHOD_GET_TORRENT_LIST:
|
||||
return EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||
case METHOD_GET_PROFILE:
|
||||
return EhEngine.getProfile(this, mOkHttpClient);
|
||||
case METHOD_VOTE_COMMENT:
|
||||
return EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
|
||||
case METHOD_IMAGE_SEARCH:
|
||||
return EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
|
||||
case METHOD_ARCHIVE_LIST:
|
||||
return EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||
case METHOD_DOWNLOAD_ARCHIVE:
|
||||
return EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
|
||||
case METHOD_VOTE_TAG:
|
||||
return EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
|
||||
default:
|
||||
return new IllegalStateException("Can't detect method " + mMethod);
|
||||
}
|
||||
return switch (mMethod) {
|
||||
case METHOD_SIGN_IN ->
|
||||
EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
|
||||
case METHOD_GET_GALLERY_LIST ->
|
||||
EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_GALLERY_DETAIL ->
|
||||
EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_PREVIEW_SET ->
|
||||
EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_GET_RATE_GALLERY ->
|
||||
EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
|
||||
case METHOD_GET_COMMENT_GALLERY ->
|
||||
EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
|
||||
case METHOD_GET_GALLERY_TOKEN ->
|
||||
EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
|
||||
case METHOD_GET_FAVORITES ->
|
||||
EhEngine.getFavorites(this, mOkHttpClient, (String) params[0]);
|
||||
case METHOD_ADD_FAVORITES ->
|
||||
EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
|
||||
case METHOD_ADD_FAVORITES_RANGE ->
|
||||
EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
|
||||
case METHOD_MODIFY_FAVORITES ->
|
||||
EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2]);
|
||||
case METHOD_GET_TORRENT_LIST ->
|
||||
EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||
case METHOD_GET_PROFILE -> EhEngine.getProfile(this, mOkHttpClient);
|
||||
case METHOD_VOTE_COMMENT ->
|
||||
EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
|
||||
case METHOD_IMAGE_SEARCH ->
|
||||
EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
|
||||
case METHOD_ARCHIVE_LIST ->
|
||||
EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
|
||||
case METHOD_DOWNLOAD_ARCHIVE ->
|
||||
EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
|
||||
case METHOD_VOTE_TAG ->
|
||||
EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
|
||||
case METHOD_GET_UCONFIG -> EhEngine.getUConfig(this, mOkHttpClient);
|
||||
default -> new IllegalStateException("Can't detect method " + mMethod);
|
||||
};
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void onPostExecute(Object result) {
|
||||
if (mCallback != null) {
|
||||
@@ -205,7 +196,6 @@ public class EhClient {
|
||||
|
||||
// Clear
|
||||
mCallback = null;
|
||||
mEhConfig = null;
|
||||
mCall.lazySet(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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", false),
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -185,8 +185,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -263,8 +262,7 @@ public class EhEngine {
|
||||
Headers headers = null;
|
||||
GalleryListParser.Result result;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -326,8 +324,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -354,14 +351,13 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
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) {
|
||||
@@ -387,8 +383,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -429,8 +424,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -466,8 +460,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -510,8 +503,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -524,7 +516,7 @@ public class EhEngine {
|
||||
}
|
||||
|
||||
public static FavoritesParser.Result getFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||
String url, boolean callApi) throws Throwable {
|
||||
String url) throws Throwable {
|
||||
String referer = EhUrl.getReferer();
|
||||
Log.d(TAG, url);
|
||||
Request request = new EhRequestBuilder(url, referer).build();
|
||||
@@ -539,8 +531,7 @@ public class EhEngine {
|
||||
Headers headers = null;
|
||||
FavoritesParser.Result result;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -592,8 +583,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -617,7 +607,7 @@ public class EhEngine {
|
||||
}
|
||||
|
||||
public static FavoritesParser.Result modifyFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||
String url, long[] gidArray, int dstCat, boolean callApi) throws Throwable {
|
||||
String url, long[] gidArray, int dstCat) throws Throwable {
|
||||
String catStr;
|
||||
if (dstCat == -1) {
|
||||
catStr = "delete";
|
||||
@@ -648,8 +638,7 @@ public class EhEngine {
|
||||
Headers headers = null;
|
||||
FavoritesParser.Result result;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -681,8 +670,7 @@ public class EhEngine {
|
||||
Headers headers = null;
|
||||
Pair<String, String>[] result;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -712,8 +700,7 @@ public class EhEngine {
|
||||
Headers headers = null;
|
||||
Pair<String, Pair<String, String>[]> result;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -754,8 +741,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -788,8 +774,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -816,8 +801,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -829,6 +813,55 @@ public class EhEngine {
|
||||
}
|
||||
}
|
||||
|
||||
public static Void getUConfig(@Nullable EhClient.Task task,
|
||||
OkHttpClient okHttpClient) throws Throwable {
|
||||
String url = EhUrl.getUConfigUrl();
|
||||
Log.d(TAG, url);
|
||||
Request request = new EhRequestBuilder(url, null).build();
|
||||
Call call = okHttpClient.newCall(request);
|
||||
|
||||
// Put call
|
||||
if (null != task) {
|
||||
task.setCall(call);
|
||||
}
|
||||
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
request = response.request();
|
||||
throwException(call, code, headers, body, null);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
throwException(call, code, headers, body, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// TODO Use a better way to handle 302
|
||||
if (!request.url().toString().equals(url)) {
|
||||
Log.d(TAG, "Redirected! Retry " + url);
|
||||
request = new EhRequestBuilder(url, null).build();
|
||||
call = okHttpClient.newCall(request);
|
||||
if (null != task) task.setCall(call);
|
||||
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
throwException(call, code, headers, body, null);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
throwException(call, code, headers, body, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static VoteCommentParser.Result voteComment(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||
long apiUid, String apiKey, long gid, String token, long commentId, int commentVote) throws Throwable {
|
||||
final JSONObject json = new JSONObject();
|
||||
@@ -857,8 +890,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -898,8 +930,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -962,8 +993,7 @@ public class EhEngine {
|
||||
Headers headers = null;
|
||||
GalleryListParser.Result result;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
|
||||
Log.d(TAG, "" + response.request().url());
|
||||
|
||||
@@ -997,8 +1027,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
@@ -1039,8 +1068,7 @@ public class EhEngine {
|
||||
String body = null;
|
||||
Headers headers = null;
|
||||
int code = -1;
|
||||
try {
|
||||
Response response = call.execute();
|
||||
try (Response response = call.execute()) {
|
||||
code = response.code();
|
||||
headers = response.headers();
|
||||
body = response.body().string();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -32,10 +31,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;
|
||||
@@ -51,10 +48,10 @@ public class EhUtils {
|
||||
|
||||
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
|
||||
public static final Pattern PATTERN_TITLE_PREFIX = Pattern.compile(
|
||||
"^(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*");
|
||||
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*");
|
||||
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
|
||||
public static final Pattern PATTERN_TITLE_SUFFIX = Pattern.compile(
|
||||
"(?:\\s+ch.[\\s\\d-]+)?(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*$",
|
||||
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static final int[] CATEGORY_VALUES = {
|
||||
@@ -68,6 +65,7 @@ public class EhUtils {
|
||||
EhConfig.ASIAN_PORN,
|
||||
EhConfig.NON_H,
|
||||
EhConfig.WESTERN,
|
||||
PRIVATE,
|
||||
UNKNOWN};
|
||||
|
||||
private static final String[][] CATEGORY_STRINGS = {
|
||||
@@ -81,6 +79,7 @@ public class EhUtils {
|
||||
new String[]{"asianporn", "Asian Porn"},
|
||||
new String[]{"non-h"},
|
||||
new String[]{"western"},
|
||||
new String[]{"private"},
|
||||
new String[]{"unknown"}
|
||||
};
|
||||
|
||||
@@ -106,41 +105,30 @@ public class EhUtils {
|
||||
}
|
||||
|
||||
public static int getCategoryColor(int category) {
|
||||
switch (category) {
|
||||
case EhConfig.DOUJINSHI:
|
||||
return BG_COLOR_DOUJINSHI;
|
||||
case EhConfig.MANGA:
|
||||
return BG_COLOR_MANGA;
|
||||
case EhConfig.ARTIST_CG:
|
||||
return BG_COLOR_ARTIST_CG;
|
||||
case EhConfig.GAME_CG:
|
||||
return BG_COLOR_GAME_CG;
|
||||
case EhConfig.WESTERN:
|
||||
return BG_COLOR_WESTERN;
|
||||
case EhConfig.NON_H:
|
||||
return BG_COLOR_NON_H;
|
||||
case EhConfig.IMAGE_SET:
|
||||
return BG_COLOR_IMAGE_SET;
|
||||
case EhConfig.COSPLAY:
|
||||
return BG_COLOR_COSPLAY;
|
||||
case EhConfig.ASIAN_PORN:
|
||||
return BG_COLOR_ASIAN_PORN;
|
||||
case EhConfig.MISC:
|
||||
return BG_COLOR_MISC;
|
||||
default:
|
||||
return BG_COLOR_UNKNOWN;
|
||||
}
|
||||
return switch (category) {
|
||||
case EhConfig.DOUJINSHI -> BG_COLOR_DOUJINSHI;
|
||||
case EhConfig.MANGA -> BG_COLOR_MANGA;
|
||||
case EhConfig.ARTIST_CG -> BG_COLOR_ARTIST_CG;
|
||||
case EhConfig.GAME_CG -> BG_COLOR_GAME_CG;
|
||||
case EhConfig.WESTERN -> BG_COLOR_WESTERN;
|
||||
case EhConfig.NON_H -> BG_COLOR_NON_H;
|
||||
case EhConfig.IMAGE_SET -> BG_COLOR_IMAGE_SET;
|
||||
case EhConfig.COSPLAY -> BG_COLOR_COSPLAY;
|
||||
case EhConfig.ASIAN_PORN -> BG_COLOR_ASIAN_PORN;
|
||||
case EhConfig.MISC -> BG_COLOR_MISC;
|
||||
default -> BG_COLOR_UNKNOWN;
|
||||
};
|
||||
}
|
||||
|
||||
public static void signOut(Context context) {
|
||||
EhApplication.getEhCookieStore(context).signOut();
|
||||
public static void 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();
|
||||
public static boolean needSignedIn() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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,6 +19,7 @@ package com.hippo.ehviewer.client.parser;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryComment;
|
||||
import com.hippo.ehviewer.client.data.GalleryCommentList;
|
||||
@@ -46,14 +47,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;
|
||||
|
||||
@@ -64,27 +63,39 @@ 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,]+)\"");
|
||||
private static final Pattern PATTERN_LARGE_PREVIEW = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"");
|
||||
private static final Pattern PATTERN_NEWER_DATE = Pattern.compile(", added (.+?)<br />");
|
||||
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("/fav.png\\); background-position:0px -(\\d+)px");
|
||||
|
||||
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"));
|
||||
}
|
||||
private static final String[] LANGUAGES = {
|
||||
"English",
|
||||
"Chinese",
|
||||
"Spanish",
|
||||
"Korean",
|
||||
"Russian",
|
||||
"French",
|
||||
"Portuguese",
|
||||
"Thai",
|
||||
"German",
|
||||
"Italian",
|
||||
"Vietnamese",
|
||||
"Polish",
|
||||
"Hungarian",
|
||||
"Dutch",
|
||||
};
|
||||
|
||||
public static GalleryDetail parse(String body) throws EhException {
|
||||
if (body.contains(OFFENSIVE_STRING)) {
|
||||
@@ -108,6 +119,15 @@ public class GalleryDetailParser {
|
||||
galleryDetail.comments = parseComments(document);
|
||||
galleryDetail.previewPages = parsePreviewPages(document, body);
|
||||
galleryDetail.previewSet = parsePreviewSet(document, body);
|
||||
|
||||
// Generate simpleLanguage for local favorites
|
||||
for (int i = 0; i < LANGUAGES.length; i++) {
|
||||
if (galleryDetail.language.equals(LANGUAGES[i])) {
|
||||
galleryDetail.simpleLanguage = GalleryInfo.S_LANGS[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return galleryDetail;
|
||||
}
|
||||
|
||||
@@ -186,7 +206,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 = "";
|
||||
@@ -196,18 +216,12 @@ 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);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
Elements es = gdd.child(0).child(0).children();
|
||||
for (int i = 0, n = es.size(); i < n; i++) {
|
||||
parseDetailInfo(gd, es.get(i));
|
||||
}
|
||||
|
||||
// Rating count
|
||||
@@ -239,15 +253,24 @@ public class GalleryDetailParser {
|
||||
|
||||
// isFavorited
|
||||
Element gdf = gm.getElementById("gdf");
|
||||
gd.isFavorited = null != gdf && !StringUtils.trim(gdf.text()).equals("Add to Favorites");
|
||||
gd.isFavorited = false;
|
||||
if (gdf != null) {
|
||||
final String favoriteName = StringUtils.trim(gdf.text());
|
||||
if (favoriteName.equals("Add to Favorites")) {
|
||||
gd.favoriteName = null;
|
||||
} else {
|
||||
gd.isFavorited = true;
|
||||
gd.favoriteName = StringUtils.trim(gdf.text());
|
||||
|
||||
matcher = PATTERN_FAVORITE_SLOT.matcher(body);
|
||||
if (matcher.find()) {
|
||||
gd.favoriteSlot = (NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gd.favoriteSlot == -2 && EhDB.containLocalFavorites(gd.gid)) {
|
||||
gd.favoriteSlot = -1;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
throw new ParseException("Can't parse gallery detail", body);
|
||||
@@ -291,7 +314,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;
|
||||
@@ -321,20 +344,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,6 +394,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) {
|
||||
@@ -394,7 +414,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();
|
||||
@@ -402,31 +422,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) {
|
||||
@@ -444,17 +439,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,7 +468,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
|
||||
@@ -485,7 +478,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;
|
||||
@@ -503,6 +496,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());
|
||||
@@ -515,20 +509,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();
|
||||
@@ -536,38 +531,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);
|
||||
@@ -634,9 +605,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++) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,11 @@ public class HistoryInfo extends GalleryInfo {
|
||||
return new HistoryInfo[size];
|
||||
}
|
||||
};
|
||||
@ColumnInfo(name = "MODE")
|
||||
public int mode;
|
||||
@ColumnInfo(name = "TIME")
|
||||
public long time;
|
||||
// Use MODE for favoriteSlot
|
||||
@ColumnInfo(name = "MODE")
|
||||
private int mode;
|
||||
|
||||
protected HistoryInfo(Parcel in) {
|
||||
super(in);
|
||||
@@ -43,10 +44,11 @@ public class HistoryInfo extends GalleryInfo {
|
||||
this.rating = galleryInfo.rating;
|
||||
this.simpleTags = galleryInfo.simpleTags;
|
||||
this.simpleLanguage = galleryInfo.simpleLanguage;
|
||||
this.favoriteSlot = galleryInfo.favoriteSlot;
|
||||
}
|
||||
|
||||
public HistoryInfo(int mode, long time) {
|
||||
this.mode = mode;
|
||||
this.favoriteSlot = mode - 2;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@@ -131,11 +133,11 @@ public class HistoryInfo extends GalleryInfo {
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mode;
|
||||
return favoriteSlot + 2;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
this.mode = mode;
|
||||
this.favoriteSlot = mode - 2;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
|
||||
@@ -44,6 +44,7 @@ public class LocalFavoriteInfo extends GalleryInfo {
|
||||
}
|
||||
|
||||
public LocalFavoriteInfo() {
|
||||
this.favoriteSlot = -1;
|
||||
}
|
||||
|
||||
public long getGid() {
|
||||
|
||||
@@ -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))
|
||||
|
||||
+17
-14
@@ -17,9 +17,9 @@
|
||||
package com.hippo.ehviewer.gallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -36,7 +36,6 @@ import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.thread.PVLock;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -47,7 +46,9 @@ import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
|
||||
public class ArchivePageLoader extends PageLoader2 {
|
||||
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
||||
public static Handler showPasswd;
|
||||
public static String passwd;
|
||||
@@ -62,10 +63,10 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
new Thread(new DecodeTask())
|
||||
};
|
||||
private Thread archiveThread;
|
||||
private volatile int size = STATE_WAIT;
|
||||
private volatile int size = PageLoader.STATE_WAIT;
|
||||
private String error;
|
||||
|
||||
public ArchiveGalleryProvider(Context context, Uri uri) {
|
||||
public ArchivePageLoader(Context context, Uri uri) {
|
||||
UriArchiveAccessor archiveAccessor1;
|
||||
try {
|
||||
archiveAccessor1 = new UriArchiveAccessor(context, uri);
|
||||
@@ -78,8 +79,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
int id = sIdGenerator.incrementAndGet();
|
||||
|
||||
archiveThread = new PriorityThread(
|
||||
@@ -92,7 +91,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (archiveThread != null) {
|
||||
archiveThread.interrupt();
|
||||
archiveThread = null;
|
||||
@@ -218,7 +216,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
size = 0;
|
||||
}
|
||||
if (size <= 0) {
|
||||
size = STATE_ERROR;
|
||||
size = PageLoader.STATE_ERROR;
|
||||
error = GetText.getString(R.string.error_reading_failed);
|
||||
notifyDataChanged();
|
||||
return;
|
||||
@@ -290,7 +288,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
// Check index valid
|
||||
if (index < 0 || index >= size) {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
|
||||
notifyPageFailed(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -335,15 +333,20 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
Image image = null;
|
||||
if (buffer != null) {
|
||||
image = Image.decode(buffer, false, () -> {
|
||||
try {
|
||||
image = Image.decode(buffer, () -> {
|
||||
archiveAccessor.releaseByteBuffer(buffer);
|
||||
return null;
|
||||
});
|
||||
} catch (ImageDecoder.DecodeException e) {
|
||||
archiveAccessor.releaseByteBuffer(buffer);
|
||||
return null;
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
notifyPageFailed(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-23
@@ -24,7 +24,6 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.GetText;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.glgallery.GalleryPageView;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.unifile.FilenameFilter;
|
||||
import com.hippo.unifile.UniFile;
|
||||
@@ -44,9 +43,11 @@ import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
|
||||
private static final String TAG = DirGalleryProvider.class.getSimpleName();
|
||||
public class DirPageLoader extends PageLoader2 implements Runnable {
|
||||
|
||||
private static final String TAG = DirPageLoader.class.getSimpleName();
|
||||
private static final AtomicInteger sIdGenerator = new AtomicInteger();
|
||||
private static final FilenameFilter imageFilter =
|
||||
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
|
||||
@@ -60,21 +61,19 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
};
|
||||
private final UniFile mDir;
|
||||
private final Stack<Integer> mRequests = new Stack<>();
|
||||
private final AtomicInteger mDecodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
|
||||
private final AtomicInteger mDecodingIndex = new AtomicInteger(-1);
|
||||
private final AtomicReference<UniFile[]> mFileList = new AtomicReference<>();
|
||||
@Nullable
|
||||
private Thread mBgThread;
|
||||
private volatile int mSize = STATE_WAIT;
|
||||
private volatile int mSize = PageLoader.STATE_WAIT;
|
||||
private String mError;
|
||||
|
||||
public DirGalleryProvider(@NonNull UniFile dir) {
|
||||
public DirPageLoader(@NonNull UniFile dir) {
|
||||
mDir = dir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
mBgThread = new PriorityThread(this, TAG + '-' + sIdGenerator.incrementAndGet(),
|
||||
Process.THREAD_PRIORITY_BACKGROUND);
|
||||
mBgThread.start();
|
||||
@@ -82,8 +81,6 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
|
||||
if (mBgThread != null) {
|
||||
mBgThread.interrupt();
|
||||
mBgThread = null;
|
||||
@@ -213,7 +210,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
UniFile[] files = mDir.listFiles(imageFilter);
|
||||
|
||||
if (files == null) {
|
||||
mSize = STATE_ERROR;
|
||||
mSize = PageLoader.STATE_ERROR;
|
||||
mError = GetText.getString(R.string.error_not_folder_path);
|
||||
|
||||
// Notify to to show error
|
||||
@@ -251,28 +248,24 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
|
||||
// Check index valid
|
||||
if (index < 0 || index >= files.length) {
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
|
||||
mDecodingIndex.lazySet(-1);
|
||||
notifyPageFailed(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = files[index].openInputStream();
|
||||
Image image = Image.decode((FileInputStream) is, false);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
}
|
||||
Image image = Image.decode((FileInputStream) is);
|
||||
mDecodingIndex.lazySet(-1);
|
||||
notifyPageSucceed(index, image);
|
||||
} catch (IOException e) {
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_reading_failed));
|
||||
mDecodingIndex.lazySet(-1);
|
||||
notifyPageFailed(index);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
}
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
mDecodingIndex.lazySet(-1);
|
||||
}
|
||||
|
||||
// Clear file list
|
||||
+11
-12
@@ -23,37 +23,36 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.spider.SpiderQueen;
|
||||
import com.hippo.glgallery.GalleryProvider;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.OnSpiderListener {
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
|
||||
public class EhPageLoader extends PageLoader2 implements SpiderQueen.OnSpiderListener {
|
||||
|
||||
private final Context mContext;
|
||||
private final GalleryInfo mGalleryInfo;
|
||||
@Nullable
|
||||
private SpiderQueen mSpiderQueen;
|
||||
|
||||
public EhGalleryProvider(Context context, GalleryInfo galleryInfo) {
|
||||
public EhPageLoader(Context context, GalleryInfo galleryInfo) {
|
||||
mContext = context;
|
||||
mGalleryInfo = galleryInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mContext, mGalleryInfo, SpiderQueen.MODE_READ);
|
||||
mSpiderQueen.addOnSpiderListener(this);
|
||||
if (mSpiderQueen.size() > 0)
|
||||
notifyDataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
|
||||
if (mSpiderQueen != null) {
|
||||
mSpiderQueen.removeOnSpiderListener(this);
|
||||
// Activity recreate may called, so wait 3000s
|
||||
@@ -120,7 +119,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
if (mSpiderQueen != null) {
|
||||
return mSpiderQueen.size();
|
||||
} else {
|
||||
return GalleryProvider.STATE_ERROR;
|
||||
return PageLoader.STATE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +130,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
if (object instanceof Float) {
|
||||
notifyPagePercent(index, (Float) object);
|
||||
} else if (object instanceof String) {
|
||||
notifyPageFailed(index, (String) object);
|
||||
notifyPageFailed(index);
|
||||
} else if (object == null) {
|
||||
notifyPageWait(index);
|
||||
}
|
||||
@@ -145,7 +144,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
if (object instanceof Float) {
|
||||
notifyPagePercent(index, (Float) object);
|
||||
} else if (object instanceof String) {
|
||||
notifyPageFailed(index, (String) object);
|
||||
notifyPageFailed(index);
|
||||
} else if (object == null) {
|
||||
notifyPageWait(index);
|
||||
}
|
||||
@@ -192,7 +191,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
|
||||
@Override
|
||||
public void onPageFailure(int index, String error, int finished, int downloaded, int total) {
|
||||
notifyPageFailed(index, error);
|
||||
notifyPageFailed(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,7 +205,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
|
||||
@Override
|
||||
public void onGetImageFailure(int index, String error) {
|
||||
notifyPageFailed(index, error);
|
||||
notifyPageFailed(index);
|
||||
}
|
||||
|
||||
private static class ReleaseTask implements Runnable {
|
||||
+3
-2
@@ -19,10 +19,11 @@ package com.hippo.ehviewer.gallery;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.glgallery.GalleryProvider;
|
||||
import com.hippo.unifile.UniFile;
|
||||
|
||||
public abstract class GalleryProvider2 extends GalleryProvider {
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
|
||||
public abstract class PageLoader2 extends PageLoader {
|
||||
|
||||
// With dot
|
||||
public static final String[] SUPPORT_IMAGE_EXTENSIONS = {
|
||||
@@ -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)
|
||||
@@ -86,7 +86,7 @@ class AccountPreference @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
builder.setPositiveButton(R.string.settings_eh_sign_out) { _: DialogInterface?, _: Int ->
|
||||
EhUtils.signOut(context)
|
||||
EhUtils.signOut()
|
||||
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
||||
import com.hippo.ehviewer.gallery.PageLoader2;
|
||||
import com.hippo.io.UniFileInputStreamPipe;
|
||||
import com.hippo.io.UniFileOutputStreamPipe;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
@@ -118,7 +118,7 @@ public final class SpiderDen {
|
||||
|
||||
@Nullable
|
||||
private static UniFile findImageFile(UniFile dir, int index) {
|
||||
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
|
||||
for (String extension : PageLoader2.SUPPORT_IMAGE_EXTENSIONS) {
|
||||
String filename = generateImageFilename(index, extension);
|
||||
UniFile file = dir.findFile(filename);
|
||||
if (file != null) {
|
||||
@@ -179,10 +179,10 @@ public final class SpiderDen {
|
||||
* @param extension with dot
|
||||
*/
|
||||
private String fixExtension(String extension) {
|
||||
if (Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||
if (Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||
return extension;
|
||||
} else {
|
||||
return GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||
return PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,8 +261,8 @@ public final class SpiderDen {
|
||||
}
|
||||
|
||||
boolean result = false;
|
||||
for (int i = 0, n = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
|
||||
String filename = generateImageFilename(index, GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[i]);
|
||||
for (int i = 0, n = PageLoader2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
|
||||
String filename = generateImageFilename(index, PageLoader2.SUPPORT_IMAGE_EXTENSIONS[i]);
|
||||
UniFile file = dir.subFile(filename);
|
||||
if (file != null) {
|
||||
result |= file.delete();
|
||||
|
||||
@@ -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;
|
||||
@@ -47,9 +48,7 @@ import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageApiParser;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageParser;
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
||||
import com.hippo.glgallery.GalleryPageView;
|
||||
import com.hippo.glgallery.GalleryProvider;
|
||||
import com.hippo.ehviewer.gallery.PageLoader2;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
@@ -84,6 +83,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
@@ -156,9 +156,9 @@ public final class SpiderQueen implements Runnable {
|
||||
// For download, when it go to mPageStateArray.size(), done
|
||||
private volatile int mDownloadPage = -1;
|
||||
|
||||
private SpiderQueen(EhApplication application, @NonNull GalleryInfo galleryInfo) {
|
||||
mHttpClient = EhApplication.getOkHttpClient(application);
|
||||
mSpiderInfoCache = EhApplication.getSpiderInfoCache(application);
|
||||
private SpiderQueen(@NonNull GalleryInfo galleryInfo) {
|
||||
mHttpClient = EhApplication.getOkHttpClient();
|
||||
mSpiderInfoCache = EhApplication.getSpiderInfoCache();
|
||||
mGalleryInfo = galleryInfo;
|
||||
mSpiderDen = new SpiderDen(mGalleryInfo);
|
||||
|
||||
@@ -166,7 +166,7 @@ public final class SpiderQueen implements Runnable {
|
||||
mPreloadNumber = MathUtils.clamp(Settings.getPreloadImage(), 0, 100);
|
||||
|
||||
for (int i = 0; i < DECODE_THREAD_NUM; i++) {
|
||||
mDecodeIndexArray[i] = GalleryPageView.INVALID_INDEX;
|
||||
mDecodeIndexArray[i] = -1;
|
||||
}
|
||||
|
||||
mWorkerPoolExecutor = new ThreadPoolExecutor(mWorkerMaxCount, mWorkerMaxCount,
|
||||
@@ -182,8 +182,8 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
|
||||
if (queen == null) {
|
||||
EhApplication application = (EhApplication) context.getApplicationContext();
|
||||
queen = new SpiderQueen(application, galleryInfo);
|
||||
context.getApplicationContext();
|
||||
queen = new SpiderQueen(galleryInfo);
|
||||
sQueenMap.put(galleryInfo.gid, queen);
|
||||
// Set mode
|
||||
queen.setMode(mode);
|
||||
@@ -357,12 +357,8 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
private void setMode(@Mode int mode) {
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
mReadReference++;
|
||||
break;
|
||||
case MODE_DOWNLOAD:
|
||||
mDownloadReference++;
|
||||
break;
|
||||
case MODE_READ -> mReadReference++;
|
||||
case MODE_DOWNLOAD -> mDownloadReference++;
|
||||
}
|
||||
|
||||
if (mDownloadReference > 1) {
|
||||
@@ -374,12 +370,8 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
private void clearMode(@Mode int mode) {
|
||||
switch (mode) {
|
||||
case MODE_READ:
|
||||
mReadReference--;
|
||||
break;
|
||||
case MODE_DOWNLOAD:
|
||||
mDownloadReference--;
|
||||
break;
|
||||
case MODE_READ -> mReadReference--;
|
||||
case MODE_DOWNLOAD -> mDownloadReference--;
|
||||
}
|
||||
|
||||
if (mReadReference < 0 || mDownloadReference < 0) {
|
||||
@@ -406,9 +398,9 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
public int size() {
|
||||
if (mQueenThread == null) {
|
||||
return GalleryProvider.STATE_ERROR;
|
||||
return PageLoader.STATE_ERROR;
|
||||
} else if (mPageStateArray == null) {
|
||||
return GalleryProvider.STATE_WAIT;
|
||||
return PageLoader.STATE_WAIT;
|
||||
} else {
|
||||
return mPageStateArray.length;
|
||||
}
|
||||
@@ -522,21 +514,16 @@ public final class SpiderQueen implements Runnable {
|
||||
Object result;
|
||||
|
||||
switch (state) {
|
||||
default:
|
||||
case STATE_NONE:
|
||||
result = null;
|
||||
break;
|
||||
case STATE_DOWNLOADING:
|
||||
result = mPagePercentMap.get(index);
|
||||
break;
|
||||
case STATE_FAILED:
|
||||
case STATE_NONE -> result = null;
|
||||
case STATE_DOWNLOADING -> result = mPagePercentMap.get(index);
|
||||
case STATE_FAILED -> {
|
||||
String error = mPageErrorMap.get(index);
|
||||
if (error == null) {
|
||||
error = GetText.getString(R.string.error_unknown);
|
||||
}
|
||||
result = error;
|
||||
break;
|
||||
case STATE_FINISHED:
|
||||
}
|
||||
case STATE_FINISHED -> {
|
||||
synchronized (mDecodeRequestQueue) {
|
||||
if (!contain(mDecodeIndexArray, index) && !mDecodeRequestQueue.contains(index)) {
|
||||
mDecodeRequestQueue.add(index);
|
||||
@@ -544,7 +531,8 @@ public final class SpiderQueen implements Runnable {
|
||||
}
|
||||
}
|
||||
result = null;
|
||||
break;
|
||||
}
|
||||
default -> throw new IllegalStateException("Unexpected value: " + state);
|
||||
}
|
||||
|
||||
tryToEnsureWorkers();
|
||||
@@ -644,7 +632,6 @@ public final class SpiderQueen implements Runnable {
|
||||
return null;
|
||||
}
|
||||
|
||||
OutputStream os = null;
|
||||
try {
|
||||
pipe.obtain();
|
||||
|
||||
@@ -660,7 +647,6 @@ public final class SpiderQueen implements Runnable {
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
IOUtils.closeQuietly(os);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,14 +736,13 @@ public final class SpiderQueen implements Runnable {
|
||||
}
|
||||
|
||||
private SpiderInfo readSpiderInfoFromInternet() {
|
||||
try {
|
||||
SpiderInfo spiderInfo = new SpiderInfo();
|
||||
spiderInfo.gid = mGalleryInfo.gid;
|
||||
spiderInfo.token = mGalleryInfo.token;
|
||||
SpiderInfo spiderInfo = new SpiderInfo();
|
||||
spiderInfo.gid = mGalleryInfo.gid;
|
||||
spiderInfo.token = mGalleryInfo.token;
|
||||
|
||||
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
|
||||
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
|
||||
Response response = mHttpClient.newCall(request).execute();
|
||||
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
|
||||
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
|
||||
try (Response response = mHttpClient.newCall(request).execute()) {
|
||||
String body = response.body().string();
|
||||
|
||||
spiderInfo.pages = GalleryDetailParser.parsePages(body);
|
||||
@@ -776,15 +761,14 @@ public final class SpiderQueen implements Runnable {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String url = EhUrl.getGalleryMultiPageViewerUrl(
|
||||
mGalleryInfo.gid, mGalleryInfo.token);
|
||||
if (DEBUG_PTOKEN) {
|
||||
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
|
||||
}
|
||||
String referer = EhUrl.getReferer();
|
||||
Request request = new EhRequestBuilder(url, referer).build();
|
||||
Response response = mHttpClient.newCall(request).execute();
|
||||
String url = EhUrl.getGalleryMultiPageViewerUrl(
|
||||
mGalleryInfo.gid, mGalleryInfo.token);
|
||||
if (DEBUG_PTOKEN) {
|
||||
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
|
||||
}
|
||||
String referer = EhUrl.getReferer();
|
||||
Request request = new EhRequestBuilder(url, referer).build();
|
||||
try (Response response = mHttpClient.newCall(request).execute()) {
|
||||
String body = response.body().string();
|
||||
|
||||
ArrayList<String> list = GalleryMultiPageViewerPTokenParser.parse(body);
|
||||
@@ -823,16 +807,15 @@ public final class SpiderQueen implements Runnable {
|
||||
previewIndex = Math.min(previewIndex, spiderInfo.previewPages - 1);
|
||||
}
|
||||
|
||||
try {
|
||||
String url = EhUrl.getGalleryDetailUrl(
|
||||
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
|
||||
String referer = EhUrl.getReferer();
|
||||
if (DEBUG_PTOKEN) {
|
||||
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
|
||||
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
|
||||
}
|
||||
Request request = new EhRequestBuilder(url, referer).build();
|
||||
Response response = mHttpClient.newCall(request).execute();
|
||||
String url = EhUrl.getGalleryDetailUrl(
|
||||
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
|
||||
String referer = EhUrl.getReferer();
|
||||
if (DEBUG_PTOKEN) {
|
||||
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
|
||||
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
|
||||
}
|
||||
Request request = new EhRequestBuilder(url, referer).build();
|
||||
try (Response response = mHttpClient.newCall(request).execute()) {
|
||||
String body = response.body().string();
|
||||
readPreviews(body, previewIndex, spiderInfo);
|
||||
|
||||
@@ -857,7 +840,7 @@ public final class SpiderQueen implements Runnable {
|
||||
UniFile file = downloadDir.createFile(SPIDER_INFO_FILENAME);
|
||||
try {
|
||||
spiderInfo.write(file.openOutputStream());
|
||||
} catch (Throwable e) {
|
||||
} catch (IOException e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
// Ignore
|
||||
}
|
||||
@@ -1149,7 +1132,7 @@ public final class SpiderQueen implements Runnable {
|
||||
}
|
||||
|
||||
// false for stop
|
||||
private boolean downloadImage(long gid, int index, String pToken, String previousPToken, boolean force) {
|
||||
private boolean downloadImage(long gid, int index, String pToken, String previousPToken) {
|
||||
String skipHathKey = null;
|
||||
List<String> skipHathKeys = new ArrayList<>(5);
|
||||
String originImageUrl = null;
|
||||
@@ -1283,12 +1266,6 @@ public final class SpiderQueen implements Runnable {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (responseBody == null) {
|
||||
error = "Empty response body";
|
||||
forceHtml = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get extension
|
||||
String extension = null;
|
||||
MediaType mediaType = responseBody.contentType();
|
||||
@@ -1296,8 +1273,8 @@ public final class SpiderQueen implements Runnable {
|
||||
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mediaType.toString());
|
||||
}
|
||||
// Ensure extension
|
||||
if (!Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||
extension = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||
if (!Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
|
||||
extension = PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
|
||||
}
|
||||
|
||||
OutputStreamPipe osPipe = null;
|
||||
@@ -1574,7 +1551,7 @@ public final class SpiderQueen implements Runnable {
|
||||
}
|
||||
|
||||
// Get image url
|
||||
return downloadImage(mGid, index, pToken, previousPToken, force);
|
||||
return downloadImage(mGid, index, pToken, previousPToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1595,7 +1572,7 @@ public final class SpiderQueen implements Runnable {
|
||||
Log.e(TAG, "WTF, mWorkerCount < 0, not thread safe or something wrong");
|
||||
mWorkerCount = 0;
|
||||
}
|
||||
finish = mWorkerCount <= 0;
|
||||
finish = mWorkerCount == 0;
|
||||
}
|
||||
|
||||
if (finish) {
|
||||
@@ -1618,7 +1595,7 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
private void resetDecodeIndex() {
|
||||
synchronized (mDecodeRequestQueue) {
|
||||
mDecodeIndexArray[mThreadIndex] = GalleryPageView.INVALID_INDEX;
|
||||
mDecodeIndexArray[mThreadIndex] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1678,7 +1655,9 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
image = Image.decode(((FileInputStream) is), false);
|
||||
image = Image.decode(((FileInputStream) is));
|
||||
} catch (ImageDecoder.DecodeException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -15,12 +15,10 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
import android.os.Build
|
||||
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
|
||||
@@ -46,13 +44,6 @@ class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment(
|
||||
): View? {
|
||||
parseDetail()
|
||||
val view = inflater.inflate(R.layout.scene_gallery_info, container, false)
|
||||
dialog?.window?.run {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
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
|
||||
|
||||
@@ -136,7 +136,7 @@ public final class MainActivity extends StageActivity
|
||||
@NonNull
|
||||
@Override
|
||||
protected Announcer getLaunchAnnouncer() {
|
||||
if (EhUtils.needSignedIn(this)) {
|
||||
if (EhUtils.needSignedIn()) {
|
||||
return new Announcer(SignInScene.class);
|
||||
} else if (Settings.getSelectSite()) {
|
||||
return new Announcer(SelectSiteScene.class);
|
||||
@@ -150,7 +150,7 @@ public final class MainActivity extends StageActivity
|
||||
// Sometimes scene can't show directly
|
||||
private Announcer processAnnouncer(Announcer announcer) {
|
||||
if (0 == getSceneCount()) {
|
||||
if (EhUtils.needSignedIn(this)) {
|
||||
if (EhUtils.needSignedIn()) {
|
||||
Bundle newArgs = new Bundle();
|
||||
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
|
||||
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.getArgs());
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
@@ -70,7 +71,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
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(),
|
||||
new ActivityResultContracts.CreateDocument("application/vnd.sqlite3"),
|
||||
uri -> {
|
||||
if (uri != null) {
|
||||
try {
|
||||
@@ -107,7 +108,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
}
|
||||
});
|
||||
ActivityResultLauncher<String> dumpLogcatLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.CreateDocument(),
|
||||
new ActivityResultContracts.CreateDocument("application/zip"),
|
||||
uri -> {
|
||||
if (uri != null) {
|
||||
try {
|
||||
@@ -291,7 +292,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
return true;
|
||||
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
|
||||
try {
|
||||
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
||||
@SuppressLint("InlinedApi") Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
||||
Uri.parse("package:" + requireContext().getPackageName()));
|
||||
startActivity(intent);
|
||||
} catch (Throwable t) {
|
||||
@@ -305,43 +306,42 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
}
|
||||
|
||||
private void backupFavorite() {
|
||||
EhClient mClient = EhApplication.getEhClient(requireContext());
|
||||
EhClient mClient = EhApplication.getEhClient();
|
||||
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
|
||||
favTotal = 0;
|
||||
favIndex = 1;
|
||||
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||
request.setCallback(new EhClient.Callback() {
|
||||
request.setCallback(new EhClient.Callback<FavoritesParser.Result>() {
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
public void onSuccess(FavoritesParser.Result result) {
|
||||
try {
|
||||
FavoritesParser.Result favResult = (FavoritesParser.Result) result;
|
||||
if (favResult.galleryInfoList.isEmpty()) {
|
||||
if (result.galleryInfoList.isEmpty()) {
|
||||
showTip(R.string.settings_advanced_backup_favorite_nothing, BaseScene.LENGTH_SHORT);
|
||||
} else {
|
||||
if (favTotal == 0 && favResult.countArray != null) {
|
||||
if (favTotal == 0 && result.countArray != null) {
|
||||
int totalFav = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
totalFav = totalFav + favResult.countArray[i];
|
||||
totalFav = totalFav + result.countArray[i];
|
||||
}
|
||||
favTotal = (int) Math.ceil((double) totalFav / favResult.galleryInfoList.size());
|
||||
favTotal = (int) Math.ceil((double) totalFav / result.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);
|
||||
EhDB.putLocalFavorites(result.galleryInfoList);
|
||||
|
||||
if (favResult.nextPage != null) {
|
||||
if (result.nextPage != null) {
|
||||
try {
|
||||
Thread.sleep(Settings.getDownloadDelay());
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
favIndex++;
|
||||
favListUrlBuilder.setNext(favResult.nextPage);
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
favListUrlBuilder.setNext(result.nextPage);
|
||||
request.setArgs(favListUrlBuilder.build());
|
||||
mClient.execute(request);
|
||||
} else {
|
||||
showTip(R.string.settings_advanced_backup_favorite_success, BaseScene.LENGTH_SHORT);
|
||||
@@ -363,7 +363,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
}
|
||||
});
|
||||
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
request.setArgs(favListUrlBuilder.build());
|
||||
mClient.execute(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class HostsFragment : BaseFragment(), View.OnClickListener {
|
||||
private var adapter: HostsAdapter? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
EhApplication.getHosts(requireContext()).apply {
|
||||
EhApplication.hosts.apply {
|
||||
hosts = this
|
||||
data = all
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -18,4 +18,9 @@ abstract class CollapsingToolbarScene : ToolbarScene() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,9 @@ import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
import com.hippo.ehviewer.client.EhCookieStore;
|
||||
import com.hippo.ehviewer.client.EhRequest;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.util.ClipboardUtilKt;
|
||||
@@ -68,31 +70,6 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
@Nullable
|
||||
private TextView mFromClipboard;
|
||||
|
||||
// false for error
|
||||
private static boolean checkIpbMemberId(String id) {
|
||||
for (int i = 0, n = id.length(); i < n; i++) {
|
||||
char ch = id.charAt(i);
|
||||
if (!(ch >= '0' && ch <= '9')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkIpbPassHash(String hash) {
|
||||
if (32 != hash.length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0, n = hash.length(); i < n; i++) {
|
||||
char ch = hash.charAt(i);
|
||||
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Cookie newCookie(String name, String value, String domain) {
|
||||
return new Cookie.Builder().name(name).value(value)
|
||||
.domain(domain).expiresAt(Long.MAX_VALUE).build();
|
||||
@@ -216,20 +193,12 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
|
||||
hideSoftInput();
|
||||
|
||||
if (!checkIpbMemberId(ipbMemberId) || !(checkIpbPassHash(ipbPassHash))) {
|
||||
new BaseDialogBuilder(context).setTitle(R.string.waring)
|
||||
.setMessage(R.string.wrong_cookie_warning)
|
||||
.setPositiveButton(R.string.i_will_check_it, null)
|
||||
.setNegativeButton(R.string.i_dont_think_so, (dialog, which) -> {
|
||||
storeCookie(ipbMemberId, ipbPassHash, igneous);
|
||||
setResult(RESULT_OK, null);
|
||||
finish();
|
||||
}).show();
|
||||
} else {
|
||||
storeCookie(ipbMemberId, ipbPassHash, igneous);
|
||||
setResult(RESULT_OK, null);
|
||||
finish();
|
||||
}
|
||||
storeCookie(ipbMemberId, ipbPassHash, igneous);
|
||||
|
||||
EhRequest request = new EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_PROFILE)
|
||||
.setCallback(new CookieSignInListener());
|
||||
EhApplication.getEhClient().execute(request);
|
||||
}
|
||||
|
||||
private void storeCookie(String id, String hash, String igneous) {
|
||||
@@ -238,9 +207,9 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
return;
|
||||
}
|
||||
|
||||
EhUtils.signOut(context);
|
||||
EhUtils.signOut();
|
||||
|
||||
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));
|
||||
@@ -252,8 +221,11 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
}
|
||||
|
||||
private void fillCookiesFromClipboard() {
|
||||
Context context = requireContext();
|
||||
|
||||
hideSoftInput();
|
||||
String text = ClipboardUtilKt.getTextFromClipboard(ClipboardUtilKt.getClipboardManager(getContext()), getContext());
|
||||
String text = ClipboardUtilKt.getTextFromClipboard(
|
||||
ClipboardUtilKt.getClipboardManager(context), context);
|
||||
if (text == null) {
|
||||
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
|
||||
return;
|
||||
@@ -309,4 +281,25 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
private class CookieSignInListener implements EhClient.Callback<Object> {
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
setResult(RESULT_OK, null);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
EhApplication.getEhCookieStore().signOut();
|
||||
new BaseDialogBuilder(requireContext()).setTitle(R.string.sign_in_failed)
|
||||
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.wrong_cookie_warning))
|
||||
.setPositiveButton(R.string.get_it, null).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
EhApplication.getEhCookieStore().signOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,6 @@ import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.download.DownloadService;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.unifile.UniFile;
|
||||
@@ -89,6 +88,7 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -590,9 +596,9 @@ public class DownloadsScene extends ToolbarScene
|
||||
return false;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(activity, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, list.get(position));
|
||||
Intent intent = new Intent(activity, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, list.get(position));
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
@@ -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,7 +34,6 @@ 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;
|
||||
@@ -47,10 +46,8 @@ 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;
|
||||
@@ -70,12 +67,10 @@ 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;
|
||||
@@ -91,9 +86,9 @@ import java.util.List;
|
||||
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,
|
||||
@SuppressLint({"NotifyDataSetChanged", "RtlHardcoded"})
|
||||
public class FavoritesScene extends SearchBarScene implements
|
||||
FastScroller.OnDragHandlerListener,
|
||||
FabLayout.OnClickFabListener, FabLayout.OnExpandListener,
|
||||
EasyRecyclerView.CustomChoiceListener {
|
||||
|
||||
@@ -108,15 +103,10 @@ public class FavoritesScene extends BaseScene implements
|
||||
public int current; // -1 for error
|
||||
public int limit; // -1 for error
|
||||
@Nullable
|
||||
private ContentLayout mContentLayout;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private SearchBar mSearchBar;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private FabLayout mFabLayout;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
@@ -124,12 +114,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private FavoritesHelper mHelper;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private SearchBarMover mSearchBarMover;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private DrawerArrowDrawable mLeftDrawable;
|
||||
private AddDeleteDrawable mActionFabDrawable;
|
||||
@Nullable
|
||||
private DrawerLayout mDrawerLayout;
|
||||
@@ -189,7 +173,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();
|
||||
@@ -245,19 +229,25 @@ public class FavoritesScene extends BaseScene implements
|
||||
mUrlBuilder = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@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_favorites, container, false);
|
||||
mContentLayout = view.findViewById(R.id.content_layout);
|
||||
ContentLayout 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);
|
||||
AssertUtils.assertNotNull(container);
|
||||
container.addView(mFabLayout);
|
||||
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
|
||||
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
|
||||
mFabLayout
|
||||
@@ -283,14 +273,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();
|
||||
updateJumpFab();
|
||||
mSearchBarMover = new SearchBarMover(this, mSearchBar, mRecyclerView);
|
||||
|
||||
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
|
||||
mActionFabDrawable = new AddDeleteDrawable(context, colorID);
|
||||
@@ -305,7 +290,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
// Restore search mode
|
||||
if (mSearchMode) {
|
||||
mSearchMode = false;
|
||||
enterSearchMode(false);
|
||||
enterSearchMode();
|
||||
}
|
||||
|
||||
// Only refresh for the first time
|
||||
@@ -321,7 +306,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;
|
||||
}
|
||||
|
||||
@@ -338,17 +323,17 @@ 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));
|
||||
}
|
||||
|
||||
mOldFavCat = favCatName;
|
||||
@@ -380,17 +365,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;
|
||||
}
|
||||
@@ -445,33 +426,31 @@ public class FavoritesScene extends BaseScene implements
|
||||
} else if (mFabLayout != null && mFabLayout.isExpanded()) {
|
||||
mFabLayout.toggle();
|
||||
} else if (mSearchMode) {
|
||||
exitSearchMode(true);
|
||||
exitSearchMode();
|
||||
} 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) {
|
||||
public boolean onItemClick(int position) {
|
||||
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) {
|
||||
// Skip if in search mode
|
||||
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
|
||||
@@ -490,12 +469,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure outOfCustomChoiceMode to avoid error
|
||||
//if (mRecyclerView != null) {
|
||||
// mRecyclerView.isInCustomChoice();
|
||||
//}
|
||||
|
||||
exitSearchMode(true);
|
||||
exitSearchMode();
|
||||
|
||||
mUrlBuilder.setKeyword(null);
|
||||
mUrlBuilder.setFavCat(newFavCat);
|
||||
@@ -534,78 +508,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()) {
|
||||
@@ -616,24 +518,13 @@ public class FavoritesScene extends BaseScene implements
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure outOfCustomChoiceMode to avoid error
|
||||
//if (mRecyclerView != null) {
|
||||
// mRecyclerView.isInCustomChoice();
|
||||
//}
|
||||
|
||||
exitSearchMode(true);
|
||||
exitSearchMode();
|
||||
|
||||
mUrlBuilder.setKeyword(query);
|
||||
updateSearchBar();
|
||||
mHelper.refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(SearchBar.Helper.class)
|
||||
public void onSearchEditTextBackPressed() {
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExpand(boolean expanded) {
|
||||
if (expanded) {
|
||||
@@ -644,7 +535,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()) {
|
||||
@@ -665,7 +555,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
|
||||
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
|
||||
|
||||
ArrayList listValidators = new ArrayList<>();
|
||||
ArrayList<CalendarConstraints.DateValidator> listValidators = new ArrayList<>();
|
||||
listValidators.add(DateValidatorPointForward.from(fromDate));
|
||||
listValidators.add(DateValidatorPointBackward.before(toDate));
|
||||
|
||||
@@ -680,13 +570,10 @@ public class FavoritesScene extends BaseScene implements
|
||||
.setSelection(toDate)
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
mHelper.goTo(v);
|
||||
});
|
||||
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(FabLayout.OnClickFabListener.class)
|
||||
public void onClickSecondaryFab(FabLayout view, FloatingActionButton fab, int position) {
|
||||
Context context = getContext();
|
||||
if (null == context || null == mRecyclerView || null == mHelper) {
|
||||
@@ -723,11 +610,9 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
switch (position) {
|
||||
case 3: { // Check all
|
||||
mRecyclerView.checkAll();
|
||||
break;
|
||||
}
|
||||
case 4: { // Download
|
||||
case 3 -> // Check all
|
||||
mRecyclerView.checkAll();
|
||||
case 4 -> { // Download
|
||||
Activity activity = getMainActivity();
|
||||
if (activity != null) {
|
||||
CommonOperations.startDownload(getMainActivity(), mModifyGiList, false);
|
||||
@@ -736,9 +621,8 @@ public class FavoritesScene extends BaseScene implements
|
||||
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
|
||||
mRecyclerView.outOfCustomChoiceMode();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5: { // Delete
|
||||
case 5 -> { // Delete
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper();
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.delete_favorites_dialog_title)
|
||||
@@ -746,9 +630,8 @@ public class FavoritesScene extends BaseScene implements
|
||||
.setPositiveButton(android.R.string.ok, helper)
|
||||
.setOnCancelListener(helper)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
case 6: { // Move
|
||||
case 6 -> { // Move
|
||||
MoveDialogHelper helper = new MoveDialogHelper();
|
||||
// First is local favorite, the other 10 is cloud favorite
|
||||
String[] array = new String[11];
|
||||
@@ -759,7 +642,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
.setItems(array, helper)
|
||||
.setOnCancelListener(helper)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -785,7 +667,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(EasyRecyclerView.CustomChoiceListener.class)
|
||||
public void onIntoCustomChoice(EasyRecyclerView view) {
|
||||
if (mFabLayout != null) {
|
||||
showSelectionFabs();
|
||||
@@ -802,7 +683,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
@Override
|
||||
@Implemented(EasyRecyclerView.CustomChoiceListener.class)
|
||||
public void onOutOfCustomChoice(EasyRecyclerView view) {
|
||||
if (mFabLayout != null) {
|
||||
showNormalFabs();
|
||||
@@ -818,7 +698,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) {
|
||||
@@ -826,29 +705,24 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
}
|
||||
|
||||
private void enterSearchMode(boolean animation) {
|
||||
if (mSearchMode || mSearchBar == null || mSearchBarMover == null || mLeftDrawable == null) {
|
||||
private void enterSearchMode() {
|
||||
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) {
|
||||
private void exitSearchMode() {
|
||||
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);
|
||||
@@ -864,9 +738,8 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
updateSearchBar();
|
||||
assert mUrlBuilder != null;
|
||||
|
||||
int pages = 0;
|
||||
int pages;
|
||||
if (result.nextPage == null)
|
||||
pages = mHelper.pgCounter + 1;
|
||||
else
|
||||
@@ -895,8 +768,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);
|
||||
}
|
||||
}
|
||||
@@ -1076,7 +948,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
holder.value.setText(Integer.toString(mFavCountArray[position - 2]));
|
||||
holder.itemView.setEnabled(true);
|
||||
}
|
||||
holder.itemView.setOnClickListener(v -> onItemClick(holder.itemView, position));
|
||||
holder.itemView.setOnClickListener(v -> onItemClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1189,7 +1061,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
|
||||
@Override
|
||||
void onItemClick(View view, int position) {
|
||||
FavoritesScene.this.onItemClick(view, position);
|
||||
FavoritesScene.this.onItemClick(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1223,8 +1095,8 @@ public class FavoritesScene extends BaseScene implements
|
||||
|
||||
boolean local = mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_LOCAL;
|
||||
|
||||
long[] gidArray = new long[mModifyGiList.size()];
|
||||
if (mModifyAdd) {
|
||||
long[] gidArray = new long[mModifyGiList.size()];
|
||||
String[] tokenArray = new String[mModifyGiList.size()];
|
||||
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
|
||||
GalleryInfo gi = mModifyGiList.get(i);
|
||||
@@ -1242,7 +1114,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
request.setArgs(gidArray, tokenArray, mModifyFavCat);
|
||||
mClient.execute(request);
|
||||
} else {
|
||||
long[] gidArray = new long[mModifyGiList.size()];
|
||||
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
|
||||
gidArray[i] = mModifyGiList.get(i).gid;
|
||||
}
|
||||
@@ -1262,7 +1133,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
request.setCallback(new GetFavoritesListener(getContext(),
|
||||
activity.getStageId(), getTag(),
|
||||
taskId, local, mUrlBuilder.getKeyword()));
|
||||
request.setArgs(url, gidArray, mModifyFavCat, Settings.getShowJpnTitle());
|
||||
request.setArgs(url, gidArray, mModifyFavCat);
|
||||
mClient.execute(request);
|
||||
}
|
||||
} else if (mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_LOCAL) {
|
||||
@@ -1273,7 +1144,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
if (jumpTo != null) {
|
||||
next = Integer.toString(minGid);
|
||||
operation = 0;
|
||||
} else if (page != 0) {
|
||||
} else if (page != 0 && mHelper != null) {
|
||||
if (page >= mHelper.getPageForTop()) {
|
||||
next = lowerPage;
|
||||
operation = 1;
|
||||
@@ -1294,7 +1165,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
request.setCallback(new GetFavoritesListener(getContext(),
|
||||
activity.getStageId(), getTag(),
|
||||
taskId, false, mUrlBuilder.getKeyword()));
|
||||
request.setArgs(url, Settings.getShowJpnTitle());
|
||||
request.setArgs(url);
|
||||
mClient.execute(request);
|
||||
}
|
||||
}
|
||||
@@ -1317,9 +1188,9 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
|
||||
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
|
||||
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1332,9 +1203,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
|
||||
@Override
|
||||
public void onShowView(View hiddenView, View shownView) {
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1343,11 +1212,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1364,16 +1231,18 @@ public class FavoritesScene extends BaseScene implements
|
||||
operation = 0;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Parcelable saveInstanceState(Parcelable superState) {
|
||||
protected Parcelable saveInstanceState(@NonNull Parcelable superState) {
|
||||
Bundle bundle = (Bundle) super.saveInstanceState(superState);
|
||||
bundle.putString(KEY_UPPER_PAGE, upperPage);
|
||||
bundle.putString(KEY_LOWER_PAGE, lowerPage);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Parcelable restoreInstanceState(Parcelable state) {
|
||||
protected Parcelable restoreInstanceState(@NonNull Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
upperPage = bundle.getString(KEY_UPPER_PAGE);
|
||||
lowerPage = bundle.getString(KEY_LOWER_PAGE);
|
||||
|
||||
@@ -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")
|
||||
@@ -408,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,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);
|
||||
}
|
||||
@@ -709,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -858,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);
|
||||
|
||||
|
||||
@@ -65,8 +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;
|
||||
import com.hippo.ehviewer.R;
|
||||
@@ -91,11 +89,10 @@ import com.hippo.ehviewer.client.parser.RateGalleryParser;
|
||||
import com.hippo.ehviewer.client.parser.VoteTagParser;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.Filter;
|
||||
import com.hippo.ehviewer.gallery.EhGalleryProvider;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
||||
import com.hippo.ehviewer.gallery.EhPageLoader;
|
||||
import com.hippo.ehviewer.gallery.PageLoader2;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.GalleryInfoBottomSheet;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
|
||||
@@ -116,20 +113,17 @@ 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 eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
import okhttp3.HttpUrl;
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
@@ -416,6 +410,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) {
|
||||
@@ -453,7 +452,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
super.onResume();
|
||||
if (mRead != null) {
|
||||
try {
|
||||
GalleryProvider2 galleryProvider = new EhGalleryProvider(requireContext(), mGalleryInfo);
|
||||
PageLoader2 galleryProvider = new EhPageLoader(requireContext(), mGalleryInfo);
|
||||
galleryProvider.start();
|
||||
int startPage = galleryProvider.getStartPage();
|
||||
if (startPage != 0) {
|
||||
@@ -560,7 +559,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;
|
||||
}
|
||||
@@ -637,8 +636,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);
|
||||
@@ -664,7 +667,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
adjustViewVisibility(STATE_FAILED, false);
|
||||
}
|
||||
|
||||
EhApplication.getDownloadManager(context).addDownloadInfoListener(this);
|
||||
EhApplication.getDownloadManager().addDownloadInfoListener(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -675,7 +678,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;
|
||||
@@ -738,7 +741,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;
|
||||
}
|
||||
@@ -768,7 +771,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;
|
||||
}
|
||||
@@ -863,10 +866,10 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
if (mPage != 0) {
|
||||
Snackbar.make(requireActivity().findViewById(R.id.snackbar), getString(R.string.read_from, mPage), Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.read, v -> {
|
||||
Intent intent = new Intent(requireContext(), GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, mPage);
|
||||
Intent intent = new Intent(requireContext(), ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryDetail);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, mPage);
|
||||
startActivity(intent);
|
||||
})
|
||||
.show();
|
||||
@@ -879,7 +882,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);
|
||||
@@ -1000,7 +1003,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);
|
||||
}
|
||||
}
|
||||
@@ -1009,6 +1012,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;
|
||||
}
|
||||
@@ -1018,16 +1022,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);
|
||||
@@ -1093,26 +1097,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1130,7 +1124,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();
|
||||
@@ -1139,7 +1133,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();
|
||||
@@ -1148,7 +1142,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,
|
||||
@@ -1156,7 +1150,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();
|
||||
@@ -1170,9 +1164,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
galleryInfo = mGalleryDetail;
|
||||
}
|
||||
if (galleryInfo != null) {
|
||||
Intent intent = new Intent(activity, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryInfo);
|
||||
Intent intent = new Intent(activity, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryInfo);
|
||||
startActivity(intent);
|
||||
}
|
||||
} else if (mNewerVersion == v) {
|
||||
@@ -1304,10 +1298,10 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
o = v.getTag(R.id.index);
|
||||
if (null != galleryInfo && o instanceof Integer) {
|
||||
int index = (Integer) o;
|
||||
Intent intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryInfo);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, index);
|
||||
Intent intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryInfo);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, index);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -1424,7 +1418,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
|
||||
@@ -1435,6 +1429,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();
|
||||
@@ -1527,7 +1524,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;
|
||||
}
|
||||
@@ -1645,7 +1642,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);
|
||||
@@ -1725,7 +1722,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;
|
||||
@@ -1854,7 +1851,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);
|
||||
}
|
||||
@@ -1893,7 +1890,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) {
|
||||
@@ -1971,7 +1968,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);
|
||||
}
|
||||
@@ -2012,7 +2009,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 {
|
||||
@@ -2107,7 +2104,7 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.text.Spannable;
|
||||
@@ -32,6 +31,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;
|
||||
@@ -64,7 +64,6 @@ 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,11 +87,9 @@ import com.hippo.ehviewer.dao.DownloadLabel;
|
||||
import com.hippo.ehviewer.dao.QuickSearch;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
|
||||
import com.hippo.ehviewer.widget.GalleryInfoContentHelper;
|
||||
import com.hippo.ehviewer.widget.SearchBar;
|
||||
import com.hippo.ehviewer.widget.SearchLayout;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
@@ -102,7 +99,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;
|
||||
@@ -120,12 +116,13 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
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";
|
||||
@@ -137,14 +134,11 @@ 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
|
||||
---------------*/
|
||||
@@ -152,11 +146,6 @@ public final class GalleryListScene extends BaseScene
|
||||
private EhClient mClient;
|
||||
@Nullable
|
||||
private ListUrlBuilder mUrlBuilder;
|
||||
/*---------------
|
||||
View life cycle
|
||||
---------------*/
|
||||
@Nullable
|
||||
private ContentLayout mContentLayout;
|
||||
@Nullable
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
@@ -165,8 +154,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() {
|
||||
@@ -197,12 +184,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;
|
||||
@@ -258,17 +239,13 @@ public final class GalleryListScene extends BaseScene
|
||||
} else if (ListUrlBuilder.MODE_WHATS_HOT == urlBuilder.getMode()) {
|
||||
return resources.getString(R.string.whats_hot);
|
||||
} else if (ListUrlBuilder.MODE_TOPLIST == urlBuilder.getMode()) {
|
||||
switch (urlBuilder.getKeyword()) {
|
||||
case "11":
|
||||
return resources.getString(R.string.toplist_alltime);
|
||||
case "12":
|
||||
return resources.getString(R.string.toplist_pastyear);
|
||||
case "13":
|
||||
return resources.getString(R.string.toplist_pastmonth);
|
||||
case "15":
|
||||
return resources.getString(R.string.toplist_yesterday);
|
||||
}
|
||||
return null;
|
||||
return switch (urlBuilder.getKeyword()) {
|
||||
case "11" -> resources.getString(R.string.toplist_alltime);
|
||||
case "12" -> resources.getString(R.string.toplist_pastyear);
|
||||
case "13" -> resources.getString(R.string.toplist_pastmonth);
|
||||
case "15" -> resources.getString(R.string.toplist_yesterday);
|
||||
default -> null;
|
||||
};
|
||||
} else if (!TextUtils.isEmpty(keyword)) {
|
||||
return keyword;
|
||||
} else if (MathUtils.hammingWeight(category) == 1) {
|
||||
@@ -289,6 +266,11 @@ public final class GalleryListScene extends BaseScene
|
||||
return new Announcer(GalleryListScene.class).setArgs(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuResId() {
|
||||
return R.menu.scene_gallery_list_searchbar_menu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNavCheckedItem() {
|
||||
return mNavCheckedId;
|
||||
@@ -328,20 +310,19 @@ public final class GalleryListScene extends BaseScene
|
||||
mHelper.refresh();
|
||||
}
|
||||
setState(STATE_NORMAL);
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
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
|
||||
@@ -441,14 +422,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));
|
||||
@@ -508,12 +489,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
|
||||
@@ -521,9 +501,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;
|
||||
@@ -546,8 +524,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();
|
||||
@@ -558,18 +536,19 @@ public final class GalleryListScene extends BaseScene
|
||||
mShowActionFab = true;
|
||||
|
||||
View mainLayout = ViewUtils.$$(view, R.id.main_layout);
|
||||
mContentLayout = (ContentLayout) ViewUtils.$$(mainLayout, R.id.content_layout);
|
||||
ContentLayout mContentLayout = (ContentLayout) ViewUtils.$$(mainLayout, R.id.content_layout);
|
||||
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);
|
||||
@@ -590,14 +569,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,
|
||||
@@ -613,10 +590,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();
|
||||
@@ -639,10 +618,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()) {
|
||||
@@ -654,22 +629,18 @@ 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;
|
||||
}
|
||||
|
||||
private void showQuickSearchTipDialog(final QsDrawerAdapter adapter,
|
||||
final EasyRecyclerView recyclerView, final TextView tip) {
|
||||
private void showQuickSearchTipDialog() {
|
||||
Context context = getContext();
|
||||
if (null == context) {
|
||||
return;
|
||||
@@ -773,7 +744,7 @@ public final class GalleryListScene extends BaseScene
|
||||
if (id == R.id.action_add) {
|
||||
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
|
||||
} else if (id == R.id.action_help) {
|
||||
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
|
||||
showQuickSearchTipDialog();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -793,19 +764,17 @@ public final class GalleryListScene extends BaseScene
|
||||
if (null != mFabLayout && mFabLayout.isExpanded()) {
|
||||
return false;
|
||||
}
|
||||
switch (mState) {
|
||||
case STATE_NORMAL:
|
||||
return getStackIndex() == 0;
|
||||
case STATE_SIMPLE_SEARCH:
|
||||
case STATE_SEARCH:
|
||||
case STATE_SEARCH_SHOW_LIST:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
if (isSearchViewShown())
|
||||
return false;
|
||||
return mState == STATE_NORMAL && getStackIndex() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (isSearchViewShown()) {
|
||||
hideSearchView();
|
||||
return;
|
||||
}
|
||||
if (null != mFabLayout && mFabLayout.isExpanded()) {
|
||||
mFabLayout.setExpanded(false);
|
||||
return;
|
||||
@@ -828,7 +797,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onItemClick(View view, int position) {
|
||||
public boolean onItemClick(int position) {
|
||||
if (null == mHelper || null == mRecyclerView) {
|
||||
return false;
|
||||
}
|
||||
@@ -846,14 +815,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) {
|
||||
@@ -903,7 +864,7 @@ public final class GalleryListScene extends BaseScene
|
||||
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
|
||||
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
|
||||
|
||||
ArrayList listValidators = new ArrayList<>();
|
||||
ArrayList<CalendarConstraints.DateValidator> listValidators = new ArrayList<>();
|
||||
listValidators.add(DateValidatorPointForward.from(fromDate));
|
||||
listValidators.add(DateValidatorPointBackward.before(toDate));
|
||||
|
||||
@@ -918,9 +879,7 @@ public final class GalleryListScene extends BaseScene
|
||||
.setSelection(toDate)
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
mHelper.goTo(v);
|
||||
});
|
||||
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -964,6 +923,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();
|
||||
@@ -1006,9 +977,9 @@ public final class GalleryListScene extends BaseScene
|
||||
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
|
||||
switch (which) {
|
||||
case 0: // Read
|
||||
Intent intent = new Intent(activity, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi);
|
||||
Intent intent = new Intent(activity, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, gi);
|
||||
startActivity(intent);
|
||||
break;
|
||||
case 1: // Download
|
||||
@@ -1030,7 +1001,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++) {
|
||||
@@ -1157,8 +1128,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;
|
||||
}
|
||||
|
||||
@@ -1166,71 +1136,40 @@ 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;
|
||||
}
|
||||
@@ -1239,51 +1178,15 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickTitle() {
|
||||
public boolean onMenuItemClick(@NonNull MenuItem item) {
|
||||
if (mState == STATE_NORMAL) {
|
||||
setState(STATE_SIMPLE_SEARCH);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClickLeftIcon() {
|
||||
if (null == mSearchBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSearchBar.getState() == SearchBar.STATE_NORMAL) {
|
||||
toggleDrawer(Gravity.LEFT);
|
||||
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;
|
||||
@@ -1311,21 +1214,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;
|
||||
}
|
||||
mSearchLayout.setSearchMode(SearchLayout.SEARCH_MODE_IMAGE);
|
||||
mSearchLayout.setImageUri(uri);
|
||||
setState(STATE_SEARCH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartDragHandler() {
|
||||
// Lock right drawer
|
||||
@@ -1336,38 +1224,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);
|
||||
@@ -1379,9 +1239,7 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
@Override
|
||||
public void onChangeSearchMode() {
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1396,38 +1254,15 @@ 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);
|
||||
mHelper.setEmptyString(emptyString);
|
||||
|
||||
int pages = 0;
|
||||
int pages;
|
||||
if (mIsTopList)
|
||||
pages = 200;
|
||||
else if (result.nextGid == 0)
|
||||
@@ -1440,8 +1275,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);
|
||||
}
|
||||
}
|
||||
@@ -1571,7 +1405,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;
|
||||
@@ -1651,7 +1485,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) {
|
||||
@@ -1739,7 +1573,7 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
@Override
|
||||
void onItemClick(View view, int position) {
|
||||
GalleryListScene.this.onItemClick(view, position);
|
||||
GalleryListScene.this.onItemClick(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1765,23 +1599,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;
|
||||
@@ -1811,6 +1646,7 @@ public final class GalleryListScene extends BaseScene
|
||||
return GalleryListScene.this.getContext();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
protected void notifyDataSetChanged() {
|
||||
if (null != mAdapter) {
|
||||
@@ -1819,9 +1655,9 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
|
||||
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
|
||||
if (null != mAdapter) {
|
||||
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
|
||||
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1834,9 +1670,7 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
@Override
|
||||
public void onShowView(View hiddenView, View shownView) {
|
||||
if (null != mSearchBarMover) {
|
||||
mSearchBarMover.showSearchBar();
|
||||
}
|
||||
showSearchBar();
|
||||
showActionFab();
|
||||
}
|
||||
|
||||
@@ -1846,11 +1680,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -50,7 +49,6 @@ import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview;
|
||||
import com.hippo.ehviewer.client.data.PreviewSet;
|
||||
import com.hippo.ehviewer.client.exception.EhException;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.widget.ContentLayout;
|
||||
@@ -63,6 +61,8 @@ import com.hippo.yorozuya.ViewUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
|
||||
public class GalleryPreviewsScene extends ToolbarScene {
|
||||
|
||||
public static final String KEY_GALLERY_INFO = "gallery_info";
|
||||
@@ -76,11 +76,6 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
@Nullable
|
||||
private GalleryInfo mGalleryInfo;
|
||||
|
||||
/*---------------
|
||||
View life cycle
|
||||
---------------*/
|
||||
@Nullable
|
||||
private ContentLayout mContentLayout;
|
||||
@Nullable
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
@@ -96,7 +91,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
mClient = EhApplication.getEhClient(context);
|
||||
mClient = EhApplication.getEhClient();
|
||||
if (savedInstanceState == null) {
|
||||
onInit();
|
||||
} else {
|
||||
@@ -132,11 +127,11 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateViewWithToolbar(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
mContentLayout = (ContentLayout) inflater.inflate(
|
||||
ContentLayout mContentLayout = (ContentLayout) inflater.inflate(
|
||||
R.layout.scene_gallery_previews, container, false);
|
||||
mContentLayout.hideFastScroll();
|
||||
mRecyclerView = mContentLayout.getRecyclerView();
|
||||
@@ -144,11 +139,10 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
Resources resources = context.getResources();
|
||||
|
||||
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,12 +185,14 @@ 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)
|
||||
showMenu(R.menu.scene_gallery_previews);
|
||||
if (mGalleryInfo != null) {
|
||||
if (((GalleryDetail) mGalleryInfo).previewPages > 2)
|
||||
showMenu(R.menu.scene_gallery_previews);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
public boolean onMenuItemClick(@NonNull MenuItem item) {
|
||||
Context context = getContext();
|
||||
if (null == context) {
|
||||
return false;
|
||||
@@ -232,10 +228,10 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
if (null != context && null != mHelper && null != mGalleryInfo) {
|
||||
GalleryPreview p = mHelper.getDataAtEx(position);
|
||||
if (p != null) {
|
||||
Intent intent = new Intent(context, GalleryActivity.class);
|
||||
intent.setAction(GalleryActivity.ACTION_EH);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryInfo);
|
||||
intent.putExtra(GalleryActivity.KEY_PAGE, p.getPosition());
|
||||
Intent intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryInfo);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, p.getPosition());
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -368,6 +364,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
return GalleryPreviewsScene.this.getContext();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
protected void notifyDataSetChanged() {
|
||||
if (mAdapter != null) {
|
||||
@@ -376,9 +373,9 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
|
||||
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
|
||||
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,8 @@ import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.HistoryInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
|
||||
import com.hippo.ehviewer.ui.CommonOperations
|
||||
import com.hippo.ehviewer.ui.GalleryActivity
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView
|
||||
import com.hippo.scene.Announcer
|
||||
@@ -55,10 +53,12 @@ import com.hippo.view.ViewTransition
|
||||
import com.hippo.widget.LoadImageView
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
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
|
||||
}
|
||||
|
||||
@@ -262,9 +267,9 @@ class HistoryScene : ToolbarScene() {
|
||||
) { _: DialogInterface?, which: Int ->
|
||||
when (which) {
|
||||
0 -> {
|
||||
val intent = Intent(activity, GalleryActivity::class.java)
|
||||
intent.action = GalleryActivity.ACTION_EH
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
|
||||
val intent = Intent(activity, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_EH
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, gi)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -366,6 +371,7 @@ class HistoryScene : ToolbarScene() {
|
||||
val simpleLanguage: TextView = itemView.findViewById(R.id.simple_language)
|
||||
val pages: TextView = itemView.findViewById(R.id.pages)
|
||||
val downloaded: ImageView = itemView.findViewById(R.id.downloaded)
|
||||
val favourited: ImageView = itemView.findViewById(R.id.favourited)
|
||||
}
|
||||
|
||||
private inner class MoveDialogHelper(
|
||||
@@ -373,7 +379,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)
|
||||
@@ -428,6 +434,8 @@ class HistoryScene : ToolbarScene() {
|
||||
}
|
||||
holder.downloaded.visibility =
|
||||
if (mDownloadManager.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
|
||||
holder.favourited.visibility =
|
||||
if (gi.favoriteSlot != -2) View.VISIBLE else View.GONE
|
||||
holder.card.setOnClickListener { onItemClick(gi) }
|
||||
holder.card.setOnLongClickListener { onItemLongClick(gi) }
|
||||
}
|
||||
|
||||
@@ -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,313 @@
|
||||
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()
|
||||
true
|
||||
}
|
||||
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(private var mHint: String, private 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;
|
||||
@@ -114,7 +113,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
outState.putInt(KEY_REQUEST_ID, mRequestId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
@@ -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));
|
||||
@@ -285,16 +280,16 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
showProgress(true);
|
||||
|
||||
// Clean up for sign in
|
||||
EhUtils.signOut(context);
|
||||
EhUtils.signOut();
|
||||
|
||||
EhCallback callback = new SignInListener(context,
|
||||
EhCallback<?, ?> callback = new SignInListener(context,
|
||||
activity.getStageId(), getTag());
|
||||
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
|
||||
EhRequest request = new EhRequest()
|
||||
.setMethod(EhClient.METHOD_SIGN_IN)
|
||||
.setArgs(username, password)
|
||||
.setCallback(callback);
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
|
||||
mSigningIn = true;
|
||||
}
|
||||
@@ -309,13 +304,16 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
hideSoftInput();
|
||||
showProgress(true);
|
||||
|
||||
EhCallback callback = new GetProfileListener(context,
|
||||
EhCallback<?, ?> callback = new GetProfileListener(context,
|
||||
activity.getStageId(), getTag());
|
||||
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
|
||||
EhRequest request = new EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_PROFILE)
|
||||
.setCallback(callback);
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
EhApplication.getEhClient().execute(request);
|
||||
|
||||
EhApplication.getEhClient().execute(new EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_UCONFIG));
|
||||
}
|
||||
|
||||
private void redirectTo() {
|
||||
@@ -346,7 +344,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
return;
|
||||
}
|
||||
|
||||
if (EhApplication.getEhCookieStore(context).hasSignedIn()) {
|
||||
if (EhApplication.getEhCookieStore().hasSignedIn()) {
|
||||
getProfile();
|
||||
} else {
|
||||
mSigningIn = false;
|
||||
|
||||
@@ -65,7 +65,7 @@ public class WebViewSignInScene extends SolidScene {
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
|
||||
EhUtils.signOut(context);
|
||||
EhUtils.signOut();
|
||||
|
||||
// http://stackoverflow.com/questions/32284642/how-to-handle-an-uncatched-exception
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
@@ -117,8 +117,8 @@ public class WebViewSignInScene extends SolidScene {
|
||||
return cookies != null ? cookies : Collections.emptyList();
|
||||
}
|
||||
|
||||
private void addCookie(Context context, String domain, Cookie cookie) {
|
||||
EhApplication.getEhCookieStore(context).addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
|
||||
private void addCookie(String domain, Cookie cookie) {
|
||||
EhApplication.getEhCookieStore().addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,8 +142,8 @@ public class WebViewSignInScene extends SolidScene {
|
||||
} else if (EhCookieStore.KEY_IPD_PASS_HASH.equals(cookie.name())) {
|
||||
getHash = true;
|
||||
}
|
||||
addCookie(context, EhUrl.DOMAIN_EX, cookie);
|
||||
addCookie(context, EhUrl.DOMAIN_E, cookie);
|
||||
addCookie(EhUrl.DOMAIN_EX, cookie);
|
||||
addCookie(EhUrl.DOMAIN_E, cookie);
|
||||
}
|
||||
|
||||
if (getId && getHash) {
|
||||
|
||||
@@ -1,202 +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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
public class GalleryGuideView extends ViewGroup implements View.OnClickListener {
|
||||
|
||||
private final float[] mPoints = new float[3 * 4];
|
||||
private int mBgColor;
|
||||
private Paint mPaint;
|
||||
private int mStep;
|
||||
|
||||
private TextView mLeftText;
|
||||
private TextView mRightText;
|
||||
private TextView mMenuText;
|
||||
private TextView mProgressText;
|
||||
private TextView mLongClickText;
|
||||
|
||||
public GalleryGuideView(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public GalleryGuideView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public GalleryGuideView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mBgColor = Color.GRAY;
|
||||
mPaint = new Paint();
|
||||
mPaint.setColor(Color.WHITE);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeWidth(context.getResources().getDimension(R.dimen.gallery_guide_divider_width));
|
||||
setOnClickListener(this);
|
||||
setWillNotDraw(false);
|
||||
bind();
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
// Clear up
|
||||
removeAllViews();
|
||||
mLeftText = null;
|
||||
mRightText = null;
|
||||
mMenuText = null;
|
||||
mProgressText = null;
|
||||
mLongClickText = null;
|
||||
|
||||
switch (mStep) {
|
||||
case 0:
|
||||
bind1();
|
||||
break;
|
||||
default:
|
||||
case 1:
|
||||
bind2();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void bind1() {
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
inflater.inflate(R.layout.widget_gallery_guide_1, this);
|
||||
mLeftText = (TextView) getChildAt(0);
|
||||
mRightText = (TextView) getChildAt(1);
|
||||
mMenuText = (TextView) getChildAt(2);
|
||||
mProgressText = (TextView) getChildAt(3);
|
||||
}
|
||||
|
||||
private void bind2() {
|
||||
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
inflater.inflate(R.layout.widget_gallery_guide_2, this);
|
||||
mLongClickText = (TextView) getChildAt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
if (MeasureSpec.EXACTLY != widthMode || MeasureSpec.EXACTLY != heightMode) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
switch (mStep) {
|
||||
case 0:
|
||||
mLeftText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
|
||||
mRightText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
|
||||
mMenuText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize / 2, MeasureSpec.EXACTLY));
|
||||
mProgressText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize / 2, MeasureSpec.EXACTLY));
|
||||
break;
|
||||
default:
|
||||
case 1:
|
||||
mLongClickText.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
|
||||
break;
|
||||
}
|
||||
|
||||
setMeasuredDimension(widthSize, heightSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int width = r - l;
|
||||
int height = b - t;
|
||||
|
||||
switch (mStep) {
|
||||
case 0:
|
||||
mLeftText.layout(0, 0, width / 3, height);
|
||||
mRightText.layout(width * 2 / 3, 0, width, height);
|
||||
mMenuText.layout(width / 3, 0, width * 2 / 3, height / 2);
|
||||
mProgressText.layout(width / 3, height / 2, width * 2 / 3, height);
|
||||
break;
|
||||
default:
|
||||
case 1:
|
||||
mLongClickText.layout(0, 0, width, height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
if (0 == mStep) {
|
||||
mPoints[0] = w / 3;
|
||||
mPoints[1] = 0;
|
||||
mPoints[2] = w / 3;
|
||||
mPoints[3] = h;
|
||||
|
||||
mPoints[4] = w * 2 / 3;
|
||||
mPoints[5] = 0;
|
||||
mPoints[6] = w * 2 / 3;
|
||||
mPoints[7] = h;
|
||||
|
||||
mPoints[8] = w / 3;
|
||||
mPoints[9] = h / 2;
|
||||
mPoints[10] = w * 2 / 3;
|
||||
mPoints[11] = h / 2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawColor(mBgColor);
|
||||
if (0 == mStep) {
|
||||
canvas.drawLines(mPoints, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (mStep) {
|
||||
case 0:
|
||||
mStep++;
|
||||
bind();
|
||||
break;
|
||||
default:
|
||||
case 1:
|
||||
Settings.putGuideGallery(false);
|
||||
ViewUtils.removeFromParent(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.view.DisplayCutoutCompat;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.ObjectUtils;
|
||||
|
||||
public class GalleryHeader extends ViewGroup {
|
||||
|
||||
private final Rect batteryRect = new Rect();
|
||||
private final Rect progressRect = new Rect();
|
||||
private final Rect clockRect = new Rect();
|
||||
private final int[] location = new int[2];
|
||||
private DisplayCutoutCompat displayCutout;
|
||||
private int topInsets = 0;
|
||||
private View battery;
|
||||
private View progress;
|
||||
private View clock;
|
||||
private int lastX = 0;
|
||||
private int lastY = 0;
|
||||
|
||||
public GalleryHeader(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void setDisplayCutout(@Nullable DisplayCutoutCompat displayCutout) {
|
||||
if (!ObjectUtils.equal(this.displayCutout, displayCutout)) {
|
||||
this.displayCutout = displayCutout;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public void setTopInsets(int topInsets) {
|
||||
if (this.topInsets != topInsets) {
|
||||
this.topInsets = topInsets;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
battery = findViewById(R.id.battery);
|
||||
progress = findViewById(R.id.progress);
|
||||
clock = findViewById(R.id.clock);
|
||||
}
|
||||
|
||||
private void measureChild(Rect rect, View view, int width, int paddingLeft, int paddingRight) {
|
||||
int left;
|
||||
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
|
||||
if (view == battery) {
|
||||
left = paddingLeft + lp.leftMargin;
|
||||
} else if (view == progress) {
|
||||
left = paddingLeft + (width - paddingLeft - paddingRight) / 2 - view.getMeasuredWidth() / 2;
|
||||
} else {
|
||||
left = width - paddingRight - lp.rightMargin - view.getMeasuredWidth();
|
||||
}
|
||||
rect.set(left, lp.topMargin + topInsets, left + view.getMeasuredWidth(), lp.topMargin + topInsets + view.getMeasuredHeight());
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
private boolean offsetVertically(Rect rect, View view, int width) {
|
||||
int offset = 0;
|
||||
|
||||
measureChild(rect, view, width, 0, 0);
|
||||
rect.offset(lastX, lastY);
|
||||
|
||||
for (Rect notch : displayCutout.getBoundingRects()) {
|
||||
if (Rect.intersects(notch, rect)) {
|
||||
offset = Math.max(offset, notch.bottom - lastY);
|
||||
}
|
||||
}
|
||||
|
||||
if (offset != 0) {
|
||||
rect.offset(-lastX, -lastY);
|
||||
rect.offset(0, offset);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
private int getOffsetLeft(Rect rect, View view, int width) {
|
||||
int offset = 0;
|
||||
|
||||
measureChild(rect, view, width, 0, 0);
|
||||
rect.offset(lastX, lastY);
|
||||
|
||||
for (Rect notch : displayCutout.getBoundingRects()) {
|
||||
if (Rect.intersects(notch, rect)) {
|
||||
offset = Math.max(offset, notch.right - lastX);
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.P)
|
||||
private int getOffsetRight(Rect rect, View view, int width) {
|
||||
int offset = 0;
|
||||
|
||||
measureChild(rect, view, width, 0, 0);
|
||||
rect.offset(lastX, lastY);
|
||||
|
||||
for (Rect notch : displayCutout.getBoundingRects()) {
|
||||
if (Rect.intersects(notch, rect)) {
|
||||
offset = Math.max(offset, lastX + width - notch.left);
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int height = 0;
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
measureChild(child, widthMeasureSpec, heightMeasureSpec);
|
||||
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
|
||||
height = Math.max(height, child.getMeasuredHeight() + lp.topMargin + topInsets);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && displayCutout != null) {
|
||||
// Check progress covered
|
||||
if (offsetVertically(progressRect, progress, width)) {
|
||||
offsetVertically(batteryRect, battery, width);
|
||||
offsetVertically(clockRect, clock, width);
|
||||
height = Math.max(progressRect.bottom, Math.max(batteryRect.bottom, clockRect.bottom));
|
||||
} else {
|
||||
// Clamp left and right
|
||||
int paddingLeft = getOffsetLeft(batteryRect, battery, width);
|
||||
int paddingRight = getOffsetRight(clockRect, clock, width);
|
||||
measureChild(batteryRect, battery, width, paddingLeft, paddingRight);
|
||||
measureChild(progressRect, progress, width, paddingLeft, paddingRight);
|
||||
measureChild(clockRect, clock, width, paddingLeft, paddingRight);
|
||||
}
|
||||
} else {
|
||||
measureChild(batteryRect, battery, width, 0, 0);
|
||||
measureChild(progressRect, progress, width, 0, 0);
|
||||
measureChild(clockRect, clock, width, 0, 0);
|
||||
}
|
||||
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
battery.layout(batteryRect.left, batteryRect.top, batteryRect.right, batteryRect.bottom);
|
||||
progress.layout(progressRect.left, progressRect.top, progressRect.right, progressRect.bottom);
|
||||
clock.layout(clockRect.left, clockRect.top, clockRect.right, clockRect.bottom);
|
||||
|
||||
getLocationOnScreen(location);
|
||||
if (lastX != location[0] || lastY != location[1]) {
|
||||
lastX = location[0];
|
||||
lastY = location[1];
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MarginLayoutParams generateLayoutParams(AttributeSet attrs) {
|
||||
return new MarginLayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
||||
return p instanceof MarginLayoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MarginLayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
|
||||
if (lp instanceof MarginLayoutParams) {
|
||||
return new MarginLayoutParams((MarginLayoutParams) lp);
|
||||
}
|
||||
return new MarginLayoutParams(lp);
|
||||
}
|
||||
}
|
||||
@@ -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,7 +23,12 @@ 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
|
||||
@@ -48,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?) {
|
||||
@@ -64,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
|
||||
}
|
||||
}
|
||||
@@ -97,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)
|
||||
@@ -109,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
|
||||
@@ -121,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,78 +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.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatSeekBar;
|
||||
|
||||
public class ReversibleSeekBar extends AppCompatSeekBar {
|
||||
|
||||
private boolean mReverse;
|
||||
|
||||
public ReversibleSeekBar(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ReversibleSeekBar(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public ReversibleSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setReverse(boolean reverse) {
|
||||
mReverse = reverse;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
boolean reverse = mReverse;
|
||||
int saveCount = 0;
|
||||
if (reverse) {
|
||||
saveCount = canvas.save();
|
||||
float px = this.getWidth() / 2.0f;
|
||||
float py = this.getHeight() / 2.0f;
|
||||
canvas.scale(-1, 1, px, py);
|
||||
}
|
||||
super.draw(canvas);
|
||||
if (reverse) {
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
boolean reverse = mReverse;
|
||||
float x = 0.0f, y = 0.0f;
|
||||
if (reverse) {
|
||||
x = event.getX();
|
||||
y = event.getY();
|
||||
event.setLocation(getWidth() - x, y);
|
||||
}
|
||||
boolean result = super.onTouchEvent(event);
|
||||
if (reverse) {
|
||||
event.setLocation(x, y);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,681 +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 final Bundle savedState) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
val mCategoryStored = mSharePref.getInt(SEARCH_CATEGORY_PREF, EhConfig.ALL_CATEGORY)
|
||||
mCategoryGroup = mNormalView.findViewById(R.id.search_category_chipgroup)
|
||||
for (mPair in mCategoryTable) {
|
||||
val mChip = IdentifiedChip(context)
|
||||
val mChip = inflate(context, R.layout.filter_chip, null) as IdentifiedChip
|
||||
mChip.isCheckable = true
|
||||
mChip.setText(mPair.second)
|
||||
mChip.idt = mPair.first
|
||||
@@ -327,12 +327,6 @@ class SearchLayout @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
inner class IdentifiedChip @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Chip(context, attrs, R.attr.filterChipStyle) {
|
||||
var idt = 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SEARCH_MODE_NORMAL = 0
|
||||
const val SEARCH_MODE_IMAGE = 1
|
||||
@@ -362,4 +356,12 @@ class SearchLayout @JvmOverloads constructor(
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class IdentifiedChip @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Chip(
|
||||
context, attrs
|
||||
) {
|
||||
var idt = 0
|
||||
}
|
||||
Loaded 100 of 365 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user