Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
428ae07e7f | ||
|
|
481ad22b53 | ||
|
|
65f58b93ed | ||
|
|
45411fa14d | ||
|
|
8ba1ba4fb1 | ||
|
|
8427e1b3a5 | ||
|
|
32f0f2a0cf | ||
|
|
8b94997cde | ||
|
|
bd2d54f774 | ||
|
|
34be285510 | ||
|
|
7b906f3c3e | ||
|
|
82ba22cda2 | ||
|
|
b1a9601ae9 | ||
|
|
7c1a297a99 | ||
|
|
a8195c0e7c | ||
|
|
08713e3922 | ||
|
|
0485d52e3e | ||
|
|
8b1723e19b | ||
|
|
9cb683dc55 | ||
|
|
dce1589c19 | ||
|
|
107cf16d36 | ||
|
|
2bab97c829 | ||
|
|
5242d825c2 | ||
|
|
11d3799b03 | ||
|
|
55c7f6b896 | ||
|
|
a09d607177 | ||
|
|
626a101208 | ||
|
|
f4ca3acbf6 | ||
|
|
86835a3bd7 | ||
|
|
0ef46e2e11 | ||
|
|
04878447b7 | ||
|
|
3669d5d90e | ||
|
|
ee5e222a13 | ||
|
|
9ea44ff59a | ||
|
|
0474e2c0f5 | ||
|
|
4a909d4b93 | ||
|
|
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 |
No files matched your search
@@ -11,7 +11,7 @@
|
||||
</p>
|
||||
|
||||
<h1 align="center">
|
||||
<img src="art/launcher_icon-web.svg" width="200">
|
||||
<img src="https://github.com/Ehviewer-Overhauled/Art/blob/master/launcher_icon-web.svg" width="200">
|
||||
<br>EhViewer<br>
|
||||
</h1>
|
||||
|
||||
@@ -66,12 +66,12 @@ with Material Design 3 and Dynamic Color Support
|
||||
|
||||
# Download
|
||||
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="art/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Git it on F-Droid" src="art/get-it-on-fdroid.svg" width="200px"/></a>
|
||||
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases"><img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/></a> <a href="https://github.com/Ehviewer-Overhauled/fdroid-repo"><img alt="Git it on F-Droid" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-fdroid.svg" width="200px"/></a>
|
||||
|
||||
# Screenshot
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
# Thanks
|
||||
|
||||
|
||||
+17
-12
@@ -31,8 +31,8 @@ android {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 33
|
||||
versionCode 180026
|
||||
versionName "1.8.4.0-alpha02"
|
||||
versionCode 180028
|
||||
versionName "1.8.5.0"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -94,6 +94,8 @@ android {
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
compose true
|
||||
dataBinding true
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
@@ -123,15 +125,14 @@ 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 "androidx.compose.material3:material3:1.0.1"
|
||||
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.constraintlayout:constraintlayout:2.2.0-alpha05"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
@@ -182,7 +183,7 @@ dependencies {
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.8.0-SNAPSHOT'
|
||||
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'
|
||||
@@ -195,6 +196,10 @@ dependencies {
|
||||
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
+2
@@ -14,6 +14,8 @@
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
|
||||
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
|
||||
|
||||
-renamesourcefileattribute
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
|
||||
@@ -32,10 +32,6 @@
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
|
||||
<application
|
||||
android:name="com.hippo.ehviewer.EhApplication"
|
||||
android:allowBackup="true"
|
||||
@@ -102,9 +98,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)
|
||||
target_link_libraries(${PROJECT_NAME} archive_static log -Wl,--gc-sections)
|
||||
@@ -43,6 +43,11 @@ typedef struct {
|
||||
struct archive_entry *entry;
|
||||
} archive_ctx;
|
||||
|
||||
typedef struct {
|
||||
const char *filename;
|
||||
int index;
|
||||
} entry;
|
||||
|
||||
pthread_mutex_t ctx_lock;
|
||||
static archive_ctx **ctx_pool;
|
||||
#define CTX_POOL_SIZE 20
|
||||
@@ -51,6 +56,8 @@ static bool need_encrypt = false;
|
||||
static char *passwd = NULL;
|
||||
static void *archiveAddr = NULL;
|
||||
static size_t archiveSize = 0;
|
||||
static entry *entries = NULL;
|
||||
static size_t entryCount = 0;
|
||||
|
||||
#define POPULATE_THRESHOLD (512 * 1024 * 1024) // 512 MiB
|
||||
|
||||
@@ -77,6 +84,30 @@ static inline int filename_is_playable_file(const char *name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int compare_entries(const void *a, const void *b) {
|
||||
const char *fa = ((entry *) a)->filename;
|
||||
const char *fb = ((entry *) b)->filename;
|
||||
int a_len = (int) strlen(fa);
|
||||
int b_len = (int) strlen(fb);
|
||||
return a_len == b_len ? strcmp(fa, fb) : a_len - b_len;
|
||||
}
|
||||
|
||||
static long archive_map_entries_index(archive_ctx *ctx) {
|
||||
long count = 0;
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK) {
|
||||
const char *name = archive_entry_pathname(ctx->entry);
|
||||
if (filename_is_playable_file(name)) {
|
||||
entries[count].filename = strdup(name);
|
||||
entries[count].index = count;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (!count)
|
||||
LOGE("%s", archive_error_string(ctx->arc));
|
||||
qsort(entries, entryCount, sizeof(entry), compare_entries);
|
||||
return count;
|
||||
}
|
||||
|
||||
static long archive_list_all_entries(archive_ctx *ctx) {
|
||||
long count = 0;
|
||||
while (archive_read_next_header(ctx->arc, &ctx->entry) == ARCHIVE_OK)
|
||||
@@ -221,10 +252,9 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
if (r) {
|
||||
if (r == -ENOMEM)
|
||||
LOGE("%s", "Mem alloc failed");
|
||||
r = 0;
|
||||
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||
} else {
|
||||
r = archive_list_all_entries(ctx);
|
||||
entryCount = archive_list_all_entries(ctx);
|
||||
LOGI("%s%ld%s", "Found ", r, " image entries in archive");
|
||||
|
||||
// We must read through the file|vm then we can know whether it is encrypted
|
||||
@@ -251,6 +281,19 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
}
|
||||
}
|
||||
|
||||
archive_release_ctx(ctx);
|
||||
|
||||
r = archive_alloc_ctx(&ctx);
|
||||
if (r) {
|
||||
if (r == -ENOMEM)
|
||||
LOGE("%s", "Mem alloc failed");
|
||||
r = 0;
|
||||
LOGE("%s%s", "Archive open failed:", archive_error_string(ctx->arc));
|
||||
} else {
|
||||
entries = calloc(entryCount, sizeof(entry));
|
||||
r = archive_map_entries_index(ctx);
|
||||
}
|
||||
|
||||
archive_release_ctx(ctx);
|
||||
return r;
|
||||
}
|
||||
@@ -259,6 +302,7 @@ JNIEXPORT jobject JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToByteBuffer(JNIEnv *env, jobject thiz, jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
@@ -295,6 +339,10 @@ Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||
passwd = NULL;
|
||||
need_encrypt = false;
|
||||
munmap(archiveAddr, archiveSize);
|
||||
for (int i = 0; i < entryCount; ++i) {
|
||||
free((void *) entries[i].filename);
|
||||
}
|
||||
free(entries);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
@@ -338,6 +386,7 @@ JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_getFilename(JNIEnv *env, jobject thiz, jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
@@ -352,6 +401,7 @@ JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint index, jint fd) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
index = entries[index].index;
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
@@ -363,6 +413,7 @@ Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint in
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_releaseByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) {
|
||||
EH_UNUSED(thiz);
|
||||
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
free(addr);
|
||||
}
|
||||
@@ -1,143 +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_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: 67b9c960d2...c9298fd6dc.
+1
-1
Submodule app/src/main/cpp/liblzma updated: 12b86bd8ca...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)
|
||||
}
|
||||
@@ -250,21 +250,7 @@ public final class DiskLruCache implements Closeable {
|
||||
|
||||
private static String inputStreamToString(InputStream in) throws IOException {
|
||||
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
|
||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // Closed.
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void readJournal() throws IOException {
|
||||
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
|
||||
@@ -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
|
||||
|
||||
@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
import com.hippo.util.HashCodeUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -85,32 +86,13 @@ public class SimpleDiskCache {
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
digest.update(key.getBytes());
|
||||
cacheKey = bytesToHexString(digest.digest());
|
||||
cacheKey = HashCodeUtils.bytesToHexString(digest.digest());
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
cacheKey = String.valueOf(key.hashCode());
|
||||
}
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/332079
|
||||
*
|
||||
* @param bytes The bytes to convert.
|
||||
* @return A {@link String} converted from the bytes of a hashable key used
|
||||
* to store a filename on the disk, to hex digits.
|
||||
*/
|
||||
private static String bytesToHexString(final byte[] bytes) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final byte b : bytes) {
|
||||
final String hex = Integer.toHexString(0xFF & b);
|
||||
if (hex.length() == 1) {
|
||||
builder.append('0');
|
||||
}
|
||||
builder.append(hex);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public synchronized boolean isValid() {
|
||||
return mDiskLruCache != null;
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class BatteryDrawable extends Drawable {
|
||||
|
||||
public static final int WARN_LIMIT = 15;
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = BatteryDrawable.class.getSimpleName();
|
||||
private final Paint mPaint;
|
||||
private final Rect mTopRect;
|
||||
private final Rect mBottomRect;
|
||||
private final Rect mRightRect;
|
||||
private final Rect mHeadRect;
|
||||
private final Rect mElectRect;
|
||||
private int mColor = Color.WHITE;
|
||||
private int mWarningColor = Color.RED;
|
||||
private int mElect = -1;
|
||||
private int mStart;
|
||||
private int mStop;
|
||||
|
||||
public BatteryDrawable() {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mTopRect = new Rect();
|
||||
mBottomRect = new Rect();
|
||||
mRightRect = new Rect();
|
||||
mHeadRect = new Rect();
|
||||
mElectRect = new Rect();
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("SuspiciousNameCombination")
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
int width = bounds.width();
|
||||
int height = bounds.height();
|
||||
int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f);
|
||||
int turn1 = width * 6 / 7;
|
||||
int turn2 = height / 3;
|
||||
int secBottom = height - strokeWidth;
|
||||
mStart = strokeWidth;
|
||||
mStop = turn1 - strokeWidth;
|
||||
|
||||
mTopRect.set(0, 0, turn1, strokeWidth);
|
||||
mBottomRect.set(0, secBottom, turn1, height);
|
||||
mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom);
|
||||
mHeadRect.set(turn1, turn2, width, height - turn2);
|
||||
mElectRect.set(0, strokeWidth, mStop, secBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* How to draw:<br>
|
||||
* |------------------------------|<br>
|
||||
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
|
||||
* |------------------------------|---|<br>
|
||||
* |/////////////////| |//|\\\|<br>
|
||||
* |/////////////////| |//|\\\|<br>
|
||||
* |------------------------------|---|<br>
|
||||
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
|
||||
* |------------------------------|
|
||||
*/
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mElect == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f);
|
||||
|
||||
canvas.drawRect(mTopRect, mPaint);
|
||||
canvas.drawRect(mBottomRect, mPaint);
|
||||
canvas.drawRect(mRightRect, mPaint);
|
||||
canvas.drawRect(mHeadRect, mPaint);
|
||||
canvas.drawRect(mElectRect, mPaint);
|
||||
}
|
||||
|
||||
private boolean isWarn() {
|
||||
return mElect <= WARN_LIMIT;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mColor == color) {
|
||||
return;
|
||||
}
|
||||
|
||||
mColor = color;
|
||||
if (!isWarn()) {
|
||||
mPaint.setColor(mColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void setWarningColor(int color) {
|
||||
if (mWarningColor == color) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWarningColor = color;
|
||||
if (isWarn()) {
|
||||
mPaint.setColor(mWarningColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void setElect(int elect) {
|
||||
if (mElect == elect) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElect = elect;
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
public void setElect(int elect, boolean warn) {
|
||||
if (mElect == elect) {
|
||||
return;
|
||||
}
|
||||
|
||||
mElect = elect;
|
||||
updatePaint(warn);
|
||||
}
|
||||
|
||||
private void updatePaint() {
|
||||
updatePaint(isWarn());
|
||||
}
|
||||
|
||||
private void updatePaint(boolean warn) {
|
||||
if (warn) {
|
||||
mPaint.setColor(mWarningColor);
|
||||
} else {
|
||||
mPaint.setColor(mColor);
|
||||
}
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mPaint.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ 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
|
||||
@@ -26,12 +27,13 @@ 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.Coil
|
||||
import coil.ImageLoader
|
||||
import coil.ImageLoaderFactory
|
||||
import com.hippo.Native
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.beerbelly.SimpleDiskCache
|
||||
@@ -41,6 +43,7 @@ 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.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhX509TrustManager
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
@@ -52,24 +55,29 @@ import com.hippo.ehviewer.ui.EhActivity
|
||||
import com.hippo.scene.SceneApplication
|
||||
import com.hippo.util.BitmapUtils
|
||||
import com.hippo.util.ExceptionUtils
|
||||
import com.hippo.util.IoThreadPoolExecutor
|
||||
import com.hippo.util.ReadableTime
|
||||
import com.hippo.yorozuya.FileUtils
|
||||
import com.hippo.yorozuya.IntIdGenerator
|
||||
import 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() {
|
||||
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
|
||||
@@ -81,9 +89,10 @@ class EhApplication : SceneApplication() {
|
||||
mActivityList.forEach { it.recreate() }
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
override fun onCreate() {
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(EhlifecycleObserver())
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||
application = this
|
||||
val handler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
||||
@@ -95,7 +104,7 @@ class EhApplication : SceneApplication() {
|
||||
}
|
||||
handler?.uncaughtException(t, e)
|
||||
}
|
||||
super.onCreate()
|
||||
super<SceneApplication>.onCreate()
|
||||
Native.initialize()
|
||||
GetText.initialize(this)
|
||||
Settings.initialize(this)
|
||||
@@ -105,40 +114,43 @@ class EhApplication : SceneApplication() {
|
||||
EhDB.initialize(this)
|
||||
EhEngine.initialize()
|
||||
BitmapUtils.initialize(this)
|
||||
Coil.setImageLoader(ImageLoader.Builder(this).okHttpClient(okHttpClient).build())
|
||||
EhTagDatabase.update()
|
||||
|
||||
// Locales can be managed by system automatically above Snow Cone v2
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()))
|
||||
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
|
||||
|
||||
IoThreadPoolExecutor.getInstance().execute {
|
||||
try {
|
||||
val downloadLocation = Settings.getDownloadLocation()
|
||||
if (Settings.getMediaScan()) {
|
||||
CommonOperations.removeNoMediaFile(downloadLocation)
|
||||
} else {
|
||||
CommonOperations.ensureNoMediaFile(downloadLocation)
|
||||
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)
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
ExceptionUtils.throwIfFatal(t)
|
||||
}
|
||||
|
||||
// Clear temp files
|
||||
try {
|
||||
clearTempDir()
|
||||
} 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
|
||||
theDawnOfNewDay()
|
||||
}
|
||||
|
||||
private fun theDawnOfNewDay() {
|
||||
private suspend fun theDawnOfNewDay() {
|
||||
if (!Settings.getRequestNews()) {
|
||||
return
|
||||
}
|
||||
@@ -150,17 +162,18 @@ class EhApplication : SceneApplication() {
|
||||
EhCookieStore.KEY_IPD_PASS_HASH
|
||||
)
|
||||
) {
|
||||
IoThreadPoolExecutor.getInstance().execute {
|
||||
withContext(Dispatchers.IO) {
|
||||
val referer = EhUrl.REFERER_E
|
||||
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
|
||||
val call = okHttpClient.newCall(request)
|
||||
try {
|
||||
val response = call.execute()
|
||||
val responseBody = response.body
|
||||
val body = responseBody.string()
|
||||
val html = EventPaneParser.parse(body)
|
||||
if (html != null) {
|
||||
showEventPane(html)
|
||||
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()
|
||||
@@ -171,7 +184,7 @@ class EhApplication : SceneApplication() {
|
||||
|
||||
fun showEventPane(html: String) {
|
||||
if (Settings.getHideHvEvents() && html.contains("You have encountered a monster!")) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
val activity = topActivity
|
||||
activity?.runOnUiThread {
|
||||
@@ -242,12 +255,22 @@ class EhApplication : SceneApplication() {
|
||||
mActivityList.remove(activity)
|
||||
}
|
||||
|
||||
internal class EhlifecycleObserver : DefaultLifecycleObserver {
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
if (!locked)
|
||||
locked_last_leave_time = System.currentTimeMillis() / 1000
|
||||
locked = true
|
||||
}
|
||||
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 {
|
||||
@@ -263,7 +286,7 @@ class EhApplication : SceneApplication() {
|
||||
val ehCookieStore by lazy { EhCookieStore(application) }
|
||||
|
||||
@JvmStatic
|
||||
val ehClient by lazy { EhClient(application) }
|
||||
val ehClient by lazy { EhClient() }
|
||||
|
||||
@JvmStatic
|
||||
val ehProxySelector by lazy { EhProxySelector() }
|
||||
@@ -273,7 +296,7 @@ class EhApplication : SceneApplication() {
|
||||
val builder = OkHttpClient.Builder()
|
||||
.cookieJar(ehCookieStore)
|
||||
.cache(Cache(File(application.cacheDir, "http_cache"), 50L * 1024L * 1024L))
|
||||
.dns(EhDns())
|
||||
.dns(EhDns)
|
||||
.proxySelector(ehProxySelector)
|
||||
|
||||
if (Settings.getDF()) {
|
||||
@@ -292,10 +315,11 @@ class EhApplication : SceneApplication() {
|
||||
trustManager = trustManagers[0] as X509TrustManager
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
trustManager = EhX509TrustManager()
|
||||
trustManager = EhX509TrustManager
|
||||
}
|
||||
|
||||
builder.sslSocketFactory(EhSSLSocketFactory(), trustManager)
|
||||
builder.sslSocketFactory(EhSSLSocketFactory, trustManager)
|
||||
builder.proxy(Proxy.NO_PROXY)
|
||||
}
|
||||
builder.build()
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,19 +27,14 @@ 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.EhTagDatabase;
|
||||
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,10 +48,7 @@ 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";
|
||||
@@ -67,8 +59,6 @@ public class Settings {
|
||||
public static final int DEFAULT_DEFAULT_CATEGORIES = EhConfig.ALL_CATEGORY;
|
||||
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
|
||||
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
|
||||
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
|
||||
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
|
||||
/********************
|
||||
****** Privacy and Security
|
||||
********************/
|
||||
@@ -87,45 +77,36 @@ 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;
|
||||
@@ -144,21 +125,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";
|
||||
@@ -235,21 +209,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 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;
|
||||
@@ -262,17 +221,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";
|
||||
@@ -283,19 +231,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)) {
|
||||
@@ -313,7 +261,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);
|
||||
@@ -358,32 +305,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);
|
||||
@@ -434,22 +355,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);
|
||||
}
|
||||
@@ -482,14 +391,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);
|
||||
}
|
||||
@@ -500,17 +401,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() {
|
||||
@@ -523,13 +420,11 @@ 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() {
|
||||
@@ -566,7 +461,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
public static boolean getShowTagTranslations() {
|
||||
return getBoolean(KEY_SHOW_TAG_TRANSLATIONS, DEFAULT_SHOW_TAG_TRANSLATIONS);
|
||||
return getBoolean(KEY_SHOW_TAG_TRANSLATIONS, DEFAULT_SHOW_TAG_TRANSLATIONS) && EhTagDatabase.INSTANCE.isTranslatable();
|
||||
}
|
||||
|
||||
public static void putShowTagTranslations(boolean value) {
|
||||
@@ -581,32 +476,14 @@ public class Settings {
|
||||
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
|
||||
}
|
||||
|
||||
public static void putDefaultCategories(int value) {
|
||||
sEhConfig.defaultCategories = value;
|
||||
sEhConfig.setDirty();
|
||||
putInt(KEY_DEFAULT_CATEGORIES, value);
|
||||
}
|
||||
|
||||
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 +504,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 +608,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 +668,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 +690,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,100 +784,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 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);
|
||||
}
|
||||
@@ -1057,10 +804,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);
|
||||
}
|
||||
@@ -1085,10 +828,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);
|
||||
}
|
||||
@@ -1113,38 +852,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();
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import okhttp3.Dns
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
|
||||
class EhDns : Dns {
|
||||
object EhDns : Dns {
|
||||
private val hosts = EhApplication.hosts
|
||||
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
|
||||
|
||||
@@ -42,7 +42,7 @@ class EhDns : Dns {
|
||||
put(
|
||||
builtInHosts,
|
||||
"e-hentai.org",
|
||||
Pair("104.20.134.21", true),
|
||||
Pair("104.20.134.21", false),
|
||||
Pair("104.20.135.21", false),
|
||||
Pair("172.67.0.127", false),
|
||||
)
|
||||
|
||||
@@ -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,8 +351,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();
|
||||
@@ -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();
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.net.Socket
|
||||
import javax.net.ssl.SSLSocket
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
|
||||
class EhSSLSocketFactory : SSLSocketFactory() {
|
||||
object EhSSLSocketFactory : SSLSocketFactory() {
|
||||
private val sslSocketFactory = getDefault() as SSLSocketFactory
|
||||
override fun getDefaultCipherSuites(): Array<String> {
|
||||
return sslSocketFactory.defaultCipherSuites
|
||||
|
||||
@@ -1,436 +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.client;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.BufferedSource;
|
||||
import okio.Okio;
|
||||
|
||||
public class EhTagDatabase {
|
||||
|
||||
private static final Map<String, String> NAMESPACE_TO_PREFIX = new HashMap<>();
|
||||
// TODO more lock for different language
|
||||
private static final Lock lock = new ReentrantLock();
|
||||
private static volatile EhTagDatabase instance;
|
||||
|
||||
static {
|
||||
NAMESPACE_TO_PREFIX.put("artist", "a:");
|
||||
NAMESPACE_TO_PREFIX.put("cosplayer", "cos:");
|
||||
NAMESPACE_TO_PREFIX.put("character", "c:");
|
||||
NAMESPACE_TO_PREFIX.put("female", "f:");
|
||||
NAMESPACE_TO_PREFIX.put("group", "g:");
|
||||
NAMESPACE_TO_PREFIX.put("language", "l:");
|
||||
NAMESPACE_TO_PREFIX.put("male", "m:");
|
||||
NAMESPACE_TO_PREFIX.put("mixed", "x:");
|
||||
NAMESPACE_TO_PREFIX.put("other", "o:");
|
||||
NAMESPACE_TO_PREFIX.put("parody", "p:");
|
||||
NAMESPACE_TO_PREFIX.put("reclass", "r:");
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final byte[] tags;
|
||||
|
||||
public EhTagDatabase(String name, BufferedSource source) throws IOException {
|
||||
this.name = name;
|
||||
String[] tmp;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
source.readInt();
|
||||
for (String i : source.readUtf8().split("\n")) {
|
||||
tmp = i.split("\r", 2);
|
||||
buffer.append(tmp[0]);
|
||||
buffer.append("\r");
|
||||
try {
|
||||
buffer.append(new String(Base64.decode(tmp[1], Base64.DEFAULT), StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
buffer.append(tmp[1]);
|
||||
}
|
||||
buffer.append("\n");
|
||||
}
|
||||
byte[] b = buffer.toString().getBytes(StandardCharsets.UTF_8);
|
||||
int totalBytes = b.length;
|
||||
tags = new byte[totalBytes];
|
||||
System.arraycopy(b, 0, tags, 0, totalBytes);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static EhTagDatabase getInstance(Context context) {
|
||||
if (isPossible(context)) {
|
||||
return instance;
|
||||
} else {
|
||||
instance = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String namespaceToPrefix(String namespace) {
|
||||
return NAMESPACE_TO_PREFIX.get(namespace);
|
||||
}
|
||||
|
||||
private static String[] getMetadata(Context context) {
|
||||
String[] metadata = context.getResources().getStringArray(R.array.tag_translation_metadata);
|
||||
if (metadata.length == 4) {
|
||||
return metadata;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPossible(Context context) {
|
||||
return getMetadata(context) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] getFileContent(File file, int length) {
|
||||
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
|
||||
byte[] content = new byte[length];
|
||||
source.readFully(content);
|
||||
return content;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] getFileSha1(File file) {
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
int n;
|
||||
byte[] buffer = new byte[4 * 1024];
|
||||
while ((n = is.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, n);
|
||||
}
|
||||
return digest.digest();
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean equals(byte[] b1, byte[] b2) {
|
||||
if (b1 == null && b2 == null) {
|
||||
return true;
|
||||
}
|
||||
if (b1 == null || b2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (b1.length != b2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < b1.length; i++) {
|
||||
if (b1[i] != b2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkData(File sha1File, File dataFile) {
|
||||
byte[] s1 = getFileContent(sha1File, 20);
|
||||
if (s1 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] s2 = getFileSha1(dataFile);
|
||||
if (s2 == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return equals(s1, s2);
|
||||
}
|
||||
|
||||
private static boolean save(OkHttpClient client, String url, File file) {
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
Call call = client.newCall(request);
|
||||
try (Response response = call.execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
return false;
|
||||
}
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try (InputStream is = body.byteStream(); OutputStream os = new FileOutputStream(file)) {
|
||||
IOUtils.copy(is, os);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ExceptionUtils.throwIfFatal(t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void update(Context context) {
|
||||
String[] urls = getMetadata(context);
|
||||
if (urls == null || urls.length != 4) {
|
||||
// Clear tags if it's not possible
|
||||
instance = null;
|
||||
return;
|
||||
}
|
||||
|
||||
String sha1Name = urls[0];
|
||||
String sha1Url = urls[1];
|
||||
String dataName = urls[2];
|
||||
String dataUrl = urls[3];
|
||||
|
||||
// Clear tags if name if different
|
||||
EhTagDatabase tmp = instance;
|
||||
if (tmp != null && !tmp.name.equals(dataName)) {
|
||||
instance = null;
|
||||
}
|
||||
|
||||
IoThreadPoolExecutor.getInstance().execute(() -> {
|
||||
if (!lock.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
File dir = AppConfig.getFilesDir("tag-translations");
|
||||
if (dir == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check current sha1 and current data
|
||||
File sha1File = new File(dir, sha1Name);
|
||||
File dataFile = new File(dir, dataName);
|
||||
if (!checkData(sha1File, dataFile)) {
|
||||
FileUtils.delete(sha1File);
|
||||
FileUtils.delete(dataFile);
|
||||
}
|
||||
|
||||
// Read current EhTagDatabase
|
||||
if (instance == null && dataFile.exists()) {
|
||||
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
|
||||
instance = new EhTagDatabase(dataName, source);
|
||||
} catch (IOException e) {
|
||||
FileUtils.delete(sha1File);
|
||||
FileUtils.delete(dataFile);
|
||||
}
|
||||
}
|
||||
|
||||
OkHttpClient client = EhApplication.getOkHttpClient();
|
||||
|
||||
// Save new sha1
|
||||
File tempSha1File = new File(dir, sha1Name + ".tmp");
|
||||
if (!save(client, sha1Url, tempSha1File)) {
|
||||
FileUtils.delete(tempSha1File);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check new sha1 and current data
|
||||
if (checkData(tempSha1File, dataFile)) {
|
||||
// The data is the same
|
||||
FileUtils.delete(tempSha1File);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save new data
|
||||
File tempDataFile = new File(dir, dataName + ".tmp");
|
||||
if (!save(client, dataUrl, tempDataFile)) {
|
||||
FileUtils.delete(tempDataFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check new sha1 and new data
|
||||
if (!checkData(tempSha1File, tempDataFile)) {
|
||||
FileUtils.delete(tempSha1File);
|
||||
FileUtils.delete(tempDataFile);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace current sha1 and current data with new sha1 and new data
|
||||
FileUtils.delete(sha1File);
|
||||
FileUtils.delete(dataFile);
|
||||
tempSha1File.renameTo(sha1File);
|
||||
tempDataFile.renameTo(dataFile);
|
||||
|
||||
// Read new EhTagDatabase
|
||||
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
|
||||
instance = new EhTagDatabase(dataName, source);
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String getTranslation(String tag) {
|
||||
return search(tags, tag.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String search(byte[] tags, byte[] tag) {
|
||||
int low = 0;
|
||||
int high = tags.length;
|
||||
while (low < high) {
|
||||
int start = (low + high) / 2;
|
||||
// Look for the starting '\n'
|
||||
while (start > -1 && tags[start] != '\n') {
|
||||
start--;
|
||||
}
|
||||
start++;
|
||||
|
||||
// Look for the middle '\r'.
|
||||
int middle = 1;
|
||||
while (tags[start + middle] != '\r') {
|
||||
middle++;
|
||||
}
|
||||
|
||||
// Look for the ending '\n'
|
||||
int end = middle + 1;
|
||||
while (tags[start + end] != '\n') {
|
||||
end++;
|
||||
}
|
||||
|
||||
int compare;
|
||||
int tagIndex = 0;
|
||||
int curIndex = start;
|
||||
|
||||
for (; ; ) {
|
||||
int tagByte = tag[tagIndex] & 0xff;
|
||||
int curByte = tags[curIndex] & 0xff;
|
||||
compare = tagByte - curByte;
|
||||
if (compare != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
tagIndex++;
|
||||
curIndex++;
|
||||
if (tagIndex == tag.length && curIndex == start + middle) {
|
||||
break;
|
||||
}
|
||||
if (tagIndex == tag.length) {
|
||||
compare = -1;
|
||||
break;
|
||||
}
|
||||
if (curIndex == start + middle) {
|
||||
compare = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (compare < 0) {
|
||||
high = start - 1;
|
||||
} else if (compare > 0) {
|
||||
low = start + end + 1;
|
||||
} else {
|
||||
return new String(tags, start + middle + 1, end - middle - 1, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArrayList<Pair<String, String>> suggest(String keyword) {
|
||||
return searchTag(tags, keyword);
|
||||
}
|
||||
|
||||
private ArrayList<Pair<String, String>> searchTag(byte[] tags, String keyword) {
|
||||
ArrayList<Pair<String, String>> searchHints = new ArrayList<>();
|
||||
int begin = 0;
|
||||
while (begin < tags.length - 1) {
|
||||
int start = begin;
|
||||
// Look for the starting '\n'
|
||||
while (tags[start] != '\n') {
|
||||
start++;
|
||||
}
|
||||
|
||||
// Look for the middle '\r'.
|
||||
int middle = 1;
|
||||
while (tags[start + middle] != '\r') {
|
||||
middle++;
|
||||
}
|
||||
|
||||
// Look for the ending '\n'
|
||||
int end = middle + 1;
|
||||
while (tags[start + end] != '\n') {
|
||||
end++;
|
||||
}
|
||||
|
||||
begin = start + end;
|
||||
|
||||
byte[] hintBytes = new byte[end - middle - 1];
|
||||
System.arraycopy(tags, start + middle + 1, hintBytes, 0, end - middle - 1);
|
||||
String hint = new String(hintBytes, StandardCharsets.UTF_8);
|
||||
byte[] tagBytes = new byte[middle];
|
||||
System.arraycopy(tags, start + 1, tagBytes, 0, middle);
|
||||
String tag = new String(tagBytes, StandardCharsets.UTF_8);
|
||||
int index = tag.indexOf(':');
|
||||
boolean keywordMatches;
|
||||
if (index == -1 || index >= tag.length() - 1 || keyword.length() > 2) {
|
||||
keywordMatches = containsIgnoreSpace(tag, keyword);
|
||||
} else {
|
||||
keywordMatches = containsIgnoreSpace(tag.substring(index + 1), keyword);
|
||||
}
|
||||
|
||||
if (keywordMatches || containsIgnoreSpace(hint, keyword)) {
|
||||
Pair<String, String> pair = new Pair<>(hint, tag);
|
||||
if (!searchHints.contains(pair)) {
|
||||
searchHints.add(pair);
|
||||
}
|
||||
}
|
||||
if (searchHints.size() > 20) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return searchHints;
|
||||
}
|
||||
|
||||
private boolean containsIgnoreSpace(String text, String key) {
|
||||
return text.replace(" ", "").contains(key.replace(" ", ""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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.client
|
||||
|
||||
import android.content.Context
|
||||
import com.hippo.ehviewer.AppConfig
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.util.ExceptionUtils
|
||||
import com.hippo.util.HashCodeUtils
|
||||
import com.hippo.yorozuya.FileUtils
|
||||
import com.hippo.yorozuya.IOUtils
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okio.BufferedSource
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
||||
object EhTagDatabase {
|
||||
private lateinit var tags: JSONObject
|
||||
private lateinit var tagList: ArrayList<Pair<String, String>>
|
||||
|
||||
fun isInitialized(): Boolean {
|
||||
return this::tags.isInitialized && this::tagList.isInitialized
|
||||
}
|
||||
|
||||
private fun updateData(source: BufferedSource) {
|
||||
try {
|
||||
val tmpTags = JSONObject(source.readString(StandardCharsets.UTF_8))
|
||||
val tmpTagList = arrayListOf<Pair<String, String>>()
|
||||
tmpTags.keys().forEachRemaining { prefix: String ->
|
||||
val values = tmpTags.optJSONObject(prefix)
|
||||
values?.let {
|
||||
it.keys().forEachRemaining { tag: String ->
|
||||
tmpTagList.add(Pair("$prefix:$tag", values.optString(tag)))
|
||||
}
|
||||
}
|
||||
}
|
||||
tags = tmpTags
|
||||
tagList = tmpTagList
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun getTranslation(prefix: String?, tag: String?): String? {
|
||||
return tags.optJSONObject(prefix)?.optString(tag)?.trim()?.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
/* Construct a cold flow for tag database suggestions */
|
||||
fun suggestFlow(
|
||||
keyword: String,
|
||||
translate: Boolean,
|
||||
exactly: Boolean = false
|
||||
): Flow<Pair<String?, String>> = flow {
|
||||
tagList.forEach {
|
||||
val tag = it.first
|
||||
val hint = if (translate) it.second else null
|
||||
val index = tag.indexOf(':')
|
||||
if (tag.substring(index + 1).trim() == keyword) {
|
||||
if (exactly)
|
||||
emit(Pair(hint, tag))
|
||||
return@forEach
|
||||
}
|
||||
if (exactly)
|
||||
return@forEach
|
||||
val keywordMatches: Boolean =
|
||||
if (index == -1 || index >= tag.length - 1 || keyword.length > 2) {
|
||||
containsIgnoreSpace(tag, keyword)
|
||||
} else {
|
||||
containsIgnoreSpace(tag.substring(index + 1), keyword)
|
||||
}
|
||||
if (keywordMatches || containsIgnoreSpace(hint, keyword)) {
|
||||
emit(Pair(hint, tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun containsIgnoreSpace(text: String?, key: String): Boolean {
|
||||
return text != null && text.replace(" ", "").contains(key.replace(" ", ""))
|
||||
}
|
||||
|
||||
private val NAMESPACE_TO_PREFIX = HashMap<String, String>().also {
|
||||
it["artist"] = "a"
|
||||
it["cosplayer"] = "cos"
|
||||
it["character"] = "c"
|
||||
it["female"] = "f"
|
||||
it["group"] = "g"
|
||||
it["language"] = "l"
|
||||
it["male"] = "m"
|
||||
it["mixed"] = "x"
|
||||
it["other"] = "o"
|
||||
it["parody"] = "p"
|
||||
it["reclass"] = "r"
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun namespaceToPrefix(namespace: String): String? {
|
||||
return NAMESPACE_TO_PREFIX[namespace]
|
||||
}
|
||||
|
||||
private fun getMetadata(context: Context): Array<String>? {
|
||||
return context.resources.getStringArray(R.array.tag_translation_metadata)
|
||||
.takeIf { it.size == 4 }
|
||||
}
|
||||
|
||||
fun isTranslatable(): Boolean {
|
||||
return EhApplication.application.resources.getBoolean(R.bool.tag_translatable)
|
||||
}
|
||||
|
||||
private fun getFileContent(file: File): String? {
|
||||
try {
|
||||
file.source().buffer().use { return it.readString(StandardCharsets.UTF_8) }
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileSha1(file: File): String? {
|
||||
try {
|
||||
FileInputStream(file).use { stream ->
|
||||
val digest = MessageDigest.getInstance("SHA-1")
|
||||
var n: Int
|
||||
val buffer = ByteArray(4 * 1024)
|
||||
while (stream.read(buffer).also { n = it } != -1) {
|
||||
digest.update(buffer, 0, n)
|
||||
}
|
||||
return HashCodeUtils.bytesToHexString(digest.digest())
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
return null
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkData(sha1File: File, dataFile: File): Boolean {
|
||||
val s1 = getFileContent(sha1File) ?: return false
|
||||
val s2 = getFileSha1(dataFile) ?: return false
|
||||
return s1 == s2
|
||||
}
|
||||
|
||||
private fun save(client: OkHttpClient, url: String, file: File): Boolean {
|
||||
val request: Request = Request.Builder().url(url).build()
|
||||
val call = client.newCall(request)
|
||||
try {
|
||||
call.execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
return false
|
||||
}
|
||||
val body = response.body
|
||||
body.byteStream()
|
||||
.use { ins -> FileOutputStream(file).use { os -> IOUtils.copy(ins, os) } }
|
||||
return true
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
FileUtils.delete(file)
|
||||
t.printStackTrace()
|
||||
ExceptionUtils.throwIfFatal(t)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@JvmStatic
|
||||
fun update() {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
updateInternal()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun updateInternal() {
|
||||
val urls = getMetadata(EhApplication.application)
|
||||
urls?.let {
|
||||
val sha1Name = urls[0]
|
||||
val sha1Url = urls[1]
|
||||
val dataName = urls[2]
|
||||
val dataUrl = urls[3]
|
||||
|
||||
try {
|
||||
val dir = AppConfig.getFilesDir("tag-translations")
|
||||
checkNotNull(dir)
|
||||
|
||||
// Check current sha1 and current data
|
||||
val sha1File = File(dir, sha1Name)
|
||||
val dataFile = File(dir, dataName)
|
||||
if (!checkData(sha1File, dataFile)) {
|
||||
FileUtils.delete(sha1File)
|
||||
FileUtils.delete(dataFile)
|
||||
}
|
||||
|
||||
// Read current EhTagDatabase
|
||||
if (!isInitialized() && dataFile.exists()) {
|
||||
try {
|
||||
dataFile.source().buffer().use { updateData(it) }
|
||||
} catch (e: IOException) {
|
||||
FileUtils.delete(sha1File)
|
||||
FileUtils.delete(dataFile)
|
||||
}
|
||||
}
|
||||
val client = okHttpClient
|
||||
|
||||
// Save new sha1
|
||||
val tempSha1File = File(dir, "$sha1Name.tmp")
|
||||
check(save(client, sha1Url, tempSha1File))
|
||||
|
||||
// Check new sha1 and current data
|
||||
if (checkData(tempSha1File, dataFile)) {
|
||||
// The data is the same
|
||||
FileUtils.delete(tempSha1File)
|
||||
return
|
||||
}
|
||||
|
||||
// Save new data
|
||||
val tempDataFile = File(dir, "$dataName.tmp")
|
||||
check(save(client, dataUrl, tempDataFile))
|
||||
|
||||
// Check new sha1 and new data
|
||||
if (!checkData(tempSha1File, tempDataFile)) {
|
||||
FileUtils.delete(tempSha1File)
|
||||
FileUtils.delete(tempDataFile)
|
||||
return
|
||||
}
|
||||
|
||||
// Replace current sha1 and current data with new sha1 and new data
|
||||
FileUtils.delete(sha1File)
|
||||
FileUtils.delete(dataFile)
|
||||
tempSha1File.renameTo(sha1File)
|
||||
tempDataFile.renameTo(dataFile)
|
||||
|
||||
// Read new EhTagDatabase
|
||||
try {
|
||||
dataFile.source().buffer().use { updateData(it) }
|
||||
} catch (_: IOException) {
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -49,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 = {
|
||||
@@ -106,40 +105,29 @@ 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) {
|
||||
public static void signOut() {
|
||||
EhApplication.getEhCookieStore().signOut();
|
||||
Settings.putAvatar(null);
|
||||
Settings.putDisplayName(null);
|
||||
Settings.putNeedSignIn(true);
|
||||
}
|
||||
|
||||
public static boolean needSignedIn(Context context) {
|
||||
public static boolean needSignedIn() {
|
||||
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore().hasSignedIn();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
class EhX509TrustManager : X509TrustManager {
|
||||
object EhX509TrustManager : X509TrustManager {
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -67,6 +68,7 @@ public class GalleryDetailParser {
|
||||
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);
|
||||
@@ -78,6 +80,23 @@ public class GalleryDetailParser {
|
||||
private static final String PINING_STRING =
|
||||
"<p>This gallery is pining for the fjords.</p>";
|
||||
|
||||
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)) {
|
||||
throw new OffensiveException();
|
||||
@@ -100,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;
|
||||
}
|
||||
|
||||
@@ -191,14 +219,9 @@ public class GalleryDetailParser {
|
||||
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));
|
||||
}
|
||||
} 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
|
||||
@@ -230,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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+8
-9
@@ -46,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;
|
||||
@@ -61,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);
|
||||
@@ -77,8 +79,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
super.start();
|
||||
|
||||
int id = sIdGenerator.incrementAndGet();
|
||||
|
||||
archiveThread = new PriorityThread(
|
||||
@@ -91,7 +91,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (archiveThread != null) {
|
||||
archiveThread.interrupt();
|
||||
archiveThread = null;
|
||||
@@ -217,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;
|
||||
@@ -289,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;
|
||||
}
|
||||
|
||||
@@ -347,7 +346,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
notifyPageFailed(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-17
@@ -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,8 +248,8 @@ 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;
|
||||
}
|
||||
|
||||
@@ -260,15 +257,15 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
try {
|
||||
is = files[index].openInputStream();
|
||||
Image image = Image.decode((FileInputStream) is);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
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
|
||||
+15
-14
@@ -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,18 +119,18 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
if (mSpiderQueen != null) {
|
||||
return mSpiderQueen.size();
|
||||
} else {
|
||||
return GalleryProvider.STATE_ERROR;
|
||||
return PageLoader.STATE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRequest(int index) {
|
||||
if (mSpiderQueen != null) {
|
||||
Object object = mSpiderQueen.request(index);
|
||||
Object object = mSpiderQueen.request(index, true);
|
||||
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);
|
||||
}
|
||||
@@ -187,12 +186,14 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
|
||||
|
||||
@Override
|
||||
public void onPageSuccess(int index, int finished, int downloaded, int total) {
|
||||
notifyDataChanged(index);
|
||||
if (mSpiderQueen != null) {
|
||||
mSpiderQueen.request(index, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFailure(int index, String error, int finished, int downloaded, int total) {
|
||||
notifyPageFailed(index, error);
|
||||
notifyPageFailed(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -206,7 +207,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 = {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -48,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;
|
||||
@@ -85,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;
|
||||
@@ -157,7 +156,7 @@ 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) {
|
||||
private SpiderQueen(@NonNull GalleryInfo galleryInfo) {
|
||||
mHttpClient = EhApplication.getOkHttpClient();
|
||||
mSpiderInfoCache = EhApplication.getSpiderInfoCache();
|
||||
mGalleryInfo = galleryInfo;
|
||||
@@ -167,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,
|
||||
@@ -183,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);
|
||||
@@ -358,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) {
|
||||
@@ -375,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) {
|
||||
@@ -407,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;
|
||||
}
|
||||
@@ -427,8 +418,8 @@ public final class SpiderQueen implements Runnable {
|
||||
return request(index, true, true, false);
|
||||
}
|
||||
|
||||
public Object request(int index) {
|
||||
return request(index, true, false, true);
|
||||
public Object request(int index, boolean addNeighbor) {
|
||||
return request(index, true, false, addNeighbor);
|
||||
}
|
||||
|
||||
private int getPageState(int index) {
|
||||
@@ -523,29 +514,25 @@ 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);
|
||||
mDecodeRequestQueue.notify();
|
||||
mDecodeRequestQueue.notifyAll();
|
||||
}
|
||||
}
|
||||
result = null;
|
||||
break;
|
||||
}
|
||||
default -> throw new IllegalStateException("Unexpected value: " + state);
|
||||
}
|
||||
|
||||
tryToEnsureWorkers();
|
||||
@@ -645,7 +632,6 @@ public final class SpiderQueen implements Runnable {
|
||||
return null;
|
||||
}
|
||||
|
||||
OutputStream os = null;
|
||||
try {
|
||||
pipe.obtain();
|
||||
|
||||
@@ -661,7 +647,6 @@ public final class SpiderQueen implements Runnable {
|
||||
} finally {
|
||||
pipe.close();
|
||||
pipe.release();
|
||||
IOUtils.closeQuietly(os);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,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);
|
||||
@@ -777,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);
|
||||
@@ -824,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);
|
||||
|
||||
@@ -858,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
|
||||
}
|
||||
@@ -1150,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;
|
||||
@@ -1284,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();
|
||||
@@ -1297,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;
|
||||
@@ -1575,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
|
||||
@@ -1596,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) {
|
||||
@@ -1619,7 +1595,7 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
private void resetDecodeIndex() {
|
||||
synchronized (mDecodeRequestQueue) {
|
||||
mDecodeIndexArray[mThreadIndex] = GalleryPageView.INVALID_INDEX;
|
||||
mDecodeIndexArray[mThreadIndex] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
@@ -69,6 +70,8 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
WindowInsetsHelper.LISTENER
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
|
||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
|
||||
window.decorView.post {
|
||||
val rootWindowInsets = window.decorView.rootWindowInsets
|
||||
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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());
|
||||
@@ -309,8 +309,6 @@ public final class MainActivity extends StageActivity
|
||||
} else {
|
||||
onRestore(savedInstanceState);
|
||||
}
|
||||
|
||||
EhTagDatabase.update(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -41,8 +41,6 @@ class SettingsActivity : EhActivity() {
|
||||
mFab = findViewById(R.id.fab)
|
||||
mAppbarLayout = findViewById(R.id.appbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
|
||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
|
||||
@@ -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) {
|
||||
@@ -312,36 +313,35 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.app.Activity
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
@@ -36,6 +37,7 @@ class EhFragment : BasePreferenceFragment() {
|
||||
val listThumbSize = findPreference<Preference>(Settings.KEY_LIST_THUMB_SIZE)
|
||||
val detailSize = findPreference<Preference>(Settings.KEY_DETAIL_SIZE)
|
||||
val thumbSize = findPreference<Preference>(Settings.KEY_THUMB_SIZE)
|
||||
val thumbResolution = findPreference<Preference>(Settings.KEY_THUMB_RESOLUTION)
|
||||
val showTagTranslations = findPreference<Preference>(Settings.KEY_SHOW_TAG_TRANSLATIONS)
|
||||
val tagTranslationsSource = findPreference<Preference>("tag_translations_source")
|
||||
Settings.getDisplayName()?.let { account?.summary = it }
|
||||
@@ -47,7 +49,10 @@ class EhFragment : BasePreferenceFragment() {
|
||||
thumbSize!!.onPreferenceChangeListener = this
|
||||
showTagTranslations!!.onPreferenceChangeListener = this
|
||||
blackDarkTheme!!.onPreferenceChangeListener = this
|
||||
if (!EhTagDatabase.isPossible(requireActivity())) {
|
||||
thumbResolution!!.setSummaryProvider {
|
||||
getString(R.string.settings_eh_thumb_resolution_summary, (it as ListPreference).entry)
|
||||
}
|
||||
if (!EhTagDatabase.isTranslatable()) {
|
||||
preferenceScreen.removePreference(showTagTranslations)
|
||||
preferenceScreen.removePreference(tagTranslationsSource!!)
|
||||
}
|
||||
@@ -75,7 +80,7 @@ class EhFragment : BasePreferenceFragment() {
|
||||
requireActivity().setResult(Activity.RESULT_OK)
|
||||
} else if (Settings.KEY_SHOW_TAG_TRANSLATIONS == key) {
|
||||
if (java.lang.Boolean.TRUE == newValue) {
|
||||
EhTagDatabase.update(requireActivity())
|
||||
EhTagDatabase.update()
|
||||
}
|
||||
} else if (Settings.KEY_BLACK_DARK_THEME == key) {
|
||||
if (requireActivity().resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0) {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public class ReadFragment extends BasePreferenceFragment {
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.read_settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFragmentTitle() {
|
||||
return R.string.settings_read;
|
||||
}
|
||||
}
|
||||
@@ -200,11 +200,6 @@ public abstract class BaseScene extends SceneFragment {
|
||||
|
||||
// Hide soft ime
|
||||
hideSoftInput();
|
||||
|
||||
insetsController = getInsetsController();
|
||||
if (insetsController != null) {
|
||||
insetsController.setAppearanceLightStatusBars((requireActivity().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) <= 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -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,7 +207,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
return;
|
||||
}
|
||||
|
||||
EhUtils.signOut(context);
|
||||
EhUtils.signOut();
|
||||
|
||||
EhCookieStore store = EhApplication.getEhCookieStore();
|
||||
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, 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")
|
||||
@@ -596,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;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ import java.util.List;
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
// TODO Get favorite, modify favorite, add favorite, what a mess!
|
||||
@SuppressLint("RtlHardcoded")
|
||||
@SuppressLint({"NotifyDataSetChanged", "RtlHardcoded"})
|
||||
public class FavoritesScene extends SearchBarScene implements
|
||||
FastScroller.OnDragHandlerListener,
|
||||
FabLayout.OnClickFabListener, FabLayout.OnExpandListener,
|
||||
@@ -103,8 +103,6 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
public int current; // -1 for error
|
||||
public int limit; // -1 for error
|
||||
@Nullable
|
||||
private ContentLayout mContentLayout;
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
@@ -231,12 +229,12 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
mUrlBuilder = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
@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) -> {
|
||||
@@ -247,7 +245,8 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
mRecyclerView = mContentLayout.getRecyclerView();
|
||||
FastScroller fastScroller = mContentLayout.getFastScroller();
|
||||
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
|
||||
AssertUtils.assertNotNull(container);
|
||||
container.addView(mFabLayout);
|
||||
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
|
||||
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
|
||||
@@ -291,7 +290,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
// Restore search mode
|
||||
if (mSearchMode) {
|
||||
mSearchMode = false;
|
||||
enterSearchMode(false);
|
||||
enterSearchMode();
|
||||
}
|
||||
|
||||
// Only refresh for the first time
|
||||
@@ -366,7 +365,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
mRecyclerView = null;
|
||||
}
|
||||
if (null != mFabLayout) {
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
|
||||
removeAboveSnackView(mFabLayout);
|
||||
mFabLayout = null;
|
||||
}
|
||||
@@ -427,7 +426,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
} else if (mFabLayout != null && mFabLayout.isExpanded()) {
|
||||
mFabLayout.toggle();
|
||||
} else if (mSearchMode) {
|
||||
exitSearchMode(true);
|
||||
exitSearchMode();
|
||||
} else if (isSearchViewShown()) {
|
||||
hideSearchView();
|
||||
} else {
|
||||
@@ -451,7 +450,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
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()) {
|
||||
@@ -470,12 +469,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure outOfCustomChoiceMode to avoid error
|
||||
//if (mRecyclerView != null) {
|
||||
// mRecyclerView.isInCustomChoice();
|
||||
//}
|
||||
|
||||
exitSearchMode(true);
|
||||
exitSearchMode();
|
||||
|
||||
mUrlBuilder.setKeyword(null);
|
||||
mUrlBuilder.setFavCat(newFavCat);
|
||||
@@ -524,12 +518,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure outOfCustomChoiceMode to avoid error
|
||||
//if (mRecyclerView != null) {
|
||||
// mRecyclerView.isInCustomChoice();
|
||||
//}
|
||||
|
||||
exitSearchMode(true);
|
||||
exitSearchMode();
|
||||
|
||||
mUrlBuilder.setKeyword(query);
|
||||
updateSearchBar();
|
||||
@@ -566,7 +555,7 @@ public class FavoritesScene extends SearchBarScene 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));
|
||||
|
||||
@@ -581,9 +570,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
.setSelection(toDate)
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
mHelper.goTo(v);
|
||||
});
|
||||
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -623,11 +610,9 @@ public class FavoritesScene extends SearchBarScene 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);
|
||||
@@ -636,9 +621,8 @@ public class FavoritesScene extends SearchBarScene 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)
|
||||
@@ -646,9 +630,8 @@ public class FavoritesScene extends SearchBarScene 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];
|
||||
@@ -659,7 +642,6 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
.setItems(array, helper)
|
||||
.setOnCancelListener(helper)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -723,7 +705,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
}
|
||||
}
|
||||
|
||||
private void enterSearchMode(boolean animation) {
|
||||
private void enterSearchMode() {
|
||||
if (mSearchMode) {
|
||||
return;
|
||||
}
|
||||
@@ -731,7 +713,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
private void exitSearchMode(boolean animation) {
|
||||
private void exitSearchMode() {
|
||||
if (!mSearchMode) {
|
||||
return;
|
||||
}
|
||||
@@ -756,9 +738,8 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
}
|
||||
|
||||
updateSearchBar();
|
||||
assert mUrlBuilder != null;
|
||||
|
||||
int pages = 0;
|
||||
int pages;
|
||||
if (result.nextPage == null)
|
||||
pages = mHelper.pgCounter + 1;
|
||||
else
|
||||
@@ -967,7 +948,7 @@ public class FavoritesScene extends SearchBarScene 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
|
||||
@@ -1080,7 +1061,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
|
||||
@Override
|
||||
void onItemClick(View view, int position) {
|
||||
FavoritesScene.this.onItemClick(view, position);
|
||||
FavoritesScene.this.onItemClick(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1114,8 +1095,8 @@ public class FavoritesScene extends SearchBarScene 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);
|
||||
@@ -1133,7 +1114,6 @@ public class FavoritesScene extends SearchBarScene 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;
|
||||
}
|
||||
@@ -1153,7 +1133,7 @@ public class FavoritesScene extends SearchBarScene 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) {
|
||||
@@ -1164,7 +1144,7 @@ public class FavoritesScene extends SearchBarScene 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;
|
||||
@@ -1185,7 +1165,7 @@ public class FavoritesScene extends SearchBarScene implements
|
||||
request.setCallback(new GetFavoritesListener(getContext(),
|
||||
activity.getStageId(), getTag(),
|
||||
taskId, false, mUrlBuilder.getKeyword()));
|
||||
request.setArgs(url, Settings.getShowJpnTitle());
|
||||
request.setArgs(url);
|
||||
mClient.execute(request);
|
||||
}
|
||||
}
|
||||
@@ -1208,9 +1188,9 @@ public class FavoritesScene extends SearchBarScene 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1251,16 +1231,18 @@ public class FavoritesScene extends SearchBarScene 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);
|
||||
|
||||
@@ -65,7 +65,6 @@ import com.google.android.material.snackbar.Snackbar;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.CheckBoxDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.R;
|
||||
@@ -90,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;
|
||||
@@ -119,13 +117,13 @@ import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
import com.hippo.yorozuya.collect.IntList;
|
||||
|
||||
import java.io.File;
|
||||
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;
|
||||
|
||||
@@ -454,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) {
|
||||
@@ -868,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();
|
||||
@@ -929,7 +927,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
mNoTags.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
EhTagDatabase ehTags = Settings.getShowTagTranslations() ? EhTagDatabase.getInstance(context) : null;
|
||||
EhTagDatabase ehTags = Settings.getShowTagTranslations() ? EhTagDatabase.INSTANCE : null;
|
||||
int colorTag = ResourcesKt.resolveColor(getTheme(), R.attr.tagBackgroundColor);
|
||||
int colorName = ResourcesKt.resolveColor(getTheme(), R.attr.tagGroupBackgroundColor);
|
||||
for (GalleryTagGroup tg : tagGroups) {
|
||||
@@ -938,8 +936,8 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
mTags.addView(ll);
|
||||
|
||||
String readableTagName = null;
|
||||
if (ehTags != null) {
|
||||
readableTagName = ehTags.getTranslation("n:" + tg.groupName);
|
||||
if (ehTags != null && ehTags.isInitialized()) {
|
||||
readableTagName = ehTags.getTranslation("n", tg.groupName);
|
||||
}
|
||||
|
||||
TextView tgName = (TextView) inflater.inflate(R.layout.item_gallery_tag, ll, false);
|
||||
@@ -948,9 +946,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
tgName.setBackgroundTintList(ColorStateList.valueOf(colorName));
|
||||
|
||||
String prefix = EhTagDatabase.namespaceToPrefix(tg.groupName);
|
||||
if (prefix == null) {
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
AutoWrapLayout awl = new AutoWrapLayout(context);
|
||||
ll.addView(awl, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
@@ -961,7 +956,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
|
||||
String readableTag = null;
|
||||
if (ehTags != null) {
|
||||
readableTag = ehTags.getTranslation(prefix + tagStr);
|
||||
readableTag = ehTags.getTranslation(prefix, tagStr);
|
||||
}
|
||||
|
||||
tag.setText(readableTag != null ? readableTag : tagStr);
|
||||
@@ -1166,9 +1161,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) {
|
||||
@@ -1300,10 +1295,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -88,7 +87,6 @@ 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;
|
||||
@@ -118,6 +116,7 @@ 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")
|
||||
@@ -140,8 +139,6 @@ public final class GalleryListScene extends SearchBarScene
|
||||
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
|
||||
---------------*/
|
||||
@@ -149,11 +146,6 @@ public final class GalleryListScene extends SearchBarScene
|
||||
private EhClient mClient;
|
||||
@Nullable
|
||||
private ListUrlBuilder mUrlBuilder;
|
||||
/*---------------
|
||||
View life cycle
|
||||
---------------*/
|
||||
@Nullable
|
||||
private ContentLayout mContentLayout;
|
||||
@Nullable
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
@@ -247,17 +239,13 @@ public final class GalleryListScene extends SearchBarScene
|
||||
} 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) {
|
||||
@@ -267,11 +255,6 @@ public final class GalleryListScene extends SearchBarScene
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMenuResId() {
|
||||
return R.menu.scene_gallery_list_searchbar_menu;
|
||||
}
|
||||
|
||||
public static void startScene(SceneFragment scene, ListUrlBuilder lub) {
|
||||
scene.startScene(getStartAnnouncer(lub));
|
||||
}
|
||||
@@ -283,6 +266,11 @@ public final class GalleryListScene extends SearchBarScene
|
||||
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;
|
||||
@@ -325,6 +313,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
showSearchBar();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -547,7 +536,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
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);
|
||||
@@ -558,7 +547,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
mFabLayout,
|
||||
(View) mSearchFab.getParent()
|
||||
));
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
|
||||
container.addView(mFabLayout);
|
||||
|
||||
int paddingTopSB = resources.getDimensionPixelOffset(R.dimen.gallery_padding_top_search_bar);
|
||||
@@ -601,7 +590,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
|
||||
mActionFabDrawable = new AddDeleteDrawable(context, colorID);
|
||||
mFabLayout.getPrimaryFab().setImageDrawable(mActionFabDrawable);
|
||||
mSearchFab.setOnClickListener( (v) -> {
|
||||
mSearchFab.setOnClickListener((v) -> {
|
||||
if (STATE_NORMAL != mState) {
|
||||
onApplySearch();
|
||||
hideSoftInput();
|
||||
@@ -640,7 +629,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
mRecyclerView = null;
|
||||
}
|
||||
if (null != mFabLayout) {
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
|
||||
removeAboveSnackView(mFabLayout);
|
||||
mFabLayout = null;
|
||||
}
|
||||
@@ -651,8 +640,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
mActionFabDrawable = null;
|
||||
}
|
||||
|
||||
private void showQuickSearchTipDialog(final QsDrawerAdapter adapter,
|
||||
final EasyRecyclerView recyclerView, final TextView tip) {
|
||||
private void showQuickSearchTipDialog() {
|
||||
Context context = getContext();
|
||||
if (null == context) {
|
||||
return;
|
||||
@@ -756,7 +744,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
if (id == R.id.action_add) {
|
||||
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
|
||||
} else if (id == R.id.action_help) {
|
||||
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
|
||||
showQuickSearchTipDialog();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -778,15 +766,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
}
|
||||
if (isSearchViewShown())
|
||||
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;
|
||||
return mState == STATE_NORMAL && getStackIndex() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -817,7 +797,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onItemClick(View view, int position) {
|
||||
public boolean onItemClick(int position) {
|
||||
if (null == mHelper || null == mRecyclerView) {
|
||||
return false;
|
||||
}
|
||||
@@ -884,7 +864,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
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));
|
||||
|
||||
@@ -899,9 +879,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
.setSelection(toDate)
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
mHelper.goTo(v);
|
||||
});
|
||||
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -999,9 +977,9 @@ public final class GalleryListScene extends SearchBarScene
|
||||
.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
|
||||
@@ -1186,21 +1164,12 @@ public final class GalleryListScene extends SearchBarScene
|
||||
}
|
||||
break;
|
||||
case STATE_SEARCH:
|
||||
if (state == STATE_NORMAL) {
|
||||
mViewTransition.showView(0, animation);
|
||||
selectActionFab(animation);
|
||||
} else if (state == STATE_SIMPLE_SEARCH) {
|
||||
mViewTransition.showView(0, animation);
|
||||
} else if (state == STATE_SEARCH_SHOW_LIST) {
|
||||
}
|
||||
break;
|
||||
case STATE_SEARCH_SHOW_LIST:
|
||||
if (state == STATE_NORMAL) {
|
||||
mViewTransition.showView(0, animation);
|
||||
selectActionFab(animation);
|
||||
} else if (state == STATE_SIMPLE_SEARCH) {
|
||||
mViewTransition.showView(0, animation);
|
||||
} else if (state == STATE_SEARCH) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1208,12 +1177,6 @@ public final class GalleryListScene extends SearchBarScene
|
||||
((StageActivity) requireActivity()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
public void onClickTitle() {
|
||||
if (mState == STATE_NORMAL) {
|
||||
setState(STATE_SIMPLE_SEARCH);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(@NonNull MenuItem item) {
|
||||
if (mState == STATE_NORMAL) {
|
||||
@@ -1251,15 +1214,6 @@ public final class GalleryListScene extends SearchBarScene
|
||||
setState(STATE_NORMAL);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1308,7 +1262,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
: R.string.gallery_list_empty_hit);
|
||||
mHelper.setEmptyString(emptyString);
|
||||
|
||||
int pages = 0;
|
||||
int pages;
|
||||
if (mIsTopList)
|
||||
pages = 200;
|
||||
else if (result.nextGid == 0)
|
||||
@@ -1619,7 +1573,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
|
||||
@Override
|
||||
void onItemClick(View view, int position) {
|
||||
GalleryListScene.this.onItemClick(view, position);
|
||||
GalleryListScene.this.onItemClick(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1692,6 +1646,7 @@ public final class GalleryListScene extends SearchBarScene
|
||||
return GalleryListScene.this.getContext();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
protected void notifyDataSetChanged() {
|
||||
if (null != mAdapter) {
|
||||
@@ -1700,9 +1655,9 @@ public final class GalleryListScene extends SearchBarScene
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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,7 +139,6 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
Resources resources = context.getResources();
|
||||
|
||||
mAdapter = new GalleryPreviewAdapter();
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.HistoryInfo
|
||||
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
|
||||
@@ -54,6 +53,7 @@ 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
|
||||
@@ -267,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)
|
||||
}
|
||||
|
||||
@@ -371,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(
|
||||
@@ -433,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) }
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.search.SearchBar
|
||||
@@ -18,9 +19,15 @@ 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.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.widget.SearchDatabase
|
||||
import com.hippo.scene.StageActivity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
abstract class SearchBarScene : ToolbarScene() {
|
||||
@@ -41,7 +48,8 @@ abstract class SearchBarScene : ToolbarScene() {
|
||||
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)
|
||||
mAppBarLayout?.statusBarForeground =
|
||||
MaterialShapeDrawable.createWithElevationOverlay(context)
|
||||
|
||||
mSearchView = view.findViewById(R.id.searchview)
|
||||
mRecyclerView = view.findViewById(R.id.search_bar_list)
|
||||
@@ -52,7 +60,7 @@ abstract class SearchBarScene : ToolbarScene() {
|
||||
(mToolbar as SearchBar).text = mSearchView!!.text
|
||||
mSearchView?.hide()
|
||||
onApplySearch()
|
||||
false
|
||||
true
|
||||
}
|
||||
mSuggestionList = ArrayList()
|
||||
mSuggestionAdapter = SuggestionAdapter(LayoutInflater.from(context))
|
||||
@@ -219,9 +227,13 @@ abstract class SearchBarScene : ToolbarScene() {
|
||||
|
||||
}
|
||||
|
||||
inner class TagSuggestion constructor(var mHint: String, var mKeyword: String) : Suggestion() {
|
||||
inner class TagSuggestion constructor(
|
||||
private var mHint: String?,
|
||||
private var mKeyword: String
|
||||
) :
|
||||
Suggestion() {
|
||||
|
||||
override fun getText(textView: TextView): CharSequence {
|
||||
override fun getText(textView: TextView): CharSequence? {
|
||||
return if (textView.id == android.R.id.text1) {
|
||||
mKeyword
|
||||
} else {
|
||||
@@ -275,32 +287,43 @@ abstract class SearchBarScene : ToolbarScene() {
|
||||
|
||||
@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))
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) {
|
||||
val suggestions = mutableListOf<Suggestion>()
|
||||
mergedSuggestionFlow().collect {
|
||||
suggestions.add(it)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
mSuggestionList = suggestions
|
||||
mSuggestionAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
mSuggestionList = suggestions
|
||||
mSuggestionAdapter?.notifyDataSetChanged()
|
||||
if (scrollToTop) {
|
||||
mRecyclerView?.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mergedSuggestionFlow(): Flow<Suggestion> = flow {
|
||||
mSearchView?.editText?.text?.toString()?.let { text ->
|
||||
mSuggestionProvider?.run { providerSuggestions(text)?.forEach { emit(it) } }
|
||||
mSearchDatabase.getSuggestions(text, 128).forEach { emit(KeywordSuggestion(it)) }
|
||||
EhTagDatabase.takeIf { it.isInitialized() }?.run {
|
||||
if (!TextUtils.isEmpty(text) && !text.endsWith(" ")) {
|
||||
val s = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (s.isNotEmpty()) {
|
||||
val keyword = s[s.size - 1]
|
||||
val translate = Settings.getShowTagTranslations() && isTranslatable()
|
||||
suggestFlow(keyword, translate, true).collect {
|
||||
emit(TagSuggestion(it.first, it.second))
|
||||
}
|
||||
suggestFlow(keyword, translate).collect {
|
||||
emit(TagSuggestion(it.first, it.second))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSuggestionProvider(suggestionProvider: SuggestionProvider) {
|
||||
mSuggestionProvider = suggestionProvider
|
||||
}
|
||||
|
||||
@@ -113,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) {
|
||||
@@ -280,9 +280,9 @@ 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()
|
||||
@@ -304,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().execute(request);
|
||||
|
||||
EhApplication.getEhClient().execute(new EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_UCONFIG));
|
||||
}
|
||||
|
||||
private void redirectTo() {
|
||||
|
||||
@@ -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,7 +117,7 @@ public class WebViewSignInScene extends SolidScene {
|
||||
return cookies != null ? cookies : Collections.emptyList();
|
||||
}
|
||||
|
||||
private void addCookie(Context context, String domain, Cookie cookie) {
|
||||
private void addCookie(String domain, Cookie cookie) {
|
||||
EhApplication.getEhCookieStore().addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
@@ -97,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
|
||||
@@ -328,17 +327,6 @@ class SearchLayout @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
inner class IdentifiedChip @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Chip(
|
||||
ContextThemeWrapper(
|
||||
context,
|
||||
com.google.android.material.R.style.Widget_Material3_Chip_Filter
|
||||
), attrs
|
||||
) {
|
||||
var idt = 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SEARCH_MODE_NORMAL = 0
|
||||
const val SEARCH_MODE_IMAGE = 1
|
||||
@@ -368,4 +356,12 @@ class SearchLayout @JvmOverloads constructor(
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class IdentifiedChip @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Chip(
|
||||
context, attrs
|
||||
) {
|
||||
var idt = 0
|
||||
}
|
||||
@@ -1,87 +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.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
public class SeekBarPanel extends LinearLayout {
|
||||
|
||||
private final int[] mLocation = new int[2];
|
||||
private SeekBar mSeekBar;
|
||||
|
||||
public SeekBarPanel(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public SeekBarPanel(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public SeekBarPanel(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public SeekBarPanel(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
post(() -> {
|
||||
var rootWindowInsets = getRootWindowInsets();
|
||||
if (rootWindowInsets != null) {
|
||||
setPadding(0, 0, 0, rootWindowInsets.getSystemWindowInsetBottom());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mSeekBar = (SeekBar) ViewUtils.$$(this, R.id.seek_bar);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
if (mSeekBar == null) {
|
||||
return super.onTouchEvent(event);
|
||||
} else {
|
||||
ViewUtils.getLocationInAncestor(mSeekBar, mLocation, this);
|
||||
final float offsetX = -mLocation[0];
|
||||
final float offsetY = -mLocation[1];
|
||||
event.offsetLocation(offsetX, offsetY);
|
||||
mSeekBar.onTouchEvent(event);
|
||||
event.offsetLocation(-offsetX, -offsetY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +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.glgallery;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
class DownUpDetector {
|
||||
private final DownUpListener mListener;
|
||||
private boolean mStillDown;
|
||||
|
||||
public DownUpDetector(DownUpListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
private void setState(boolean down, MotionEvent e) {
|
||||
if (down == mStillDown) return;
|
||||
mStillDown = down;
|
||||
if (down) {
|
||||
mListener.onDown(e);
|
||||
} else {
|
||||
mListener.onUp(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchEvent(MotionEvent ev) {
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
setState(true, ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
mListener.onPointerDown(ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
mListener.onPointerUp(ev);
|
||||
setState(false, ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
setState(false, ev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDown() {
|
||||
return mStillDown;
|
||||
}
|
||||
|
||||
public interface DownUpListener {
|
||||
void onDown(MotionEvent e);
|
||||
|
||||
void onUp(MotionEvent e);
|
||||
|
||||
void onPointerDown(MotionEvent e);
|
||||
|
||||
void onPointerUp(MotionEvent e);
|
||||
}
|
||||
}
|
||||
@@ -1,137 +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.glgallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.SensorManager;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.glview.anim.Animation;
|
||||
|
||||
abstract class Fling extends Animation {
|
||||
|
||||
private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
|
||||
private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
|
||||
private static final float START_TENSION = 0.5f;
|
||||
private static final float END_TENSION = 1.0f;
|
||||
private static final float P1 = START_TENSION * INFLEXION;
|
||||
private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
|
||||
private static final float FLING_FRICTION = ViewConfiguration.getScrollFriction();
|
||||
|
||||
private static final int NB_SAMPLES = 100;
|
||||
private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
|
||||
private static final Interpolator FLING_INTERPOLATOR = new Interpolator() {
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
final int index = (int) (NB_SAMPLES * input);
|
||||
float distanceCoef = 1.f;
|
||||
float velocityCoef;
|
||||
if (index < NB_SAMPLES) {
|
||||
final float t_inf = (float) index / NB_SAMPLES;
|
||||
final float t_sup = (float) (index + 1) / NB_SAMPLES;
|
||||
final float d_inf = SPLINE_POSITION[index];
|
||||
final float d_sup = SPLINE_POSITION[index + 1];
|
||||
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
|
||||
distanceCoef = d_inf + (input - t_inf) * velocityCoef;
|
||||
}
|
||||
return distanceCoef;
|
||||
}
|
||||
};
|
||||
private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
|
||||
|
||||
static {
|
||||
float x_min = 0.0f;
|
||||
float y_min = 0.0f;
|
||||
for (int i = 0; i < NB_SAMPLES; i++) {
|
||||
final float alpha = (float) i / NB_SAMPLES;
|
||||
|
||||
float x_max = 1.0f;
|
||||
float x, tx, coef;
|
||||
while (true) {
|
||||
x = x_min + (x_max - x_min) / 2.0f;
|
||||
coef = 3.0f * x * (1.0f - x);
|
||||
tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
|
||||
if (Math.abs(tx - alpha) < 1E-5) break;
|
||||
if (tx > alpha) x_max = x;
|
||||
else x_min = x;
|
||||
}
|
||||
SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
|
||||
|
||||
float y_max = 1.0f;
|
||||
float y, dy;
|
||||
while (true) {
|
||||
y = y_min + (y_max - y_min) / 2.0f;
|
||||
coef = 3.0f * y * (1.0f - y);
|
||||
dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
|
||||
if (Math.abs(dy - alpha) < 1E-5) break;
|
||||
if (dy > alpha) y_max = y;
|
||||
else y_min = y;
|
||||
}
|
||||
SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
|
||||
}
|
||||
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
|
||||
}
|
||||
|
||||
private final float mPhysicalCoeff;
|
||||
|
||||
public Fling(Context context) {
|
||||
final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
|
||||
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
|
||||
* 39.37f // inch/meter
|
||||
* ppi
|
||||
* 0.84f; // look and feel tuning
|
||||
setInterpolator(FLING_INTERPOLATOR);
|
||||
}
|
||||
|
||||
private double getSplineDeceleration(int velocity) {
|
||||
return Math.log(INFLEXION * Math.abs(velocity) / (FLING_FRICTION * mPhysicalCoeff));
|
||||
}
|
||||
|
||||
/* Returns the duration, expressed in milliseconds */
|
||||
protected int getSplineFlingDuration(int velocity) {
|
||||
final double l = getSplineDeceleration(velocity);
|
||||
final double decelMinusOne = DECELERATION_RATE - 1.0;
|
||||
return (int) (1000.0 * Math.exp(l / decelMinusOne));
|
||||
}
|
||||
|
||||
protected double getSplineFlingDistance(int velocity) {
|
||||
final double l = getSplineDeceleration(velocity);
|
||||
final double decelMinusOne = DECELERATION_RATE - 1.0;
|
||||
return FLING_FRICTION * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies mDuration to the duration it takes to get from start to newFinal using the
|
||||
* spline interpolation. The previous duration was needed to get to oldFinal.
|
||||
**/
|
||||
protected int adjustDuration(int start, int oldFinal, int newFinal, int duration) {
|
||||
final int oldDistance = oldFinal - start;
|
||||
final int newDistance = newFinal - start;
|
||||
final float x = Math.abs((float) newDistance / oldDistance);
|
||||
final int index = (int) (NB_SAMPLES * x);
|
||||
if (index < NB_SAMPLES) {
|
||||
final float x_inf = (float) index / NB_SAMPLES;
|
||||
final float x_sup = (float) (index + 1) / NB_SAMPLES;
|
||||
final float t_inf = SPLINE_TIME[index];
|
||||
final float t_sup = SPLINE_TIME[index + 1];
|
||||
final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
|
||||
duration *= timeCoef;
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
}
|
||||
@@ -1,197 +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.glgallery;
|
||||
|
||||
import com.hippo.glview.glrenderer.BasicTexture;
|
||||
import com.hippo.glview.glrenderer.Texture;
|
||||
import com.hippo.glview.image.GLImageMovableTextView;
|
||||
import com.hippo.glview.image.ImageMovableTextTexture;
|
||||
import com.hippo.glview.image.ImageTexture;
|
||||
import com.hippo.glview.view.Gravity;
|
||||
import com.hippo.glview.widget.GLFrameLayout;
|
||||
import com.hippo.glview.widget.GLLinearLayout;
|
||||
import com.hippo.glview.widget.GLProgressView;
|
||||
import com.hippo.glview.widget.GLTextureView;
|
||||
|
||||
public class GalleryPageView extends GLFrameLayout {
|
||||
|
||||
public static final int INVALID_INDEX = -1;
|
||||
|
||||
public static final float PROGRESS_GONE = -1.0f;
|
||||
public static final float PROGRESS_INDETERMINATE = -2.0f;
|
||||
|
||||
private final ImageView mImage;
|
||||
private final GLLinearLayout mInfo;
|
||||
private final GLImageMovableTextView mPage;
|
||||
private final GLTextureView mError;
|
||||
private final GLProgressView mProgress;
|
||||
|
||||
private final int mMinHeight;
|
||||
|
||||
private int mIndex = INVALID_INDEX;
|
||||
|
||||
public GalleryPageView(ImageMovableTextTexture pageTextTexture,
|
||||
int progressColor, int progressBgColor, int progressSize,
|
||||
int minHeight, int infoInterval) {
|
||||
// Add image
|
||||
mImage = new ImageView();
|
||||
GravityLayoutParams glp = new GravityLayoutParams(LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT);
|
||||
addComponent(mImage, glp);
|
||||
|
||||
// Add other panel
|
||||
mInfo = new GLLinearLayout();
|
||||
mInfo.setOrientation(GLLinearLayout.VERTICAL);
|
||||
mInfo.setInterval(infoInterval);
|
||||
glp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
glp.gravity = Gravity.CENTER;
|
||||
addComponent(mInfo, glp);
|
||||
|
||||
// Add page
|
||||
mPage = new GLImageMovableTextView();
|
||||
mPage.setTextTexture(pageTextTexture);
|
||||
GLLinearLayout.LayoutParams lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mInfo.addComponent(mPage, lp);
|
||||
|
||||
// Add error
|
||||
mError = new GLTextureView();
|
||||
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mInfo.addComponent(mError, lp);
|
||||
|
||||
// Add progress
|
||||
mProgress = new GLProgressView();
|
||||
mProgress.setBgColor(progressBgColor);
|
||||
mProgress.setColor(progressColor);
|
||||
mProgress.setMinimumWidth(progressSize);
|
||||
mProgress.setMinimumHeight(progressSize);
|
||||
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mInfo.addComponent(mProgress, lp);
|
||||
|
||||
mMinHeight = minHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
// The height of the actual image may be smaller than mPageMinHeight.
|
||||
// Set min height as 0 when the image is visible.
|
||||
// For PageLayoutManager, min height is useless.
|
||||
if (mImage.getVisibility() == VISIBLE) {
|
||||
return 0;
|
||||
} else {
|
||||
return mMinHeight;
|
||||
}
|
||||
}
|
||||
|
||||
int getIndex() {
|
||||
return mIndex;
|
||||
}
|
||||
|
||||
void setIndex(int index) {
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
public void showImage() {
|
||||
mImage.setVisibility(VISIBLE);
|
||||
mInfo.setVisibility(GONE);
|
||||
}
|
||||
|
||||
public void showInfo() {
|
||||
// For image valid rect
|
||||
mImage.setVisibility(INVISIBLE);
|
||||
mInfo.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void unbindImage() {
|
||||
ImageTexture texture = mImage.getImageTexture();
|
||||
if (texture != null) {
|
||||
mImage.setImageTexture(null);
|
||||
texture.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImage(ImageTexture imageTexture) {
|
||||
unbindImage();
|
||||
if (imageTexture != null) {
|
||||
mImage.setImageTexture(imageTexture);
|
||||
}
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
mPage.setVisibility(VISIBLE);
|
||||
mPage.setText(Integer.toString(page));
|
||||
}
|
||||
|
||||
public void hidePage() {
|
||||
mPage.setVisibility(GONE);
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
if (progress == PROGRESS_GONE) {
|
||||
mProgress.setVisibility(GONE);
|
||||
} else if (progress == PROGRESS_INDETERMINATE) {
|
||||
mProgress.setVisibility(VISIBLE);
|
||||
mProgress.setIndeterminate(true);
|
||||
} else {
|
||||
mProgress.setVisibility(VISIBLE);
|
||||
mProgress.setIndeterminate(false);
|
||||
mProgress.setProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
private void unbindError() {
|
||||
Texture texture = mError.getTexture();
|
||||
if (texture != null) {
|
||||
mError.setTexture(null);
|
||||
if (texture instanceof BasicTexture) {
|
||||
((BasicTexture) texture).recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setError(String error, GalleryView galleryView) {
|
||||
unbindError();
|
||||
if (error == null) {
|
||||
mError.setVisibility(GONE);
|
||||
} else {
|
||||
mError.setVisibility(VISIBLE);
|
||||
galleryView.bindErrorView(mError, error);
|
||||
}
|
||||
}
|
||||
|
||||
ImageView getImageView() {
|
||||
return mImage;
|
||||
}
|
||||
|
||||
boolean isLoaded() {
|
||||
return mImage.getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
boolean isError() {
|
||||
return mError.getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
boolean isUnderInfo(float x, float y) {
|
||||
return mInfo.bounds().contains((int) x, (int) y);
|
||||
}
|
||||
}
|
||||
@@ -1,264 +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.glgallery;
|
||||
|
||||
import android.util.LruCache;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.image.ImageWrapper;
|
||||
import com.hippo.glview.view.GLRoot;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.yorozuya.ConcurrentPool;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.OSUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public abstract class GalleryProvider {
|
||||
|
||||
public static final int STATE_WAIT = -1;
|
||||
public static final int STATE_ERROR = -2;
|
||||
|
||||
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
|
||||
private final ImageCache mImageCache = new ImageCache();
|
||||
private volatile Listener mListener;
|
||||
private volatile GLRoot mGLRoot;
|
||||
private boolean mStarted = false;
|
||||
|
||||
@UiThread
|
||||
public void start() {
|
||||
OSUtils.checkMainLoop();
|
||||
|
||||
if (mStarted) {
|
||||
throw new IllegalStateException("Can't start it twice");
|
||||
}
|
||||
mStarted = true;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void stop() {
|
||||
OSUtils.checkMainLoop();
|
||||
mImageCache.evictAll();
|
||||
}
|
||||
|
||||
public void setGLRoot(GLRoot glRoot) {
|
||||
mGLRoot = glRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link #STATE_WAIT} for wait,
|
||||
* {@link #STATE_ERROR} for error, {@link #getError()} to get error message,
|
||||
* 0 for empty
|
||||
*/
|
||||
public abstract int size();
|
||||
|
||||
public final void request(int index) {
|
||||
ImageWrapper imageWrapper = mImageCache.get(index);
|
||||
if (imageWrapper != null) {
|
||||
notifyPageSucceed(index, imageWrapper);
|
||||
} else {
|
||||
onRequest(index);
|
||||
}
|
||||
}
|
||||
|
||||
public final void forceRequest(int index) {
|
||||
onForceRequest(index);
|
||||
}
|
||||
|
||||
public void removeCache(int index) {
|
||||
mImageCache.remove(index);
|
||||
}
|
||||
|
||||
protected abstract void onRequest(int index);
|
||||
|
||||
protected abstract void onForceRequest(int index);
|
||||
|
||||
public final void cancelRequest(int index) {
|
||||
onCancelRequest(index);
|
||||
}
|
||||
|
||||
protected abstract void onCancelRequest(int index);
|
||||
|
||||
public abstract String getError();
|
||||
|
||||
public void setListener(Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public void notifyDataChanged() {
|
||||
notify(NotifyTask.TYPE_DATA_CHANGED, -1, 0.0f, null, null);
|
||||
}
|
||||
|
||||
public void notifyDataChanged(int index) {
|
||||
notify(NotifyTask.TYPE_DATA_CHANGED, index, 0.0f, null, null);
|
||||
}
|
||||
|
||||
public void notifyPageWait(int index) {
|
||||
notify(NotifyTask.TYPE_WAIT, index, 0.0f, null, null);
|
||||
}
|
||||
|
||||
public void notifyPagePercent(int index, float percent) {
|
||||
notify(NotifyTask.TYPE_PERCENT, index, percent, null, null);
|
||||
}
|
||||
|
||||
public void notifyPageSucceed(int index, Image image) {
|
||||
ImageWrapper imageWrapper = new ImageWrapper(image);
|
||||
mImageCache.add(index, imageWrapper);
|
||||
notifyPageSucceed(index, imageWrapper);
|
||||
}
|
||||
|
||||
public void notifyPageSucceed(int index, ImageWrapper image) {
|
||||
notify(NotifyTask.TYPE_SUCCEED, index, 0.0f, image, null);
|
||||
}
|
||||
|
||||
public void notifyPageFailed(int index, String error) {
|
||||
notify(NotifyTask.TYPE_FAILED, index, 0.0f, null, error);
|
||||
}
|
||||
|
||||
private void notify(@NotifyTask.Type int type, int index, float percent, ImageWrapper image, String error) {
|
||||
Listener listener = mListener;
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLRoot glRoot = mGLRoot;
|
||||
if (glRoot == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotifyTask task = mNotifyTaskPool.pop();
|
||||
if (task == null) {
|
||||
task = new NotifyTask(listener, mNotifyTaskPool);
|
||||
}
|
||||
task.setData(type, index, percent, image, error);
|
||||
glRoot.addOnGLIdleListener(task);
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
|
||||
void onDataChanged();
|
||||
|
||||
void onPageWait(int index);
|
||||
|
||||
void onPagePercent(int index, float percent);
|
||||
|
||||
void onPageSucceed(int index, ImageWrapper image);
|
||||
|
||||
void onPageFailed(int index, String error);
|
||||
|
||||
void onDataChanged(int index);
|
||||
}
|
||||
|
||||
private static class NotifyTask implements GLRoot.OnGLIdleListener {
|
||||
|
||||
public static final int TYPE_DATA_CHANGED = 0;
|
||||
public static final int TYPE_WAIT = 1;
|
||||
public static final int TYPE_PERCENT = 2;
|
||||
public static final int TYPE_SUCCEED = 3;
|
||||
public static final int TYPE_FAILED = 4;
|
||||
private final Listener mListener;
|
||||
private final ConcurrentPool<NotifyTask> mPool;
|
||||
@Type
|
||||
private int mType;
|
||||
private int mIndex;
|
||||
private float mPercent;
|
||||
private ImageWrapper mImage;
|
||||
private String mError;
|
||||
|
||||
public NotifyTask(Listener listener, ConcurrentPool<NotifyTask> pool) {
|
||||
mListener = listener;
|
||||
mPool = pool;
|
||||
}
|
||||
|
||||
public void setData(@Type int type, int index, float percent, ImageWrapper image, String error) {
|
||||
mType = type;
|
||||
mIndex = index;
|
||||
mPercent = percent;
|
||||
mImage = image;
|
||||
mError = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
switch (mType) {
|
||||
case TYPE_DATA_CHANGED:
|
||||
if (mIndex < 0) {
|
||||
mListener.onDataChanged();
|
||||
} else {
|
||||
mListener.onDataChanged(mIndex);
|
||||
}
|
||||
break;
|
||||
case TYPE_WAIT:
|
||||
mListener.onPageWait(mIndex);
|
||||
break;
|
||||
case TYPE_PERCENT:
|
||||
mListener.onPagePercent(mIndex, mPercent);
|
||||
break;
|
||||
case TYPE_SUCCEED:
|
||||
mListener.onPageSucceed(mIndex, mImage);
|
||||
break;
|
||||
case TYPE_FAILED:
|
||||
mListener.onPageFailed(mIndex, mError);
|
||||
break;
|
||||
}
|
||||
|
||||
// Clean data
|
||||
mImage = null;
|
||||
mError = null;
|
||||
// Push back
|
||||
mPool.push(this);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@IntDef({TYPE_DATA_CHANGED, NotifyTask.TYPE_WAIT, TYPE_PERCENT, TYPE_SUCCEED, TYPE_FAILED})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Type {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImageCache extends LruCache<Integer, ImageWrapper> {
|
||||
|
||||
private static final int CACHE_SIZE = 512 * 1024 * 1024;
|
||||
|
||||
public ImageCache() {
|
||||
super(CACHE_SIZE);
|
||||
}
|
||||
|
||||
public void add(Integer key, ImageWrapper value) {
|
||||
if (!value.getAnimated())
|
||||
if (value.obtain())
|
||||
put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int sizeOf(Integer key, ImageWrapper value) {
|
||||
return value.getWidth() * value.getHeight() * 4;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void entryRemoved(boolean evicted, Integer key, ImageWrapper oldValue, ImageWrapper newValue) {
|
||||
if (oldValue != null) {
|
||||
oldValue.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,186 +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.glgallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
|
||||
import androidx.core.view.GestureDetectorCompat;
|
||||
|
||||
// This class aggregates three gesture detectors: GestureDetector,
|
||||
// ScaleGestureDetector, and DownUpDetector.
|
||||
class GestureRecognizer {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "GestureRecognizer";
|
||||
private final GestureDetectorCompat mGestureDetector;
|
||||
private final ScaleGestureDetector mScaleDetector;
|
||||
private final DownUpDetector mDownUpDetector;
|
||||
private final Listener mListener;
|
||||
|
||||
public GestureRecognizer(Context context, Listener listener) {
|
||||
mListener = listener;
|
||||
MyGestureListener gestureListener = new MyGestureListener();
|
||||
mGestureDetector = new GestureDetectorCompat(context, gestureListener,
|
||||
null /* ignoreMultitouch */);
|
||||
mGestureDetector.setOnDoubleTapListener(gestureListener);
|
||||
mScaleDetector = new ScaleGestureDetector(
|
||||
context, new MyScaleListener());
|
||||
mDownUpDetector = new DownUpDetector(new MyDownUpListener());
|
||||
}
|
||||
|
||||
public void onTouchEvent(MotionEvent event) {
|
||||
mScaleDetector.onTouchEvent(event);
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
mDownUpDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public boolean isDown() {
|
||||
return mDownUpDetector.isDown();
|
||||
}
|
||||
|
||||
public void cancelScale() {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(
|
||||
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
|
||||
mScaleDetector.onTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
boolean onSingleTapUp(float x, float y);
|
||||
|
||||
boolean onSingleTapConfirmed(float x, float y);
|
||||
|
||||
boolean onDoubleTap(float x, float y);
|
||||
|
||||
boolean onDoubleTapConfirmed(float x, float y);
|
||||
|
||||
void onLongPress(float x, float y);
|
||||
|
||||
boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y);
|
||||
|
||||
/**
|
||||
* @param velocityX Finger from top to bottom is positive
|
||||
* @param velocityY Finger from left to right is positive
|
||||
*/
|
||||
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
|
||||
|
||||
boolean onScaleBegin(float focusX, float focusY);
|
||||
|
||||
boolean onScale(float focusX, float focusY, float scale);
|
||||
|
||||
void onScaleEnd();
|
||||
|
||||
void onDown(float x, float y);
|
||||
|
||||
void onUp();
|
||||
|
||||
void onPointerDown(float x, float y);
|
||||
|
||||
void onPointerUp();
|
||||
}
|
||||
|
||||
private class MyGestureListener
|
||||
extends GestureDetector.SimpleOnGestureListener {
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
return mListener.onSingleTapUp(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
return mListener.onSingleTapConfirmed(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||
if (e.getAction() == MotionEvent.ACTION_UP) {
|
||||
return mListener.onDoubleTapConfirmed(e.getX(), e.getY());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
return mListener.onDoubleTap(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
mListener.onLongPress(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(
|
||||
MotionEvent e1, MotionEvent e2, float dx, float dy) {
|
||||
return mListener.onScroll(
|
||||
dx, dy, e2.getX() - e1.getX(), e2.getY() - e1.getY(), e2.getX(), e2.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||||
float velocityY) {
|
||||
return mListener.onFling(e1, e2, velocityX, velocityY);
|
||||
}
|
||||
}
|
||||
|
||||
private class MyScaleListener
|
||||
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
return mListener.onScaleBegin(
|
||||
detector.getFocusX(), detector.getFocusY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
return mListener.onScale(detector.getFocusX(),
|
||||
detector.getFocusY(), detector.getScaleFactor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
mListener.onScaleEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private class MyDownUpListener implements DownUpDetector.DownUpListener {
|
||||
@Override
|
||||
public void onDown(MotionEvent e) {
|
||||
mListener.onDown(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUp(MotionEvent e) {
|
||||
mListener.onUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerDown(MotionEvent e) {
|
||||
mListener.onPointerDown(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerUp(MotionEvent e) {
|
||||
mListener.onPointerUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,597 +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.glgallery;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.hippo.glview.anim.AlphaAnimation;
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.glview.glrenderer.Texture;
|
||||
import com.hippo.glview.image.ImageTexture;
|
||||
import com.hippo.glview.view.GLView;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
class ImageView extends GLView implements ImageTexture.Callback {
|
||||
|
||||
public static final int SCALE_ORIGIN = 0;
|
||||
public static final int SCALE_FIT_WIDTH = 1;
|
||||
public static final int SCALE_FIT_HEIGHT = 2;
|
||||
public static final int SCALE_FIT = 3;
|
||||
public static final int SCALE_FIXED = 4;
|
||||
public static final int START_POSITION_TOP_LEFT = 0;
|
||||
public static final int START_POSITION_TOP_RIGHT = 1;
|
||||
public static final int START_POSITION_BOTTOM_LEFT = 2;
|
||||
public static final int START_POSITION_BOTTOM_RIGHT = 3;
|
||||
public static final int START_POSITION_CENTER = 4;
|
||||
// TODO adjust scale max and min according to image size and screen size
|
||||
private static final float SCALE_MIN = 1 / 10.0f;
|
||||
private static final float SCALE_MAX = 10.0f;
|
||||
private static final long ALPHA_ANIMATION_DURING = 300L;
|
||||
private final RectF mDst = new RectF();
|
||||
private final RectF mSrcActual = new RectF();
|
||||
private final RectF mDstActual = new RectF();
|
||||
private final Rect mValidRect = new Rect();
|
||||
private final AlphaAnimation mAlphaAnimation;
|
||||
private ImageTexture mImageTexture;
|
||||
private int mTextureWidth;
|
||||
private int mTextureHeight;
|
||||
private int mScaleMode = SCALE_FIT;
|
||||
private int mStartPosition = START_POSITION_TOP_RIGHT;
|
||||
private float mScaleValue = 1.0f;
|
||||
private float mScale = 1.0f;
|
||||
private boolean mScaleOffsetDirty = true;
|
||||
private boolean mPositionInRootDirty = true;
|
||||
|
||||
public ImageView() {
|
||||
mAlphaAnimation = new AlphaAnimation(0.0f, 1.0f);
|
||||
mAlphaAnimation.setDuration(ALPHA_ANIMATION_DURING);
|
||||
mAlphaAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
|
||||
}
|
||||
|
||||
public static int sanitizeScaleMode(int scaleMode) {
|
||||
if (scaleMode != SCALE_ORIGIN &&
|
||||
scaleMode != SCALE_FIT_WIDTH &&
|
||||
scaleMode != SCALE_FIT_HEIGHT &&
|
||||
scaleMode != SCALE_FIT &&
|
||||
scaleMode != SCALE_FIXED) {
|
||||
return SCALE_FIT;
|
||||
} else {
|
||||
return scaleMode;
|
||||
}
|
||||
}
|
||||
|
||||
public static int sanitizeStartPosition(int startPosition) {
|
||||
if (startPosition != START_POSITION_TOP_LEFT &&
|
||||
startPosition != START_POSITION_TOP_RIGHT &&
|
||||
startPosition != START_POSITION_BOTTOM_LEFT &&
|
||||
startPosition != START_POSITION_BOTTOM_RIGHT &&
|
||||
startPosition != START_POSITION_CENTER) {
|
||||
return START_POSITION_TOP_RIGHT;
|
||||
} else {
|
||||
return startPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumWidth() {
|
||||
return Math.max(super.getSuggestedMinimumWidth(),
|
||||
mImageTexture == null ? 0 : mTextureWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
return Math.max(super.getSuggestedMinimumHeight(),
|
||||
mImageTexture == null ? 0 : mTextureHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||
if (mImageTexture == null) {
|
||||
super.onMeasure(widthSpec, heightSpec);
|
||||
} else {
|
||||
float ratio = (float) mTextureWidth / mTextureHeight;
|
||||
int widthSize = MeasureSpec.getSize(widthSpec);
|
||||
int heightSize = MeasureSpec.getSize(heightSpec);
|
||||
int widthMode = MeasureSpec.getMode(widthSpec);
|
||||
int heightMode = MeasureSpec.getMode(heightSpec);
|
||||
int measureWidth = -1;
|
||||
int measureHeight = -1;
|
||||
|
||||
if (widthMode == MeasureSpec.EXACTLY) {
|
||||
measureWidth = widthSize;
|
||||
if (heightMode == MeasureSpec.EXACTLY) {
|
||||
measureHeight = heightSize;
|
||||
} else {
|
||||
measureHeight = (int) (widthSize / ratio);
|
||||
if (heightMode == MeasureSpec.AT_MOST) {
|
||||
measureHeight = Math.min(measureHeight, heightSize);
|
||||
}
|
||||
}
|
||||
} else if (heightMode == MeasureSpec.EXACTLY) {
|
||||
measureHeight = heightSize;
|
||||
measureWidth = (int) (heightSize * ratio);
|
||||
if (widthMode == MeasureSpec.AT_MOST) {
|
||||
measureWidth = Math.min(measureWidth, widthSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (measureWidth == -1 || measureHeight == -1) {
|
||||
super.onMeasure(widthSpec, heightSpec);
|
||||
} else {
|
||||
setMeasuredSize(measureWidth, measureHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int newW, int newH, int oldW, int oldH) {
|
||||
mScaleOffsetDirty = true;
|
||||
mPositionInRootDirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPositionInRootChanged(int x, int y, int oldX, int oldY) {
|
||||
mPositionInRootDirty = true;
|
||||
|
||||
if (mImageTexture != null) {
|
||||
getValidRect(mValidRect);
|
||||
if (!mValidRect.isEmpty()) {
|
||||
mImageTexture.start();
|
||||
} else {
|
||||
mImageTexture.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void getScaleDefault(float[] scaleDefault) {
|
||||
if (mImageTexture == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scaleDefault[0] = 1.0f;
|
||||
scaleDefault[1] = (float) getWidth() / mTextureWidth;
|
||||
scaleDefault[2] = (float) getHeight() / mTextureHeight;
|
||||
scaleDefault[3] = Math.max(scaleDefault[1], scaleDefault[2]) * 2;
|
||||
|
||||
scaleDefault[0] = MathUtils.clamp(scaleDefault[0], SCALE_MIN, SCALE_MAX);
|
||||
scaleDefault[1] = MathUtils.clamp(scaleDefault[1], SCALE_MIN, SCALE_MAX);
|
||||
scaleDefault[2] = MathUtils.clamp(scaleDefault[2], SCALE_MIN, SCALE_MAX);
|
||||
scaleDefault[3] = MathUtils.clamp(scaleDefault[3], SCALE_MIN, SCALE_MAX);
|
||||
|
||||
Arrays.sort(scaleDefault);
|
||||
}
|
||||
|
||||
public ImageTexture getImageTexture() {
|
||||
return mImageTexture;
|
||||
}
|
||||
|
||||
public void setImageTexture(ImageTexture imageTexture) {
|
||||
// Remove callback
|
||||
if (mImageTexture != null) {
|
||||
mImageTexture.setCallback(null);
|
||||
mImageTexture.stop();
|
||||
}
|
||||
|
||||
int oldTextureWidth = mTextureWidth;
|
||||
int oldTextureHeight = mTextureHeight;
|
||||
|
||||
mImageTexture = imageTexture;
|
||||
|
||||
if (imageTexture != null) {
|
||||
imageTexture.setCallback(this);
|
||||
mTextureWidth = imageTexture.getWidth();
|
||||
mTextureHeight = imageTexture.getHeight();
|
||||
// Avoid zero and negative
|
||||
if (mTextureWidth <= 0) {
|
||||
mTextureWidth = 1;
|
||||
}
|
||||
if (mTextureHeight <= 0) {
|
||||
mTextureHeight = 1;
|
||||
}
|
||||
|
||||
// Start alpha animation, do not show animation for image has no valid rect
|
||||
getValidRect(mValidRect);
|
||||
if (!mValidRect.isEmpty()) {
|
||||
startAnimation(mAlphaAnimation, true);
|
||||
mImageTexture.start();
|
||||
}
|
||||
} else {
|
||||
mTextureWidth = 1;
|
||||
mTextureHeight = 1;
|
||||
}
|
||||
|
||||
mScaleOffsetDirty = true;
|
||||
mPositionInRootDirty = true;
|
||||
|
||||
if (oldTextureWidth != mTextureWidth || oldTextureHeight != mTextureHeight) {
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return mImageTexture != null;
|
||||
}
|
||||
|
||||
public boolean canFlingVertically() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return mDst.top < 0.0f || mDst.bottom > getHeight();
|
||||
}
|
||||
|
||||
public boolean canFlingHorizontally() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return mDst.left < 0.0f || mDst.right > getWidth();
|
||||
}
|
||||
|
||||
public boolean canFling() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return mDst.left < 0.0f || mDst.top < 0.0f || mDst.right > getWidth() || mDst.bottom > getHeight();
|
||||
}
|
||||
|
||||
public int getMaxDx() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.max(0, -(int) mDst.left);
|
||||
}
|
||||
|
||||
public int getMinDx() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.min(0, getWidth() - (int) mDst.right);
|
||||
}
|
||||
|
||||
public int getMaxDy() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.max(0, -(int) mDst.top);
|
||||
}
|
||||
|
||||
public int getMinDy() {
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
if (mScaleOffsetDirty) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return Math.min(0, getHeight() - (int) mDst.bottom);
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
return mScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* If target is shorter then screen, make it in screen center. If target is
|
||||
* longer then parent, make sure target fill parent over
|
||||
*/
|
||||
private void adjustPosition() {
|
||||
RectF dst = mDst;
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
float targetWidth = dst.width();
|
||||
float targetHeight = dst.height();
|
||||
|
||||
if (targetWidth > screenWidth) {
|
||||
float fixXOffset = dst.left;
|
||||
if (fixXOffset > 0) {
|
||||
dst.left -= fixXOffset;
|
||||
dst.right -= fixXOffset;
|
||||
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
|
||||
dst.left += fixXOffset;
|
||||
dst.right += fixXOffset;
|
||||
}
|
||||
} else {
|
||||
float left = (screenWidth - targetWidth) / 2;
|
||||
dst.offsetTo(left, dst.top);
|
||||
}
|
||||
if (targetHeight > screenHeight) {
|
||||
float fixYOffset = dst.top;
|
||||
if (fixYOffset > 0) {
|
||||
dst.top -= fixYOffset;
|
||||
dst.bottom -= fixYOffset;
|
||||
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
|
||||
dst.top += fixYOffset;
|
||||
dst.bottom += fixYOffset;
|
||||
}
|
||||
} else {
|
||||
float top = (screenHeight - targetHeight) / 2;
|
||||
dst.offsetTo(dst.left, top);
|
||||
}
|
||||
}
|
||||
|
||||
public void setScaleOffset(int scaleMode, int startPosition, float scaleValue) {
|
||||
mScaleMode = scaleMode;
|
||||
mStartPosition = startPosition;
|
||||
mScaleValue = scaleValue;
|
||||
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
|
||||
if (mImageTexture == null || screenWidth == 0 || screenHeight == 0) {
|
||||
mScaleOffsetDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int textureWidth = mTextureWidth;
|
||||
int textureHeight = mTextureHeight;
|
||||
|
||||
// Set scale
|
||||
float targetWidth;
|
||||
float targetHeight;
|
||||
switch (scaleMode) {
|
||||
case SCALE_ORIGIN:
|
||||
mScale = 1.0f;
|
||||
targetWidth = textureWidth;
|
||||
targetHeight = textureHeight;
|
||||
break;
|
||||
case SCALE_FIT_WIDTH:
|
||||
mScale = (float) screenWidth / textureWidth;
|
||||
targetWidth = screenWidth;
|
||||
targetHeight = textureHeight * mScale;
|
||||
break;
|
||||
case SCALE_FIT_HEIGHT:
|
||||
mScale = (float) screenHeight / textureHeight;
|
||||
targetWidth = textureWidth * mScale;
|
||||
targetHeight = screenHeight;
|
||||
break;
|
||||
case SCALE_FIT:
|
||||
float scaleX = (float) screenWidth / textureWidth;
|
||||
float scaleY = (float) screenHeight / textureHeight;
|
||||
if (scaleX < scaleY) {
|
||||
mScale = scaleX;
|
||||
targetWidth = screenWidth;
|
||||
targetHeight = textureHeight * scaleX;
|
||||
} else {
|
||||
mScale = scaleY;
|
||||
targetWidth = textureWidth * scaleY;
|
||||
targetHeight = screenHeight;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SCALE_FIXED:
|
||||
default:
|
||||
mScale = scaleValue;
|
||||
targetWidth = textureWidth * scaleValue;
|
||||
targetHeight = textureHeight * scaleValue;
|
||||
break;
|
||||
}
|
||||
|
||||
// adjust scale, not too big, not too small
|
||||
if (mScale < SCALE_MIN) {
|
||||
mScale = SCALE_MIN;
|
||||
targetWidth = textureWidth * SCALE_MIN;
|
||||
targetHeight = textureHeight * SCALE_MIN;
|
||||
} else if (mScale > SCALE_MAX) {
|
||||
mScale = SCALE_MAX;
|
||||
targetWidth = textureWidth * SCALE_MAX;
|
||||
targetHeight = textureHeight * SCALE_MAX;
|
||||
}
|
||||
|
||||
// Set mDst.left and mDst.right
|
||||
RectF dst = mDst;
|
||||
switch (startPosition) {
|
||||
case START_POSITION_TOP_LEFT:
|
||||
dst.left = 0;
|
||||
dst.top = 0;
|
||||
break;
|
||||
case START_POSITION_TOP_RIGHT:
|
||||
dst.left = screenWidth - targetWidth;
|
||||
dst.top = 0;
|
||||
break;
|
||||
case START_POSITION_BOTTOM_LEFT:
|
||||
dst.left = 0;
|
||||
dst.top = screenHeight - targetHeight;
|
||||
break;
|
||||
case START_POSITION_BOTTOM_RIGHT:
|
||||
dst.left = screenWidth - targetWidth;
|
||||
dst.top = screenHeight - targetHeight;
|
||||
break;
|
||||
case START_POSITION_CENTER:
|
||||
default:
|
||||
dst.left = (screenWidth - targetWidth) / 2;
|
||||
dst.top = (screenHeight - targetHeight) / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set mDst.right and mDst.bottom
|
||||
dst.right = dst.left + targetWidth;
|
||||
dst.bottom = dst.top + targetHeight;
|
||||
|
||||
// adjust position
|
||||
adjustPosition();
|
||||
|
||||
mScaleOffsetDirty = false;
|
||||
mPositionInRootDirty = true;
|
||||
}
|
||||
|
||||
public void scroll(int dx, int dy, int[] remain) {
|
||||
// Only work after layout
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mScaleOffsetDirty) {
|
||||
remain[0] = dx;
|
||||
remain[1] = dy;
|
||||
return;
|
||||
}
|
||||
|
||||
RectF dst = mDst;
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
float targetWidth = dst.width();
|
||||
float targetHeight = dst.height();
|
||||
|
||||
if (targetWidth > screenWidth) {
|
||||
dst.left -= dx;
|
||||
dst.right -= dx;
|
||||
|
||||
float fixXOffset = dst.left;
|
||||
if (fixXOffset > 0) {
|
||||
dst.left -= fixXOffset;
|
||||
dst.right -= fixXOffset;
|
||||
remain[0] = -(int) fixXOffset;
|
||||
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
|
||||
dst.left += fixXOffset;
|
||||
dst.right += fixXOffset;
|
||||
remain[0] = (int) fixXOffset;
|
||||
} else {
|
||||
remain[0] = 0;
|
||||
}
|
||||
} else {
|
||||
remain[0] = dx;
|
||||
}
|
||||
if (targetHeight > screenHeight) {
|
||||
dst.top -= dy;
|
||||
dst.bottom -= dy;
|
||||
|
||||
float fixYOffset = dst.top;
|
||||
if (fixYOffset > 0) {
|
||||
dst.top -= fixYOffset;
|
||||
dst.bottom -= fixYOffset;
|
||||
remain[1] = -(int) fixYOffset;
|
||||
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
|
||||
dst.top += fixYOffset;
|
||||
dst.bottom += fixYOffset;
|
||||
remain[1] = (int) fixYOffset;
|
||||
} else {
|
||||
remain[1] = 0;
|
||||
}
|
||||
} else {
|
||||
remain[1] = dy;
|
||||
}
|
||||
|
||||
if (dx != remain[0] || dy != remain[1]) {
|
||||
mPositionInRootDirty = true;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void scale(float focusX, float focusY, float scale) {
|
||||
// Only work after layout
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mScaleOffsetDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((mScale == SCALE_MAX && scale >= 1.0f) || (mScale == SCALE_MIN && scale < 1.0f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
float newScale = mScale * scale;
|
||||
newScale = MathUtils.clamp(newScale, SCALE_MIN, SCALE_MAX);
|
||||
mScale = newScale;
|
||||
RectF dst = mDst;
|
||||
float left = (focusX - ((focusX - dst.left) * scale));
|
||||
float top = (focusY - ((focusY - dst.top) * scale));
|
||||
dst.set(left, top,
|
||||
(left + (mImageTexture.getWidth() * newScale)),
|
||||
(top + (mImageTexture.getHeight() * newScale)));
|
||||
|
||||
// adjust position
|
||||
adjustPosition();
|
||||
|
||||
mPositionInRootDirty = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void applyPositionInRoot() {
|
||||
int width = mImageTexture.getWidth();
|
||||
int height = mImageTexture.getHeight();
|
||||
RectF dst = mDst;
|
||||
RectF dstActual = mDstActual;
|
||||
RectF srcActual = mSrcActual;
|
||||
|
||||
dstActual.set(dst);
|
||||
getValidRect(mValidRect);
|
||||
if (dstActual.intersect(mValidRect.left, mValidRect.top, mValidRect.right, mValidRect.bottom)) {
|
||||
srcActual.left = MathUtils.lerp(0, width,
|
||||
MathUtils.delerp(dst.left, dst.right, dstActual.left));
|
||||
srcActual.right = MathUtils.lerp(0, width,
|
||||
MathUtils.delerp(dst.left, dst.right, dstActual.right));
|
||||
srcActual.top = MathUtils.lerp(0, height,
|
||||
MathUtils.delerp(dst.top, dst.bottom, dstActual.top));
|
||||
srcActual.bottom = MathUtils.lerp(0, height,
|
||||
MathUtils.delerp(dst.top, dst.bottom, dstActual.bottom));
|
||||
} else {
|
||||
// Can't be seen, set src and dst empty
|
||||
srcActual.setEmpty();
|
||||
dstActual.setEmpty();
|
||||
}
|
||||
|
||||
mPositionInRootDirty = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRender(GLCanvas canvas) {
|
||||
Texture texture = mImageTexture;
|
||||
if (texture == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
|
||||
if (mPositionInRootDirty) {
|
||||
applyPositionInRoot();
|
||||
}
|
||||
|
||||
if (!mSrcActual.isEmpty()) {
|
||||
texture.draw(canvas, mSrcActual, mDstActual);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateImageTexture(ImageTexture who) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
@@ -1,963 +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.glgallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.Log;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.glview.anim.Animation;
|
||||
import com.hippo.glview.view.GLView;
|
||||
import com.hippo.glview.widget.GLProgressView;
|
||||
import com.hippo.glview.widget.GLTextureView;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
class PagerLayoutManager extends GalleryView.LayoutManager {
|
||||
|
||||
public static final int MODE_LEFT_TO_RIGHT = 0;
|
||||
public static final int MODE_RIGHT_TO_LEFT = 1;
|
||||
private static final String TAG = PagerLayoutManager.class.getSimpleName();
|
||||
private static final Interpolator SMOOTH_SCROLLER_INTERPOLATOR = new Interpolator() {
|
||||
@Override
|
||||
public float getInterpolation(float t) {
|
||||
t -= 1.0f;
|
||||
return t * t * t * t * t + 1.0f;
|
||||
}
|
||||
};
|
||||
private final int[] mScrollRemain = new int[2];
|
||||
private final float[] mScaleDefault = new float[4];
|
||||
private final SmoothScroller mSmoothScroller;
|
||||
private final PageFling mPageFling;
|
||||
private final SmoothScaler mSmoothScaler;
|
||||
private final Rect mTempRect = new Rect();
|
||||
private GalleryView.Adapter mAdapter;
|
||||
private GLProgressView mProgress;
|
||||
private String mErrorStr;
|
||||
private GLTextureView mErrorView;
|
||||
private GalleryPageView mPrevious;
|
||||
private GalleryPageView mCurrent;
|
||||
private GalleryPageView mNext;
|
||||
@Mode
|
||||
private int mMode = MODE_RIGHT_TO_LEFT;
|
||||
private int mScaleMode;
|
||||
private int mStartPosition;
|
||||
private float mScaleValue;
|
||||
private int mOffset;
|
||||
private int mDeltaX;
|
||||
private int mDeltaY;
|
||||
private boolean mCanScrollBetweenPages = false;
|
||||
private boolean mStopAnimationFinger;
|
||||
private int mInterval;
|
||||
// Current index
|
||||
private int mIndex;
|
||||
|
||||
public PagerLayoutManager(Context context, @NonNull GalleryView galleryView,
|
||||
int scaleMode, int startPoint, float scaleValue, int interval) {
|
||||
super(galleryView);
|
||||
|
||||
mScaleMode = scaleMode;
|
||||
mStartPosition = startPoint;
|
||||
mScaleValue = scaleValue;
|
||||
|
||||
mInterval = interval;
|
||||
mSmoothScroller = new SmoothScroller();
|
||||
mPageFling = new PageFling(context);
|
||||
mSmoothScaler = new SmoothScaler();
|
||||
}
|
||||
|
||||
public void setInterval(int interval) {
|
||||
if (mInterval == interval) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAdapter != null) {
|
||||
int index = getInternalCurrentIndex();
|
||||
GalleryView.Adapter adapter = onDetach();
|
||||
mInterval = interval;
|
||||
onAttach(adapter);
|
||||
setCurrentIndex(index);
|
||||
mGalleryView.requestFill();
|
||||
} else {
|
||||
mInterval = interval;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetParameters() {
|
||||
mOffset = 0;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
mCanScrollBetweenPages = false;
|
||||
mStopAnimationFinger = false;
|
||||
}
|
||||
|
||||
private boolean cancelAllAnimations() {
|
||||
boolean running = mSmoothScroller.isRunning() ||
|
||||
mPageFling.isRunning() ||
|
||||
mSmoothScaler.isRunning();
|
||||
mSmoothScroller.cancel();
|
||||
mPageFling.cancel();
|
||||
mSmoothScaler.cancel();
|
||||
return running;
|
||||
}
|
||||
|
||||
public void setMode(@Mode int mode) {
|
||||
if (mMode == mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMode = mode;
|
||||
if (mAdapter != null) {
|
||||
// It is attached, refill
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Remove all view
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
// Request fill
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
}
|
||||
|
||||
public void setScaleMode(int scaleMode) {
|
||||
if (mScaleMode == scaleMode) {
|
||||
return;
|
||||
}
|
||||
mScaleMode = scaleMode;
|
||||
|
||||
if (mCurrent != null) {
|
||||
mCurrent.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mPrevious != null) {
|
||||
mPrevious.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mNext != null) {
|
||||
mNext.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void setStartPosition(int startPosition) {
|
||||
if (mStartPosition == startPosition) {
|
||||
return;
|
||||
}
|
||||
mStartPosition = startPosition;
|
||||
|
||||
if (mCurrent != null) {
|
||||
mCurrent.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mPrevious != null) {
|
||||
mPrevious.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
if (mNext != null) {
|
||||
mNext.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(GalleryView.Adapter adapter) {
|
||||
AssertUtils.assertNull("The PagerLayoutManager is attached", mAdapter);
|
||||
AssertUtils.assertNotNull("The adapter is null", adapter);
|
||||
mAdapter = adapter;
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
}
|
||||
|
||||
private void removeProgress() {
|
||||
if (mProgress != null) {
|
||||
mGalleryView.removeComponent(mProgress);
|
||||
mGalleryView.releaseProgress(mProgress);
|
||||
mProgress = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeErrorView() {
|
||||
if (mErrorView != null) {
|
||||
mGalleryView.removeComponent(mErrorView);
|
||||
mGalleryView.releaseErrorView(mErrorView);
|
||||
mErrorView = null;
|
||||
mErrorStr = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removePage(@NonNull GalleryPageView page) {
|
||||
mGalleryView.removeComponent(page);
|
||||
mAdapter.unbind(page);
|
||||
mGalleryView.releasePage(page);
|
||||
}
|
||||
|
||||
private void removeAllPages() {
|
||||
// Remove gallery view
|
||||
if (mPrevious != null) {
|
||||
removePage(mPrevious);
|
||||
mPrevious = null;
|
||||
}
|
||||
if (mCurrent != null) {
|
||||
removePage(mCurrent);
|
||||
mCurrent = null;
|
||||
}
|
||||
if (mNext != null) {
|
||||
removePage(mNext);
|
||||
mNext = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryView.Adapter onDetach() {
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
|
||||
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
|
||||
// Remove all view
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
|
||||
// Clear iterator
|
||||
GalleryView.Adapter adapter = mAdapter;
|
||||
mAdapter = null;
|
||||
|
||||
return adapter;
|
||||
}
|
||||
|
||||
private GalleryPageView getLeftPage() {
|
||||
switch (mMode) {
|
||||
case MODE_LEFT_TO_RIGHT:
|
||||
return mPrevious;
|
||||
default:
|
||||
case MODE_RIGHT_TO_LEFT:
|
||||
return mNext;
|
||||
}
|
||||
}
|
||||
|
||||
private GalleryPageView getRightPage() {
|
||||
switch (mMode) {
|
||||
case MODE_LEFT_TO_RIGHT:
|
||||
return mNext;
|
||||
default:
|
||||
case MODE_RIGHT_TO_LEFT:
|
||||
return mPrevious;
|
||||
}
|
||||
}
|
||||
|
||||
private GalleryPageView obtainPage() {
|
||||
GalleryPageView page = mGalleryView.obtainPage();
|
||||
page.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
return page;
|
||||
}
|
||||
|
||||
private void layoutPage(GalleryPageView page, int widthSpec, int heightSpec,
|
||||
int left, int top, int right, int bottom) {
|
||||
Rect rect = mTempRect;
|
||||
page.getValidRect(rect);
|
||||
boolean oldValid = !rect.isEmpty();
|
||||
page.measure(widthSpec, heightSpec);
|
||||
page.layout(left, top, right, bottom);
|
||||
page.getValidRect(rect);
|
||||
boolean newValid = !rect.isEmpty();
|
||||
if (!oldValid && newValid) {
|
||||
page.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFill() {
|
||||
GalleryView.Adapter adapter = mAdapter;
|
||||
GalleryView galleryView = mGalleryView;
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", adapter);
|
||||
|
||||
int width = galleryView.getWidth();
|
||||
int height = galleryView.getHeight();
|
||||
int size = adapter.size();
|
||||
String errorStr = adapter.getError();
|
||||
|
||||
if (size == GalleryProvider.STATE_WAIT) { // Wait here, show progress bar
|
||||
// Remove error view and all pages
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
|
||||
// Ensure progress
|
||||
if (mProgress == null) {
|
||||
mProgress = galleryView.obtainProgress();
|
||||
galleryView.addComponent(mProgress);
|
||||
}
|
||||
|
||||
// Place progress center
|
||||
placeCenter(mProgress);
|
||||
} else if (size <= GalleryProvider.STATE_ERROR || size == 0) { // Get error or empty, show error text
|
||||
// Ensure error is not null
|
||||
if (0 == size) {
|
||||
errorStr = galleryView.getEmptyStr();
|
||||
} else if (null == errorStr) {
|
||||
errorStr = galleryView.getDefaultErrorStr();
|
||||
}
|
||||
|
||||
// Remove progress and all pages
|
||||
removeProgress();
|
||||
removeAllPages();
|
||||
|
||||
// Ensure error view
|
||||
if (mErrorView == null) {
|
||||
mErrorView = galleryView.obtainErrorView();
|
||||
galleryView.addComponent(mErrorView);
|
||||
}
|
||||
|
||||
// Update error string
|
||||
if (!errorStr.equals(mErrorStr)) {
|
||||
mErrorStr = errorStr;
|
||||
galleryView.bindErrorView(mErrorView, errorStr);
|
||||
}
|
||||
|
||||
// Place error view center
|
||||
placeCenter(mErrorView);
|
||||
} else {
|
||||
// Remove progress and error view
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
|
||||
// Ensure index in range
|
||||
int index = mIndex;
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
mIndex = index;
|
||||
removeAllPages();
|
||||
Log.e(TAG, "index < 0, index = " + index);
|
||||
} else if (index >= size) {
|
||||
index = size - 1;
|
||||
mIndex = index;
|
||||
removeAllPages();
|
||||
Log.e(TAG, "index >= size, index = " + index + ", size = " + size);
|
||||
}
|
||||
|
||||
// Ensure pages
|
||||
if (mCurrent == null) {
|
||||
mCurrent = obtainPage();
|
||||
galleryView.addComponent(mCurrent);
|
||||
adapter.bind(mCurrent, index);
|
||||
}
|
||||
if (mPrevious == null && index > 0) {
|
||||
mPrevious = obtainPage();
|
||||
galleryView.addComponent(mPrevious);
|
||||
adapter.bind(mPrevious, index - 1);
|
||||
} else if (mPrevious != null && index == 0) {
|
||||
removePage(mPrevious);
|
||||
}
|
||||
if (mNext == null && index < size - 1) {
|
||||
mNext = obtainPage();
|
||||
galleryView.addComponent(mNext);
|
||||
adapter.bind(mNext, index + 1);
|
||||
} else if (mNext != null && index == size - 1) {
|
||||
removePage(mNext);
|
||||
}
|
||||
|
||||
GalleryPageView leftPage = getLeftPage();
|
||||
GalleryPageView rightPage = getRightPage();
|
||||
|
||||
// Fix offset
|
||||
final int min = rightPage == null ? 0 : -width - mInterval + 1;
|
||||
final int max = leftPage == null ? 0 : width + mInterval - 1;
|
||||
mOffset = MathUtils.clamp(mOffset, min, max);
|
||||
|
||||
// Measure and layout pages
|
||||
final int offset = mOffset;
|
||||
final int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY);
|
||||
final int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.EXACTLY);
|
||||
if (mCurrent != null) {
|
||||
layoutPage(mCurrent, widthSpec, heightSpec,
|
||||
offset, 0, width + offset, height);
|
||||
}
|
||||
if (leftPage != null) {
|
||||
layoutPage(leftPage, widthSpec, heightSpec,
|
||||
-mInterval - width + offset, 0, -mInterval + offset, height);
|
||||
}
|
||||
if (rightPage != null) {
|
||||
layoutPage(rightPage, widthSpec, heightSpec,
|
||||
width + mInterval + offset, 0, width + mInterval + width + offset, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDown() {
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
mStopAnimationFinger = cancelAllAnimations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUp() {
|
||||
if (mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll
|
||||
if (mOffset != 0) {
|
||||
int width = mGalleryView.getWidth();
|
||||
int dx;
|
||||
if (mOffset >= mInterval && getLeftPage() != null) {
|
||||
dx = mOffset - width - mInterval;
|
||||
} else if (mOffset <= -mInterval && getRightPage() != null) {
|
||||
dx = mOffset + width + mInterval;
|
||||
} else {
|
||||
dx = mOffset;
|
||||
}
|
||||
final float pageDelta = 7 * (float) Math.abs(mOffset) / (width + mInterval);
|
||||
int duration = (int) ((pageDelta + 1) * 100);
|
||||
mSmoothScroller.startSmoothScroll(dx, 0, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDoubleTapConfirmed(float x, float y) {
|
||||
if (mCurrent == null || !mCurrent.getImageView().isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float[] scales = mScaleDefault;
|
||||
ImageView image = mCurrent.getImageView();
|
||||
image.getScaleDefault(scales);
|
||||
float scale = image.getScale();
|
||||
float endScale = scales[0];
|
||||
for (float value : scales) {
|
||||
if (scale < value - 0.01f) {
|
||||
endScale = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mSmoothScaler.startSmoothScaler(x, y, scale, endScale, 300);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(float x, float y) {
|
||||
}
|
||||
|
||||
private void pagePrevious() {
|
||||
if (mIndex <= 0) {
|
||||
return;
|
||||
}
|
||||
mIndex--;
|
||||
|
||||
if (mNext != null) {
|
||||
removePage(mNext);
|
||||
}
|
||||
mNext = mCurrent;
|
||||
mCurrent = mPrevious;
|
||||
mPrevious = null;
|
||||
|
||||
if (mIndex > 0) {
|
||||
mPrevious = obtainPage();
|
||||
mGalleryView.addComponent(mPrevious);
|
||||
mAdapter.bind(mPrevious, mIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void pageNext() {
|
||||
GalleryView.Adapter adapter = mAdapter;
|
||||
int size = adapter.size();
|
||||
if (mIndex >= size - 1) {
|
||||
return;
|
||||
}
|
||||
mIndex++;
|
||||
|
||||
if (mPrevious != null) {
|
||||
removePage(mPrevious);
|
||||
}
|
||||
mPrevious = mCurrent;
|
||||
mCurrent = mNext;
|
||||
mNext = null;
|
||||
|
||||
if (mIndex < size - 1) {
|
||||
mNext = obtainPage();
|
||||
mGalleryView.addComponent(mNext);
|
||||
adapter.bind(mNext, mIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void pageLeft() {
|
||||
switch (mMode) {
|
||||
case MODE_LEFT_TO_RIGHT:
|
||||
pagePrevious();
|
||||
break;
|
||||
default:
|
||||
case MODE_RIGHT_TO_LEFT:
|
||||
pageNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void pageRight() {
|
||||
switch (mMode) {
|
||||
case MODE_LEFT_TO_RIGHT:
|
||||
pageNext();
|
||||
break;
|
||||
default:
|
||||
case MODE_RIGHT_TO_LEFT:
|
||||
pagePrevious();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private int scrollBetweenPages(int dx) {
|
||||
GalleryPageView leftPage = getLeftPage();
|
||||
GalleryPageView rightPage = getRightPage();
|
||||
int width = mGalleryView.getWidth();
|
||||
|
||||
int remain;
|
||||
if (dx < 0) { // Try to show left
|
||||
int limit;
|
||||
if (leftPage == null) {
|
||||
limit = 0;
|
||||
} else {
|
||||
limit = width + mInterval;
|
||||
}
|
||||
|
||||
if (dx > mOffset - limit) {
|
||||
remain = 0;
|
||||
mOffset -= dx;
|
||||
} else {
|
||||
// Go to left page if left page not null
|
||||
if (leftPage != null) {
|
||||
pageLeft();
|
||||
}
|
||||
remain = dx + limit - mOffset;
|
||||
mOffset = 0;
|
||||
}
|
||||
} else { // Try to show right
|
||||
int limit;
|
||||
if (rightPage == null) {
|
||||
limit = 0;
|
||||
} else {
|
||||
limit = -width - mInterval;
|
||||
}
|
||||
|
||||
if (dx < mOffset - limit) {
|
||||
remain = 0;
|
||||
mOffset -= dx;
|
||||
} else {
|
||||
// Go to right page if right page not null
|
||||
if (rightPage != null) {
|
||||
pageRight();
|
||||
}
|
||||
remain = dx + limit - mOffset;
|
||||
mOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return remain;
|
||||
}
|
||||
|
||||
public void scrollInternal(float dx, float dy, float x, float y) {
|
||||
if (mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean needFill = false;
|
||||
boolean canImageScroll = true;
|
||||
int remainX = (int) dx;
|
||||
int remainY = (int) dy;
|
||||
|
||||
if (mGalleryView.isFirstScroll()) {
|
||||
mCanScrollBetweenPages = Math.abs(dx) > Math.abs(dy) * 1.5;
|
||||
}
|
||||
|
||||
while (remainX != 0 || remainY != 0) {
|
||||
if (mOffset == 0 && canImageScroll) {
|
||||
ImageView image = mCurrent.getImageView();
|
||||
image.scroll(remainX, remainY, mScrollRemain);
|
||||
remainX = mScrollRemain[0];
|
||||
remainY = mScrollRemain[1];
|
||||
canImageScroll = false;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
} else if (remainX == 0 ||
|
||||
(getLeftPage() == null && mOffset == 0 && remainX < 0) ||
|
||||
(getRightPage() == null && mOffset == 0 && remainX > 0)) {
|
||||
// On edge
|
||||
remainX = 0;
|
||||
remainY = 0;
|
||||
} else if (mCanScrollBetweenPages) {
|
||||
remainX = scrollBetweenPages(remainX);
|
||||
canImageScroll = true;
|
||||
needFill = true;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
} else {
|
||||
remainX = 0;
|
||||
remainY = 0;
|
||||
mDeltaX = 0;
|
||||
mDeltaY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (needFill) {
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(float dx, float dy, float totalX, float totalY, float x, float y) {
|
||||
scrollInternal(dx, dy, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFling(float velocityX, float velocityY) {
|
||||
if (mCurrent == null || mOffset != 0 || !mCurrent.getImageView().isLoaded() ||
|
||||
!mCurrent.getImageView().canFling()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImageView image = mCurrent.getImageView();
|
||||
mPageFling.startFling((int) velocityX, image.getMinDx(), image.getMaxDx(),
|
||||
(int) velocityY, image.getMinDy(), image.getMaxDy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canScale() {
|
||||
return mCurrent != null && mOffset == 0 && mCurrent.getImageView().isLoaded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScale(float focusX, float focusY, float scale) {
|
||||
if (mCurrent == null || !mCurrent.getImageView().isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrent.getImageView().scale(focusX, focusY, scale);
|
||||
mScaleValue = mCurrent.getImageView().getScale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUpdateAnimation(long time) {
|
||||
boolean invalidate = mSmoothScroller.calculate(time);
|
||||
invalidate |= mPageFling.calculate(time);
|
||||
invalidate |= mSmoothScaler.calculate(time);
|
||||
return invalidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged() {
|
||||
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
|
||||
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Remove all views
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageLeft() {
|
||||
int size = mAdapter.size();
|
||||
if (size <= 0 || mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMode == MODE_LEFT_TO_RIGHT) {
|
||||
if (mIndex != 0) {
|
||||
setCurrentIndex(mIndex - 1);
|
||||
}
|
||||
} else {
|
||||
if (mIndex < size - 1) {
|
||||
setCurrentIndex(mIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageRight() {
|
||||
int size = mAdapter.size();
|
||||
if (size <= 0 || mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMode == MODE_LEFT_TO_RIGHT) {
|
||||
if (mIndex < size - 1) {
|
||||
setCurrentIndex(mIndex + 1);
|
||||
}
|
||||
} else {
|
||||
if (mIndex != 0) {
|
||||
setCurrentIndex(mIndex - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTapOrPressEnable() {
|
||||
return !mStopAnimationFinger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryPageView findPageByIndex(int index) {
|
||||
if (mCurrent != null && mCurrent.getIndex() == index) {
|
||||
return mCurrent;
|
||||
}
|
||||
if (mPrevious != null && mPrevious.getIndex() == index) {
|
||||
return mPrevious;
|
||||
}
|
||||
if (mNext != null && mNext.getIndex() == index) {
|
||||
return mNext;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentIndex() {
|
||||
if (mCurrent != null) {
|
||||
return mCurrent.getIndex();
|
||||
} else {
|
||||
return GalleryPageView.INVALID_INDEX;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentIndex(int index) {
|
||||
int size = mAdapter.size();
|
||||
if (size <= 0) {
|
||||
// Can't get size now, assume size is MAX
|
||||
size = Integer.MAX_VALUE;
|
||||
}
|
||||
if (index == mIndex || index < 0 || index >= size) {
|
||||
return;
|
||||
}
|
||||
if (mCurrent == null) {
|
||||
mIndex = index;
|
||||
} else if (index == mIndex - 1) {
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
// Go to previous
|
||||
pagePrevious();
|
||||
// Request fill
|
||||
mGalleryView.requestFill();
|
||||
} else if (index == mIndex + 1) {
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
// Go to next
|
||||
pageNext();
|
||||
// Request fill
|
||||
mGalleryView.requestFill();
|
||||
} else {
|
||||
mIndex = index;
|
||||
// It is attached, refill
|
||||
// Cancel all animations
|
||||
cancelAllAnimations();
|
||||
// Remove all view
|
||||
removeProgress();
|
||||
removeErrorView();
|
||||
removeAllPages();
|
||||
// Reset parameters
|
||||
resetParameters();
|
||||
// Request fill
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexUnder(float x, float y) {
|
||||
if (mCurrent == null) {
|
||||
return GalleryPageView.INVALID_INDEX;
|
||||
} else {
|
||||
int intX = (int) x;
|
||||
int intY = (int) y;
|
||||
if (mCurrent.bounds().contains(intX, intY)) {
|
||||
return mCurrent.getIndex();
|
||||
} else if (mPrevious != null && mPrevious.bounds().contains(intX, intY)) {
|
||||
return mPrevious.getIndex();
|
||||
} else if (mNext != null && mNext.bounds().contains(intX, intY)) {
|
||||
return mNext.getIndex();
|
||||
} else {
|
||||
return GalleryPageView.INVALID_INDEX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
int getInternalCurrentIndex() {
|
||||
int currentIndex = getCurrentIndex();
|
||||
if (currentIndex == GalleryPageView.INVALID_INDEX) {
|
||||
currentIndex = mIndex;
|
||||
}
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
@IntDef({MODE_LEFT_TO_RIGHT, MODE_RIGHT_TO_LEFT})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Mode {
|
||||
}
|
||||
|
||||
private class SmoothScroller extends Animation {
|
||||
|
||||
private int mDx;
|
||||
private int mDy;
|
||||
private int mLastX;
|
||||
private int mLastY;
|
||||
|
||||
public SmoothScroller() {
|
||||
setInterpolator(SMOOTH_SCROLLER_INTERPOLATOR);
|
||||
}
|
||||
|
||||
public void startSmoothScroll(int dx, int dy, int duration) {
|
||||
mDx = dx;
|
||||
mDy = dy;
|
||||
mLastX = 0;
|
||||
mLastY = 0;
|
||||
setDuration(duration);
|
||||
start();
|
||||
mGalleryView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
int x = (int) (mDx * progress);
|
||||
int y = (int) (mDy * progress);
|
||||
int offsetX = x - mLastX;
|
||||
while (offsetX != 0) {
|
||||
int oldOffsetX = offsetX;
|
||||
offsetX = scrollBetweenPages(offsetX);
|
||||
// Avoid loop infinitely
|
||||
if (offsetX == oldOffsetX) {
|
||||
break;
|
||||
} else {
|
||||
mGalleryView.requestFill();
|
||||
}
|
||||
}
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
}
|
||||
}
|
||||
|
||||
private class PageFling extends Fling {
|
||||
|
||||
private final int[] mTemp = new int[2];
|
||||
private int mVelocityX;
|
||||
private int mVelocityY;
|
||||
private int mDx;
|
||||
private int mDy;
|
||||
private int mLastX;
|
||||
private int mLastY;
|
||||
|
||||
public PageFling(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void startFling(int velocityX, int minX, int maxX,
|
||||
int velocityY, int minY, int maxY) {
|
||||
mVelocityX = velocityX;
|
||||
mVelocityY = velocityY;
|
||||
mDx = (int) (getSplineFlingDistance(velocityX) * Math.signum(velocityX));
|
||||
mDy = (int) (getSplineFlingDistance(velocityY) * Math.signum(velocityY));
|
||||
mLastX = 0;
|
||||
mLastY = 0;
|
||||
int durationX = getSplineFlingDuration(velocityX);
|
||||
int durationY = getSplineFlingDuration(velocityY);
|
||||
|
||||
if (mDx < minX) {
|
||||
durationX = adjustDuration(0, mDx, minX, durationX);
|
||||
mDx = minX;
|
||||
}
|
||||
if (mDx > maxX) {
|
||||
durationX = adjustDuration(0, mDx, maxX, durationX);
|
||||
mDx = maxX;
|
||||
}
|
||||
if (mDy < minY) {
|
||||
durationY = adjustDuration(0, mDy, minY, durationY);
|
||||
mDy = minY;
|
||||
}
|
||||
if (mDy > maxY) {
|
||||
durationY = adjustDuration(0, mDy, maxY, durationY);
|
||||
mDy = maxY;
|
||||
}
|
||||
|
||||
if (mDx == 0 && mDy == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDuration(Math.max(durationX, durationY));
|
||||
start();
|
||||
mGalleryView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
int x = (int) (mDx * progress);
|
||||
int y = (int) (mDy * progress);
|
||||
int offsetX = x - mLastX;
|
||||
int offsetY = y - mLastY;
|
||||
if (mCurrent != null && (offsetX != 0 || offsetY != 0)) {
|
||||
mCurrent.getImageView().scroll(-offsetX, -offsetY, mTemp);
|
||||
}
|
||||
mLastX = x;
|
||||
mLastY = y;
|
||||
}
|
||||
}
|
||||
|
||||
private class SmoothScaler extends Animation {
|
||||
|
||||
private float mFocusX;
|
||||
private float mFocusY;
|
||||
private float mStartScale;
|
||||
private float mEndScale;
|
||||
private float mLastScale;
|
||||
|
||||
public SmoothScaler() {
|
||||
setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
|
||||
}
|
||||
|
||||
public void startSmoothScaler(float focusX, float focusY,
|
||||
float startScale, float endScale, int duration) {
|
||||
mFocusX = focusX;
|
||||
mFocusY = focusY;
|
||||
mStartScale = startScale;
|
||||
mEndScale = endScale;
|
||||
mLastScale = startScale;
|
||||
setDuration(duration);
|
||||
start();
|
||||
mGalleryView.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
if (mCurrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
float scale = MathUtils.lerp(mStartScale, mEndScale, progress);
|
||||
mCurrent.getImageView().scale(mFocusX, mFocusY, scale / mLastScale);
|
||||
mLastScale = scale;
|
||||
mScaleValue = scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,163 +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.glgallery;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.glview.image.ImageTexture;
|
||||
import com.hippo.glview.image.ImageWrapper;
|
||||
import com.hippo.glview.view.GLRootView;
|
||||
|
||||
public class SimpleAdapter extends GalleryView.Adapter implements GalleryProvider.Listener {
|
||||
|
||||
private final GalleryProvider mProvider;
|
||||
private final ImageTexture.Uploader mUploader;
|
||||
private boolean mShowIndex = true;
|
||||
|
||||
public SimpleAdapter(@NonNull GLRootView glRootView, @NonNull GalleryProvider provider) {
|
||||
mProvider = provider;
|
||||
mUploader = new ImageTexture.Uploader(glRootView);
|
||||
}
|
||||
|
||||
public void clearUploader() {
|
||||
mUploader.clear();
|
||||
}
|
||||
|
||||
public void setShowIndex(boolean show) {
|
||||
mShowIndex = show;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(GalleryPageView view, int index) {
|
||||
mProvider.request(index);
|
||||
view.showInfo();
|
||||
view.setImage(null);
|
||||
if (mShowIndex) {
|
||||
view.setPage(index + 1);
|
||||
} else {
|
||||
view.hidePage();
|
||||
}
|
||||
view.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
|
||||
view.setError(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnbind(GalleryPageView view, int index) {
|
||||
mProvider.cancelRequest(index);
|
||||
view.setImage(null);
|
||||
view.setError(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getError() {
|
||||
return mProvider.getError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return mProvider.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged() {
|
||||
mGalleryView.onDataChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageWait(int index) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
page.showInfo();
|
||||
page.setImage(null);
|
||||
if (mShowIndex) {
|
||||
page.setPage(index + 1);
|
||||
} else {
|
||||
page.hidePage();
|
||||
}
|
||||
page.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
|
||||
page.setError(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPagePercent(int index, float percent) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
page.showInfo();
|
||||
page.setImage(null);
|
||||
if (mShowIndex) {
|
||||
page.setPage(index + 1);
|
||||
} else {
|
||||
page.hidePage();
|
||||
}
|
||||
page.setProgress(percent);
|
||||
page.setError(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSucceed(int index, ImageWrapper image) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
if (image.obtain()) {
|
||||
ImageTexture imageTexture = new ImageTexture(image);
|
||||
mUploader.addTexture(imageTexture);
|
||||
page.showImage();
|
||||
page.setImage(imageTexture);
|
||||
if (mShowIndex) {
|
||||
page.setPage(index + 1);
|
||||
} else {
|
||||
page.hidePage();
|
||||
}
|
||||
page.setProgress(GalleryPageView.PROGRESS_GONE);
|
||||
page.setError(null, null);
|
||||
} else {
|
||||
// The image is recycled, request again.
|
||||
// TODO request loop ?
|
||||
mProvider.request(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFailed(int index, String error) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
page.showInfo();
|
||||
page.setImage(null);
|
||||
if (mShowIndex) {
|
||||
page.setPage(index + 1);
|
||||
} else {
|
||||
page.hidePage();
|
||||
}
|
||||
page.setProgress(GalleryPageView.PROGRESS_GONE);
|
||||
page.setError(error, mGalleryView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataChanged(int index) {
|
||||
GalleryPageView page = findPageByIndex(index);
|
||||
if (page != null) {
|
||||
mProvider.request(index);
|
||||
}
|
||||
}
|
||||
|
||||
private GalleryPageView findPageByIndex(int index) {
|
||||
return mGalleryView != null ? mGalleryView.findPageByIndex(index) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.anim;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class AlphaAnimation extends CanvasAnimation {
|
||||
private final float mStartAlpha;
|
||||
private final float mEndAlpha;
|
||||
private float mCurrentAlpha;
|
||||
|
||||
public AlphaAnimation(float from, float to) {
|
||||
mStartAlpha = from;
|
||||
mEndAlpha = to;
|
||||
mCurrentAlpha = from;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(GLCanvas canvas) {
|
||||
canvas.multiplyAlpha(mCurrentAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCanvasSaveFlags() {
|
||||
return GLCanvas.SAVE_FLAG_ALPHA;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
mCurrentAlpha = MathUtils.clamp(mStartAlpha +
|
||||
(mEndAlpha - mStartAlpha) * progress, 0f, 1f);
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.anim;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
// Animation calculates a value according to the current input time.
|
||||
//
|
||||
// 1. First we need to use setDuration(int) to set the duration of the
|
||||
// animation. The duration is in milliseconds.
|
||||
// 2. Then we should call start(). The actual start time is the first value
|
||||
// passed to calculate(long).
|
||||
// 3. Each time we want to get an animation value, we call
|
||||
// calculate(long currentTimeMillis) to ask the Animation to calculate it.
|
||||
// The parameter passed to calculate(long) should be nonnegative.
|
||||
// 4. Use get() to get that value.
|
||||
//
|
||||
// In step 3, onCalculate(float progress) is called so subclasses can calculate
|
||||
// the value according to progress (progress is a value in [0,1]).
|
||||
//
|
||||
// Before onCalculate(float) is called, There is an optional interpolator which
|
||||
// can change the progress value. The interpolator can be set by
|
||||
// setInterpolator(Interpolator). If the interpolator is used, the value passed
|
||||
// to onCalculate may be (for example, the overshoot effect).
|
||||
//
|
||||
// The isActive() method returns true after the animation start() is called and
|
||||
// before calculate is passed a value which reaches the duration of the
|
||||
// animation.
|
||||
//
|
||||
// The start() method can be called again to restart the Animation.
|
||||
//
|
||||
abstract public class Animation {
|
||||
/**
|
||||
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
|
||||
* or a positive value, the animation restarts from the beginning.
|
||||
*/
|
||||
public static final int RESTART = 1;
|
||||
/**
|
||||
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
|
||||
* or a positive value, the animation reverses direction on every iteration.
|
||||
*/
|
||||
public static final int REVERSE = 2;
|
||||
/**
|
||||
* This value used used with the {@link #setRepeatCount(int)} property to repeat
|
||||
* the animation indefinitely.
|
||||
*/
|
||||
public static final int INFINITE = -1;
|
||||
private static final long ANIMATION_START = -1;
|
||||
private static final long NO_ANIMATION = -2;
|
||||
private long mStartTime = NO_ANIMATION;
|
||||
private long mDuration;
|
||||
private Interpolator mInterpolator;
|
||||
private int mRepeatCount;
|
||||
private int mRepeatMode;
|
||||
|
||||
private int mRunnedCount;
|
||||
private long mLastFrameTime;
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
mDuration = duration;
|
||||
}
|
||||
|
||||
public void setRepeatCount(int repeatCount) {
|
||||
mRepeatCount = repeatCount;
|
||||
}
|
||||
|
||||
public void setRepeatMode(int repeatMode) {
|
||||
mRepeatMode = repeatMode;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return mStartTime > 0 || mStartTime == ANIMATION_START;
|
||||
}
|
||||
|
||||
public long getLastFrameTime() {
|
||||
return mLastFrameTime;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (mStartTime == NO_ANIMATION) {
|
||||
mStartTime = ANIMATION_START;
|
||||
mRunnedCount = 0;
|
||||
mLastFrameTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void startAt(long time) {
|
||||
start();
|
||||
setStartTime(time);
|
||||
}
|
||||
|
||||
public void startNow() {
|
||||
start();
|
||||
setStartTime(SystemClock.uptimeMillis());
|
||||
}
|
||||
|
||||
public void setStartTime(long time) {
|
||||
mStartTime = time;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
mStartTime = NO_ANIMATION;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mStartTime = ANIMATION_START;
|
||||
mRunnedCount = 0;
|
||||
mLastFrameTime = 0;
|
||||
}
|
||||
|
||||
// TODO mRepeatMode
|
||||
public boolean calculate(long currentTimeMillis) {
|
||||
if (mStartTime == NO_ANIMATION) {
|
||||
return false;
|
||||
}
|
||||
if (mStartTime == ANIMATION_START) {
|
||||
mStartTime = currentTimeMillis;
|
||||
}
|
||||
mLastFrameTime = currentTimeMillis;
|
||||
|
||||
long elapse = currentTimeMillis - mStartTime;
|
||||
float x = MathUtils.clamp(mDuration == 0.0f ? 1.0f : (float) elapse / mDuration, 0.0f, 1.0f); // Avoid NaN
|
||||
Interpolator i = mInterpolator;
|
||||
onCalculate(i != null ? i.getInterpolation(x) : x);
|
||||
|
||||
// It is ok to call cancel() in onCalculate()
|
||||
if (mStartTime != NO_ANIMATION && elapse >= mDuration) {
|
||||
mRunnedCount++;
|
||||
if (mRunnedCount >= mRepeatCount && mRepeatCount != INFINITE) {
|
||||
onFinish();
|
||||
mStartTime = NO_ANIMATION;
|
||||
} else {
|
||||
mStartTime += elapse;
|
||||
}
|
||||
}
|
||||
|
||||
return mStartTime != NO_ANIMATION;
|
||||
}
|
||||
|
||||
abstract protected void onCalculate(float progress);
|
||||
|
||||
protected void onFinish() {
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.anim;
|
||||
|
||||
import com.hippo.glview.glrenderer.GLCanvas;
|
||||
|
||||
public abstract class CanvasAnimation extends Animation {
|
||||
|
||||
public abstract int getCanvasSaveFlags();
|
||||
|
||||
public abstract void apply(GLCanvas canvas);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.anim;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class FloatAnimation extends Animation {
|
||||
|
||||
private float mFrom;
|
||||
private float mTo;
|
||||
private float mCurrent;
|
||||
|
||||
public void setRange(float from, float to) {
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
mCurrent = MathUtils.lerp(mFrom, mTo, progress);
|
||||
}
|
||||
|
||||
public float get() {
|
||||
return mCurrent;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +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.glview.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, ElementType.CONSTRUCTOR, ElementType.TYPE})
|
||||
public @interface RenderThread {
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
// BasicTexture is a Texture corresponds to a real GL texture.
|
||||
// The state of a BasicTexture indicates whether its data is loaded to GL memory.
|
||||
// If a BasicTexture is loaded into GL memory, it has a GL texture id.
|
||||
public abstract class BasicTexture implements Texture {
|
||||
|
||||
protected static final int UNSPECIFIED = -1;
|
||||
protected static final int STATE_UNLOADED = 0;
|
||||
protected static final int STATE_LOADED = 1;
|
||||
protected static final int STATE_ERROR = -1;
|
||||
private static final String TAG = "BasicTexture";
|
||||
// Log a warning if a texture is larger along a dimension
|
||||
private static final int MAX_TEXTURE_SIZE = 4096;
|
||||
private final static WeakHashMap<BasicTexture, Object> sAllTextures
|
||||
= new WeakHashMap<>();
|
||||
private static final ThreadLocal<Class> sInFinalizer = new ThreadLocal<>();
|
||||
protected int mId = -1;
|
||||
protected int mState;
|
||||
protected int mWidth = UNSPECIFIED;
|
||||
protected int mHeight = UNSPECIFIED;
|
||||
protected int mTextureWidth;
|
||||
protected int mTextureHeight;
|
||||
protected GLCanvas mCanvasRef = null;
|
||||
private boolean mHasBorder;
|
||||
|
||||
protected BasicTexture(GLCanvas canvas, int id, int state) {
|
||||
setAssociatedCanvas(canvas);
|
||||
mId = id;
|
||||
mState = state;
|
||||
synchronized (sAllTextures) {
|
||||
sAllTextures.put(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected BasicTexture() {
|
||||
this(null, 0, STATE_UNLOADED);
|
||||
}
|
||||
|
||||
// This is for deciding if we can call Bitmap's recycle().
|
||||
// We cannot call Bitmap's recycle() in finalizer because at that point
|
||||
// the finalizer of Bitmap may already be called so recycle() will crash.
|
||||
public static boolean inFinalizer() {
|
||||
return sInFinalizer.get() != null;
|
||||
}
|
||||
|
||||
public static void yieldAllTextures() {
|
||||
synchronized (sAllTextures) {
|
||||
for (BasicTexture t : sAllTextures.keySet()) {
|
||||
t.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidateAllTextures() {
|
||||
synchronized (sAllTextures) {
|
||||
for (BasicTexture t : sAllTextures.keySet()) {
|
||||
t.mState = STATE_UNLOADED;
|
||||
t.setAssociatedCanvas(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setAssociatedCanvas(GLCanvas canvas) {
|
||||
mCanvasRef = canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content size of this texture. In OpenGL, the actual texture
|
||||
* size must be of power of 2, the size of the content may be smaller.
|
||||
*/
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mTextureWidth = width > 0 ? MathUtils.nextPowerOf2(width) : 0;
|
||||
mTextureHeight = height > 0 ? MathUtils.nextPowerOf2(height) : 0;
|
||||
if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
|
||||
Log.w(TAG, String.format("texture is too large: %d x %d",
|
||||
mTextureWidth, mTextureHeight), new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFlippedVertically() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
// Returns the width rounded to the next power of 2.
|
||||
public int getTextureWidth() {
|
||||
return mTextureWidth;
|
||||
}
|
||||
|
||||
// Returns the height rounded to the next power of 2.
|
||||
public int getTextureHeight() {
|
||||
return mTextureHeight;
|
||||
}
|
||||
|
||||
// Returns true if the texture has one pixel transparent border around the
|
||||
// actual content. This is used to avoid jigged edges.
|
||||
//
|
||||
// The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
|
||||
// mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
|
||||
// covered by the texture will use the color of the edge texel. If we add
|
||||
// the transparent border, the color of the edge texel will be mixed with
|
||||
// appropriate amount of transparent.
|
||||
//
|
||||
// Currently our background is black, so we can draw the thumbnails without
|
||||
// enabling blending.
|
||||
public boolean hasBorder() {
|
||||
return mHasBorder;
|
||||
}
|
||||
|
||||
protected void setBorder(boolean hasBorder) {
|
||||
mHasBorder = hasBorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
canvas.drawTexture(this, x, y, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
canvas.drawTexture(this, x, y, w, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
canvas.drawTexture(this, source, target);
|
||||
}
|
||||
|
||||
// onBind is called before GLCanvas binds this texture.
|
||||
// It should make sure the data is uploaded to GL memory.
|
||||
abstract protected boolean onBind(GLCanvas canvas);
|
||||
|
||||
// Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
|
||||
abstract protected int getTarget();
|
||||
|
||||
public boolean isLoaded() {
|
||||
return mState == STATE_LOADED;
|
||||
}
|
||||
|
||||
// recycle() is called when the texture will never be used again,
|
||||
// so it can free all resources.
|
||||
public void recycle() {
|
||||
freeResource();
|
||||
}
|
||||
|
||||
// yield() is called when the texture will not be used temporarily,
|
||||
// so it can free some resources.
|
||||
// The default implementation unloads the texture from GL memory, so
|
||||
// the subclass should make sure it can reload the texture to GL memory
|
||||
// later, or it will have to override this method.
|
||||
public void yield() {
|
||||
freeResource();
|
||||
}
|
||||
|
||||
private void freeResource() {
|
||||
GLCanvas canvas = mCanvasRef;
|
||||
if (canvas != null && mId != -1) {
|
||||
canvas.unloadTexture(this);
|
||||
mId = -1; // Don't free it again.
|
||||
}
|
||||
mState = STATE_UNLOADED;
|
||||
setAssociatedCanvas(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
sInFinalizer.set(BasicTexture.class);
|
||||
recycle();
|
||||
sInFinalizer.set(null);
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
|
||||
// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
|
||||
//
|
||||
// The texture does not own the Bitmap. The user should make sure the Bitmap
|
||||
// is valid during the texture's lifetime. When the texture is recycled, it
|
||||
// does not free the Bitmap.
|
||||
public class BitmapTexture extends UploadedTexture {
|
||||
protected Bitmap mContentBitmap;
|
||||
|
||||
public BitmapTexture(Bitmap bitmap) {
|
||||
this(bitmap, false);
|
||||
}
|
||||
|
||||
public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
|
||||
super(hasBorder);
|
||||
AssertUtils.assertTrue(bitmap != null && !bitmap.isRecycled());
|
||||
mContentBitmap = bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
return mContentBitmap;
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return mContentBitmap;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
|
||||
// CanvasTexture is a texture whose content is the drawing on a Canvas.
|
||||
// The subclasses should override onDraw() to draw on the bitmap.
|
||||
// By default CanvasTexture is not opaque.
|
||||
abstract class CanvasTexture extends UploadedTexture {
|
||||
private final Config mConfig;
|
||||
|
||||
public CanvasTexture(int width, int height) {
|
||||
mConfig = Config.ARGB_8888;
|
||||
setSize(width, height);
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
onDraw(canvas, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
if (!inFinalizer()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected void onDraw(Canvas canvas, Bitmap backing);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.hippo.yorozuya.ColorUtils;
|
||||
|
||||
// ColorTexture is a texture which fills the rectangle with the specified color.
|
||||
public class ColorTexture implements Texture {
|
||||
|
||||
private final int mColor;
|
||||
private int mWidth = 0;
|
||||
private int mHeight = 0;
|
||||
|
||||
public ColorTexture(int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
private boolean isValidSize() {
|
||||
return mWidth > 0 && mHeight > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
if (isValidSize()) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
canvas.fillRect(x, y, w, h, mColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
canvas.fillRect(target.left, target.top, target.width(), target.height(), mColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return ColorUtils.isOpaque(mColor);
|
||||
}
|
||||
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
// ExtTexture is a texture whose content comes from a external texture.
|
||||
// Before drawing, setSize() should be called.
|
||||
public class ExtTexture extends BasicTexture {
|
||||
|
||||
private final int mTarget;
|
||||
|
||||
public ExtTexture(GLCanvas canvas, int target) {
|
||||
GLId glId = canvas.getGLId();
|
||||
mId = glId.generateTexture();
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
canvas.setTextureParameters(this);
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
uploadToCanvas(canvas);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTarget() {
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void yield() {
|
||||
// we cannot free the texture because we have no backup.
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
// FadeInTexture is a texture which begins with a color, then gradually animates
|
||||
// into a given texture.
|
||||
public class FadeInTexture extends FadeTexture implements Texture {
|
||||
|
||||
private final int mColor;
|
||||
private final TiledTexture mTexture;
|
||||
|
||||
public FadeInTexture(int color, TiledTexture texture) {
|
||||
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
|
||||
mColor = color;
|
||||
mTexture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (isAnimating()) {
|
||||
mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h);
|
||||
} else {
|
||||
mTexture.draw(canvas, x, y, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
if (isAnimating()) {
|
||||
mTexture.drawMixed(canvas, mColor, getRatio(), source, target);
|
||||
} else {
|
||||
mTexture.draw(canvas, source, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
// FadeOutTexture is a texture which begins with a given texture, then gradually animates
|
||||
// into fading out totally.
|
||||
public class FadeOutTexture extends FadeTexture {
|
||||
|
||||
private final BasicTexture mTexture;
|
||||
|
||||
public FadeOutTexture(BasicTexture texture) {
|
||||
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
|
||||
mTexture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (isAnimating()) {
|
||||
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
|
||||
canvas.setAlpha(getRatio());
|
||||
mTexture.draw(canvas, x, y, w, h);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
if (isAnimating()) {
|
||||
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
|
||||
canvas.setAlpha(getRatio());
|
||||
mTexture.draw(canvas, source, target);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import com.hippo.glview.view.AnimationTime;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
// FadeTexture is a texture which fades the given texture along the time.
|
||||
public abstract class FadeTexture implements Texture {
|
||||
|
||||
// The duration of the fading animation in milliseconds
|
||||
public static final int DURATION = 180;
|
||||
|
||||
private final long mStartTime;
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final boolean mIsOpaque;
|
||||
private boolean mIsAnimating;
|
||||
|
||||
public FadeTexture(int width, int height, boolean opaque) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mIsOpaque = opaque;
|
||||
mStartTime = now();
|
||||
mIsAnimating = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mIsOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public boolean isAnimating() {
|
||||
if (mIsAnimating) {
|
||||
if (now() - mStartTime >= DURATION) {
|
||||
mIsAnimating = false;
|
||||
}
|
||||
}
|
||||
return mIsAnimating;
|
||||
}
|
||||
|
||||
protected float getRatio() {
|
||||
float r = (float) (now() - mStartTime) / DURATION;
|
||||
return MathUtils.clamp(1.0f - r, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
private long now() {
|
||||
return AnimationTime.get();
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
//
|
||||
// GLCanvas gives a convenient interface to draw using OpenGL.
|
||||
//
|
||||
// When a rectangle is specified in this interface, it means the region
|
||||
// [x, x+width) * [y, y+height)
|
||||
//
|
||||
public interface GLCanvas {
|
||||
|
||||
int SAVE_FLAG_ALL = 0xFFFFFFFF;
|
||||
int SAVE_FLAG_ALPHA = 0x01;
|
||||
int SAVE_FLAG_MATRIX = 0x02;
|
||||
|
||||
GLId getGLId();
|
||||
|
||||
// Tells GLCanvas the size of the underlying GL surface. This should be
|
||||
// called before first drawing and when the size of GL surface is changed.
|
||||
// This is called by GLRoot and should not be called by the clients
|
||||
// who only want to draw on the GLCanvas. Both width and height must be
|
||||
// nonnegative.
|
||||
void setSize(int width, int height);
|
||||
|
||||
// Clear the drawing buffers. This should only be used by GLRoot.
|
||||
void clearBuffer();
|
||||
|
||||
void clearBuffer(float[] argb);
|
||||
|
||||
float getAlpha();
|
||||
|
||||
// Sets and gets the current alpha, alpha must be in [0, 1].
|
||||
void setAlpha(float alpha);
|
||||
|
||||
// (current alpha) = (current alpha) * alpha
|
||||
void multiplyAlpha(float alpha);
|
||||
|
||||
// Change the current transform matrix.
|
||||
void translate(float x, float y, float z);
|
||||
|
||||
void translate(float x, float y);
|
||||
|
||||
void scale(float sx, float sy, float sz);
|
||||
|
||||
void rotate(float angle, float x, float y, float z);
|
||||
|
||||
void multiplyMatrix(float[] mMatrix, int offset);
|
||||
|
||||
// Pushes the configuration state (matrix, and alpha) onto
|
||||
// a private stack.
|
||||
void save();
|
||||
|
||||
// Same as save(), but only save those specified in saveFlags.
|
||||
void save(int saveFlags);
|
||||
|
||||
// Pops from the top of the stack as current configuration state (matrix,
|
||||
// alpha, and clip). This call balances a previous call to save(), and is
|
||||
// used to remove all modifications to the configuration state since the
|
||||
// last save call.
|
||||
void restore();
|
||||
|
||||
// Draws a line using the specified paint from (x1, y1) to (x2, y2).
|
||||
// (Both end points are included).
|
||||
void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
|
||||
|
||||
// Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
|
||||
// (Both end points are included).
|
||||
void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
|
||||
|
||||
// Draws a oval using the specified paint for (cx, cy, radiusX, radiusY)
|
||||
// (Both end points are included).
|
||||
void drawOval(float cx, float cy, float radiusX, float radiusY,
|
||||
GLPaint paint);
|
||||
|
||||
// Draw a arc inside a rect
|
||||
void drawArc(float cx, float cy, float radiusX, float radiusY,
|
||||
float sweepAngle, GLPaint paint);
|
||||
|
||||
// Fills the specified rectangle with the specified color.
|
||||
void fillRect(float x, float y, float width, float height, int color);
|
||||
|
||||
// Fills the specified oval with the specified color.
|
||||
void fillOval(float cx, float cy, float radiusX, float radiusY, int color);
|
||||
|
||||
// Fills the specified sector with the specified color.
|
||||
void fillSector(float cx, float cy, float radiusX, float radiusY, float sweepAngle, int color);
|
||||
|
||||
// Draws a texture to the specified rectangle.
|
||||
void drawTexture(BasicTexture texture, int x, int y, int width, int height);
|
||||
|
||||
void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, int uvBuffer, int indexBuffer, int indexCount);
|
||||
|
||||
// Draws the source rectangle part of the texture to the target rectangle.
|
||||
void drawTexture(BasicTexture texture, RectF source, RectF target);
|
||||
|
||||
// Draw a texture with a specified texture transform.
|
||||
void drawTexture(BasicTexture texture, float[] mTextureTransform, int x, int y, int w, int h);
|
||||
|
||||
// Draw two textures to the specified rectangle. The actual texture used is
|
||||
// from * (1 - ratio) + to * ratio
|
||||
// The two textures must have the same size.
|
||||
void drawMixed(BasicTexture from, int toColor, float ratio, int x, int y, int w, int h);
|
||||
|
||||
// Draw a region of a texture and a specified color to the specified
|
||||
// rectangle. The actual color used is from * (1 - ratio) + to * ratio.
|
||||
// The region of the texture is defined by parameter "src". The target
|
||||
// rectangle is specified by parameter "target".
|
||||
void drawMixed(BasicTexture from, int toColor, float ratio, RectF src, RectF target);
|
||||
|
||||
// Unloads the specified texture from the canvas. The resource allocated
|
||||
// to draw the texture will be released. The specified texture will return
|
||||
// to the unloaded state. This function should be called only from
|
||||
// BasicTexture or its descendant
|
||||
boolean unloadTexture(BasicTexture texture);
|
||||
|
||||
// Delete the specified buffer object, similar to unloadTexture.
|
||||
void deleteBuffer(int bufferId);
|
||||
|
||||
// Delete the textures and buffers in GL side. This function should only be
|
||||
// called in the GL thread.
|
||||
void deleteRecycledResources();
|
||||
|
||||
// Dump statistics information and clear the counters. For debug only.
|
||||
void dumpStatisticsAndClear();
|
||||
|
||||
void beginRenderTarget(RawTexture texture);
|
||||
|
||||
void endRenderTarget();
|
||||
|
||||
/**
|
||||
* Sets texture parameters to use GL_CLAMP_TO_EDGE for both
|
||||
* GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
|
||||
* GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
|
||||
* bindTexture() must be called prior to this.
|
||||
*
|
||||
* @param texture The texture to set parameters on.
|
||||
*/
|
||||
void setTextureParameters(BasicTexture texture);
|
||||
|
||||
/**
|
||||
* Initializes the texture to a size by calling texImage2D on it.
|
||||
*
|
||||
* @param texture The texture to initialize the size.
|
||||
* @param format The texture format (e.g. GL_RGBA)
|
||||
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
|
||||
*/
|
||||
void initializeTextureSize(BasicTexture texture, int format, int type);
|
||||
|
||||
/**
|
||||
* Initializes the texture to a size by calling texImage2D on it.
|
||||
*
|
||||
* @param texture The texture to initialize the size.
|
||||
* @param bitmap The bitmap to initialize the bitmap with.
|
||||
*/
|
||||
void initializeTexture(BasicTexture texture, Bitmap bitmap);
|
||||
|
||||
/**
|
||||
* Calls glTexSubImage2D to upload a bitmap to the texture.
|
||||
*
|
||||
* @param texture The target texture to write to.
|
||||
* @param xOffset Specifies a texel offset in the x direction within the
|
||||
* texture array.
|
||||
* @param yOffset Specifies a texel offset in the y direction within the
|
||||
* texture array.
|
||||
* @param format The texture format (e.g. GL_RGBA)
|
||||
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
|
||||
*/
|
||||
void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, int format, int type);
|
||||
|
||||
/**
|
||||
* Generates buffers and uploads the buffer data.
|
||||
*
|
||||
* @param buffer The buffer to upload
|
||||
* @return The buffer ID that was generated.
|
||||
*/
|
||||
int uploadBuffer(java.nio.FloatBuffer buffer);
|
||||
|
||||
/**
|
||||
* Generates buffers and uploads the element array buffer data.
|
||||
*
|
||||
* @param buffer The buffer to upload
|
||||
* @return The buffer ID that was generated.
|
||||
*/
|
||||
int uploadBuffer(java.nio.ByteBuffer buffer);
|
||||
|
||||
/**
|
||||
* After LightCycle makes GL calls, this method is called to restore the GL
|
||||
* configuration to the one expected by GLCanvas.
|
||||
*/
|
||||
void recoverFromLightCycle();
|
||||
|
||||
/**
|
||||
* Gets the bounds given by x, y, width, and height as well as the internal
|
||||
* matrix state. There is no special handling for non-90-degree rotations.
|
||||
* It only considers the lower-left and upper-right corners as the bounds.
|
||||
*
|
||||
* @param bounds The output bounds to write to.
|
||||
* @param x The left side of the input rectangle.
|
||||
* @param y The bottom of the input rectangle.
|
||||
* @param width The width of the input rectangle.
|
||||
* @param height The height of the input rectangle.
|
||||
*/
|
||||
void getBounds(Rect bounds, int x, int y, int width, int height);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
/**
|
||||
* Open GL ES 1.1 implementation for generating and destroying texture IDs and
|
||||
* buffer IDs
|
||||
*/
|
||||
public class GLES11IdImpl implements GLId {
|
||||
|
||||
// Mutex for sNextId
|
||||
private final static Object sLock = new Object();
|
||||
private static int sNextId = 1;
|
||||
|
||||
@Override
|
||||
public int generateTexture() {
|
||||
synchronized (sLock) {
|
||||
return sNextId++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
while (n-- > 0) {
|
||||
buffers[offset + n] = sNextId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl.glDeleteTextures(n, textures, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl.glDeleteBuffers(n, buffers, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,60 +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.glview.glrenderer;
|
||||
|
||||
import android.opengl.GLES20;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
public class GLES20IdImpl implements GLId {
|
||||
private final int[] mTempIntArray = new int[1];
|
||||
|
||||
@Override
|
||||
public int generateTexture() {
|
||||
GLES20.glGenTextures(1, mTempIntArray, 0);
|
||||
GLES20Canvas.checkError();
|
||||
return mTempIntArray[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(int n, int[] buffers, int offset) {
|
||||
GLES20.glGenBuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
|
||||
GLES20.glDeleteTextures(n, textures, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
|
||||
GLES20.glDeleteBuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
|
||||
GLES20.glDeleteFramebuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
// This mimics corresponding GL functions.
|
||||
public interface GLId {
|
||||
int generateTexture();
|
||||
|
||||
void glGenBuffers(int n, int[] buffers, int offset);
|
||||
|
||||
void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
|
||||
|
||||
void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
|
||||
|
||||
void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
public class GLPaint {
|
||||
private float mLineWidth = 1f;
|
||||
private int mColor = 0;
|
||||
private int mBackgroundColor = 0;
|
||||
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
return mBackgroundColor;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(int backgroundColor) {
|
||||
mBackgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
public float getLineWidth() {
|
||||
return mLineWidth;
|
||||
}
|
||||
|
||||
public void setLineWidth(float width) {
|
||||
if (width < 0) {
|
||||
throw new IllegalArgumentException("width < 0");
|
||||
}
|
||||
mLineWidth = width;
|
||||
}
|
||||
}
|
||||
@@ -1,202 +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.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO support multiline
|
||||
|
||||
/**
|
||||
* Works like movable type.<br>
|
||||
* <br>
|
||||
* Only support single line now
|
||||
*/
|
||||
public final class MovableTextTexture extends SpriteTexture {
|
||||
|
||||
private final char[] mCharacters;
|
||||
private final float[] mWidths;
|
||||
private final float mHeight;
|
||||
private final float mMaxWidth;
|
||||
|
||||
private MovableTextTexture(Bitmap bitmap, int count, int[] rects, char[] characters,
|
||||
float[] widths, float height, float maxWidth) {
|
||||
super(bitmap, false, count, rects);
|
||||
mCharacters = characters;
|
||||
mWidths = widths;
|
||||
mHeight = height;
|
||||
mMaxWidth = maxWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a TextTexture to draw text
|
||||
*
|
||||
* @param typeface the typeface
|
||||
* @param size text size
|
||||
* @param characters all Characters
|
||||
* @return the TextTexture
|
||||
*/
|
||||
public static MovableTextTexture create(Typeface typeface, int size, int color, char[] characters) {
|
||||
Paint paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setTextSize(size);
|
||||
paint.setColor(color);
|
||||
paint.setTypeface(typeface);
|
||||
|
||||
Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
|
||||
int fixed = fmi.bottom;
|
||||
int height = fmi.bottom - fmi.top;
|
||||
|
||||
int length = characters.length;
|
||||
float[] widths = new float[length];
|
||||
paint.getTextWidths(characters, 0, length, widths);
|
||||
|
||||
// Calculate bitmap size
|
||||
float maxWidth = 0.0f;
|
||||
for (float f : widths) {
|
||||
maxWidth = Math.max(maxWidth, f);
|
||||
}
|
||||
int hCount = (int) Math.ceil(Math.sqrt(height / maxWidth * length));
|
||||
int vCount = (int) Math.ceil(Math.sqrt(maxWidth / height * length));
|
||||
if (hCount * (vCount - 1) > length) {
|
||||
vCount--;
|
||||
}
|
||||
if ((hCount - 1) * vCount > length) {
|
||||
hCount--;
|
||||
}
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap((int) Math.ceil(hCount * maxWidth),
|
||||
(int) Math.ceil(vCount * height), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.translate(0, height - fixed);
|
||||
|
||||
// Draw
|
||||
int[] rects = new int[length * 4];
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int offset = i * 4;
|
||||
rects[offset] = x;
|
||||
rects[offset + 1] = y;
|
||||
rects[offset + 2] = (int) widths[i];
|
||||
rects[offset + 3] = height;
|
||||
|
||||
canvas.drawText(characters, i, 1, x, y, paint);
|
||||
|
||||
if (i % hCount == hCount - 1) {
|
||||
// The end of row
|
||||
x = 0;
|
||||
y += height;
|
||||
} else {
|
||||
x += maxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return new MovableTextTexture(bitmap, length, rects, characters, widths, height, maxWidth);
|
||||
}
|
||||
|
||||
public int[] getTextIndexes(String text) {
|
||||
char[] characters = mCharacters;
|
||||
|
||||
int length = text.length();
|
||||
int[] indexes = new int[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = text.charAt(i);
|
||||
indexes[i] = Arrays.binarySearch(characters, ch);
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
public float getTextWidth(String text) {
|
||||
char[] characters = mCharacters;
|
||||
float[] widths = mWidths;
|
||||
float width = 0.0f;
|
||||
|
||||
for (int i = 0, n = text.length(); i < n; i++) {
|
||||
char ch = text.charAt(i);
|
||||
int index = Arrays.binarySearch(characters, ch);
|
||||
if (index >= 0) {
|
||||
width += widths[index];
|
||||
} else {
|
||||
width += mMaxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
public float getTextWidth(int[] indexes) {
|
||||
float[] widths = mWidths;
|
||||
float width = 0.0f;
|
||||
int length = indexes.length;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int index = indexes[i];
|
||||
if (index >= 0) {
|
||||
width += widths[index];
|
||||
} else {
|
||||
width += mMaxWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
public float getMaxWidth() {
|
||||
return mMaxWidth;
|
||||
}
|
||||
|
||||
public float getTextHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public void drawText(GLCanvas canvas, String text, int x, int y) {
|
||||
char[] characters = mCharacters;
|
||||
float[] widths = mWidths;
|
||||
|
||||
for (int i = 0, n = text.length(); i < n; i++) {
|
||||
char ch = text.charAt(i);
|
||||
int index = Arrays.binarySearch(characters, ch);
|
||||
if (index >= 0) {
|
||||
drawSprite(canvas, index, x, y);
|
||||
x += widths[index];
|
||||
} else {
|
||||
x += mMaxWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawText(GLCanvas canvas, int[] indexes, int x, int y) {
|
||||
float[] widths = mWidths;
|
||||
|
||||
for (int i = 0, n = indexes.length; i < n; i++) {
|
||||
int index = indexes[i];
|
||||
if (index >= 0) {
|
||||
drawSprite(canvas, index, x, y);
|
||||
x += widths[index];
|
||||
} else {
|
||||
x += mMaxWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.glview.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
|
||||
|
||||
// MultiLineTexture is a texture shows the content of a specified String.
|
||||
//
|
||||
// To create a MultiLineTexture, use the newInstance() method and specify
|
||||
// the String, the font size, and the color.
|
||||
class MultiLineTexture extends CanvasTexture {
|
||||
private final Layout mLayout;
|
||||
|
||||
private MultiLineTexture(Layout layout) {
|
||||
super(layout.getWidth(), layout.getHeight());
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
public static MultiLineTexture newInstance(
|
||||
String text, int maxWidth, float textSize, int color,
|
||||
Layout.Alignment alignment) {
|
||||
TextPaint paint = StringTexture.getDefaultPaint(textSize, color);
|
||||
Layout layout = new StaticLayout(text, 0, text.length(), paint,
|
||||
maxWidth, alignment, 1, 0, true, null, 0);
|
||||
|
||||
return new MultiLineTexture(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas, Bitmap backing) {
|
||||
mLayout.draw(canvas);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 300 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user