Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
238d769f27 | ||
|
|
1e648cc6b4 | ||
|
|
7ac20d59fc | ||
|
|
1d8ab51a22 | ||
|
|
cdfb3b74b3 | ||
|
|
6f2868f7c3 | ||
|
|
f3061fa497 | ||
|
|
468cce5f3d | ||
|
|
bfaba80d3b | ||
|
|
2ca4a2af86 | ||
|
|
7d0b245d3d | ||
|
|
96fcf69bdd | ||
|
|
63d95b1265 | ||
|
|
d173e83e01 | ||
|
|
54d11dea95 | ||
|
|
fbdab389c4 | ||
|
|
3dd3bad362 | ||
|
|
65a9a4b4f2 | ||
|
|
5f6cf5550d | ||
|
|
673dad4032 | ||
|
|
5e45ec9a9c |
No files matched your search
+10
-6
@@ -29,10 +29,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 33
|
||||
versionCode 180020
|
||||
versionName "1.8.3.0-beta02"
|
||||
versionCode 180021
|
||||
versionName "1.8.3.0"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -43,12 +43,16 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
sourceCompatibility JavaVersion.VERSION_19
|
||||
targetCompatibility JavaVersion.VERSION_19
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
jvmTarget = '19'
|
||||
freeCompilerArgs += [
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
|
||||
]
|
||||
}
|
||||
|
||||
lint {
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
android:required="true" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
project(ehviewer)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto=thin \
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -funroll-loops -flto=thin \
|
||||
-mllvm -polly \
|
||||
-mllvm -polly-run-dce \
|
||||
-mllvm -polly-run-inliner \
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <syscall.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
@@ -249,8 +250,8 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
return r;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToAddr(JNIEnv *env, jobject thiz, jint index) {
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToByteBuffer(JNIEnv *env, jobject thiz, jint index) {
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
@@ -259,17 +260,16 @@ Java_com_hippo_UriArchiveAccessor_extractToAddr(JNIEnv *env, jobject thiz, jint
|
||||
if (ret)
|
||||
return 0;
|
||||
long long size = archive_entry_size(ctx->entry);
|
||||
void *buffer = malloc(size + sizeof(long long));
|
||||
void *buffer = malloc(size);
|
||||
if (!buffer) {
|
||||
ctx->using = 0;
|
||||
LOGE("Allocate buffer for decompression failed:ENOMEM");
|
||||
return 0;
|
||||
}
|
||||
*(long long *) buffer = size;
|
||||
ret = archive_read_data(ctx->arc, buffer + sizeof(long long), size);
|
||||
ret = archive_read_data(ctx->arc, buffer, size);
|
||||
ctx->using = 0;
|
||||
if (ret == size)
|
||||
return (jlong) buffer;
|
||||
return (*env)->NewDirectByteBuffer(env, buffer, size);
|
||||
if (ret != size)
|
||||
LOGE("%s", "No enough data read, WTF?");
|
||||
if (ret < 0)
|
||||
@@ -354,4 +354,10 @@ Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint in
|
||||
archive_read_data_into_fd(ctx->arc, fd);
|
||||
ctx->using = 0;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_releaseByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) {
|
||||
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
free(addr);
|
||||
}
|
||||
+26
-202
@@ -20,58 +20,21 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/bitmap.h>
|
||||
#include <android/data_space.h>
|
||||
#include <android/imagedecoder.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
#include <jni.h>
|
||||
|
||||
#define TAG "ImageDecoder_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG ,__VA_ARGS__)
|
||||
|
||||
#include "ehviewer.h"
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
|
||||
|
||||
typedef struct {
|
||||
AImageDecoder *decoder;
|
||||
void *srcBuffer;
|
||||
size_t stride;
|
||||
int fd;
|
||||
} SOURCE;
|
||||
|
||||
typedef struct {
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
void *buffer;
|
||||
int bufferLen;
|
||||
bool isAnimated;
|
||||
bool alpha;
|
||||
int delay;
|
||||
SOURCE *src;
|
||||
} IMAGE;
|
||||
|
||||
static void recycleSource(SOURCE *src) {
|
||||
if (!src)
|
||||
return;
|
||||
AImageDecoder_delete(src->decoder);
|
||||
free(src->srcBuffer);
|
||||
free(src);
|
||||
}
|
||||
|
||||
static void recycle(IMAGE *image) {
|
||||
if (!image)
|
||||
return;
|
||||
recycleSource(image->src);
|
||||
free(image->buffer);
|
||||
free(image);
|
||||
}
|
||||
|
||||
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
@@ -159,138 +122,40 @@ bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
|
||||
return true;
|
||||
}
|
||||
|
||||
void decodeCommon(IMAGE *image, SOURCE *src) {
|
||||
image->src = src;
|
||||
AImageDecoder *decoder = src->decoder;
|
||||
image->isAnimated = AImageDecoder_isAnimated(decoder);
|
||||
AImageDecoder_setDataSpace(decoder, ADATASPACE_SRGB);
|
||||
AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888);
|
||||
const AImageDecoderHeaderInfo *headerInfo = AImageDecoder_getHeaderInfo(decoder);
|
||||
image->height = AImageDecoderHeaderInfo_getHeight(headerInfo);
|
||||
image->width = AImageDecoderHeaderInfo_getWidth(headerInfo);
|
||||
image->alpha =
|
||||
AImageDecoderHeaderInfo_getAlphaFlags(headerInfo) == ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
|
||||
src->stride = AImageDecoder_getMinimumStride(decoder);
|
||||
image->bufferLen = (int) (image->height * src->stride);
|
||||
image->buffer = malloc(image->bufferLen);
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_setInternallyHandleDisposePrevious(decoder, false);
|
||||
AImageDecoderFrameInfo *frameInfo = AImageDecoderFrameInfo_create();
|
||||
AImageDecoder_getFrameInfo(decoder, frameInfo);
|
||||
image->delay = (int) AImageDecoderFrameInfo_getDuration(frameInfo) / 1000000;
|
||||
AImageDecoderFrameInfo_delete(frameInfo);
|
||||
}
|
||||
AImageDecoder_decodeImage(decoder, image->buffer, src->stride, image->bufferLen);
|
||||
if (!image->isAnimated) {
|
||||
recycleSource(image->src);
|
||||
image->src = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
IMAGE *createFromFd(int fd) {
|
||||
AImageDecoder *decoder = NULL;
|
||||
if (AImageDecoder_createFromFd(fd, &decoder) != ANDROID_IMAGE_DECODER_SUCCESS)
|
||||
return NULL;
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
||||
src->decoder = decoder;
|
||||
src->fd = fd;
|
||||
decodeCommon(image, src);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *createFromAddr(void *addr, long long size) {
|
||||
AImageDecoder *decoder;
|
||||
AImageDecoder_createFromBuffer((const void *) addr + sizeof(long long), size, &decoder);
|
||||
IMAGE *image = calloc(1, sizeof(IMAGE));
|
||||
SOURCE *src = calloc(1, sizeof(SOURCE));
|
||||
src->decoder = decoder;
|
||||
src->srcBuffer = addr;
|
||||
decodeCommon(image, src);
|
||||
return image;
|
||||
}
|
||||
|
||||
IMAGE *create(int32_t width, int32_t height, const void *data) {
|
||||
IMAGE *plain = calloc(1, sizeof(IMAGE));
|
||||
void *buffer = NULL;
|
||||
size_t length = width * height * 4;
|
||||
|
||||
buffer = malloc(length);
|
||||
memcpy(buffer, data, length);
|
||||
|
||||
// plain->isAnimated = false; this is unnecessary since we use calloc
|
||||
plain->width = width;
|
||||
plain->height = height;
|
||||
plain->buffer = buffer;
|
||||
plain->bufferLen = (int) length;
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
void render(IMAGE *image, int src_x, int src_y,
|
||||
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
|
||||
int width, int height) {
|
||||
copy_pixels(image->buffer, image->width, image->height, src_x, src_y, dst, dst_w, dst_h, dst_x,
|
||||
dst_y, width, height);
|
||||
}
|
||||
|
||||
void advance(IMAGE *image) {
|
||||
if (image->isAnimated) {
|
||||
AImageDecoder_advanceFrame(image->src->decoder);
|
||||
if (AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
||||
image->bufferLen) == ANDROID_IMAGE_DECODER_FINISHED) {
|
||||
AImageDecoder_rewind(image->src->decoder);
|
||||
AImageDecoder_decodeImage(image->src->decoder, image->buffer, image->src->stride,
|
||||
image->bufferLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecode(JNIEnv *env, jclass clazz, jint fd) {
|
||||
IMAGE *image = createFromFd(fd);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeCreate(JNIEnv *env, jclass clazz, jobject bitmap) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
void *image = NULL;
|
||||
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
|
||||
image = create((int) info.width, (int) info.height, pixels);
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jlong ptr, jint src_x,
|
||||
Java_com_hippo_image_Image_nativeRender(JNIEnv *env, jclass clazz, jobject srcBitmap, jint src_x,
|
||||
jint src_y, jobject dst, jint dst_x, jint dst_y, jint width,
|
||||
jint height) {
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
AndroidBitmapInfo dstInfo;
|
||||
AndroidBitmapInfo srcInfo;
|
||||
void *srcPixels = NULL;
|
||||
void *dstPixels = NULL;
|
||||
|
||||
AndroidBitmap_getInfo(env, dst, &info);
|
||||
AndroidBitmap_lockPixels(env, dst, &pixels);
|
||||
AndroidBitmap_lockPixels(env, srcBitmap, &srcPixels);
|
||||
AndroidBitmap_lockPixels(env, dst, &dstPixels);
|
||||
AndroidBitmap_getInfo(env, srcBitmap, &srcInfo);
|
||||
AndroidBitmap_getInfo(env, dst, &dstInfo);
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, pixels, (int) info.width, (int) info.height, dst_x, dst_y,
|
||||
width,
|
||||
height);
|
||||
copy_pixels(srcPixels, srcInfo.width, srcInfo.height, src_x, src_y, dstPixels, dstInfo.width,
|
||||
dstInfo.height, dst_x, dst_y,
|
||||
width,
|
||||
height);
|
||||
|
||||
AndroidBitmap_unlockPixels(env, dst);
|
||||
AndroidBitmap_unlockPixels(env, srcBitmap);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jlong ptr, jboolean init,
|
||||
jint src_x, jint src_y, jint width, jint height) {
|
||||
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jobject bitmap, jboolean init,
|
||||
jint offset_x, jint offset_y, jint width, jint height) {
|
||||
if (width * height > IMAGE_TILE_MAX_SIZE)
|
||||
return;
|
||||
|
||||
render((IMAGE *) ptr, src_x, src_y, tile_buffer, width, height, 0, 0, width, height);
|
||||
|
||||
AndroidBitmapInfo info;
|
||||
void *pixels = NULL;
|
||||
AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
||||
AndroidBitmap_getInfo(env, bitmap, &info);
|
||||
copy_pixels(pixels, info.width, info.height, offset_x, offset_y, tile_buffer, width, height, 0, 0, width, height);
|
||||
AndroidBitmap_unlockPixels(env, bitmap);
|
||||
if (init) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
tile_buffer);
|
||||
@@ -299,44 +164,3 @@ Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jlong ptr,
|
||||
tile_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeAdvance(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
advance((IMAGE *) ptr);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetDelay(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
return ((IMAGE *) ptr)->delay;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_image_Image_nativeIsOpaque(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
return ((IMAGE *) ptr)->alpha;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_image_Image_nativeRecycle(JNIEnv *env, jclass clazz, jlong ptr) {
|
||||
recycle((IMAGE *) ptr);
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_image_Image_nativeDecodeAddr(JNIEnv *env, jclass clazz, jlong addr) {
|
||||
IMAGE *image = createFromAddr((void *) addr, *(long long *) addr);
|
||||
return (jlong) image;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetFormat(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->isAnimated;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetWidth(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->width;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_image_Image_nativeGetHeight(JNIEnv *env, jclass clazz, jlong native_ptr) {
|
||||
return ((IMAGE *) native_ptr)->height;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package com.hippo
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
val pfd by lazy { ctx.contentResolver.openFileDescriptor(uri, "r")!! }
|
||||
@@ -27,7 +28,9 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
}
|
||||
|
||||
private external fun openArchive(fd: Int, size: Long): Int
|
||||
external fun extractToAddr(index: Int): Long
|
||||
external fun extractToByteBuffer(index: Int): ByteBuffer?
|
||||
|
||||
external fun releaseByteBuffer(buffer: ByteBuffer)
|
||||
external fun extractToFd(index: Int, fd: Int)
|
||||
external fun getFilename(index: Int): String
|
||||
external fun needPassword(): Boolean
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -10,10 +11,12 @@ open class BaseDialogBuilder constructor(
|
||||
) : MaterialAlertDialogBuilder(context) {
|
||||
override fun create(): AlertDialog {
|
||||
return super.create().apply {
|
||||
window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +231,21 @@ public final class DiskLruCache implements Closeable {
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
|
||||
if (deleteDestination) {
|
||||
deleteIfExists(to);
|
||||
}
|
||||
if (!from.renameTo(to)) {
|
||||
throw new IOException();
|
||||
}
|
||||
} private final Callable<Void> cleanupCallable = new Callable<>() {
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
@@ -247,21 +262,6 @@ public final class DiskLruCache implements Closeable {
|
||||
}
|
||||
};
|
||||
|
||||
private static void deleteIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static void renameTo(File from, File to, boolean deleteDestination) throws IOException {
|
||||
if (deleteDestination) {
|
||||
deleteIfExists(to);
|
||||
}
|
||||
if (!from.renameTo(to)) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
private static String inputStreamToString(InputStream in) throws IOException {
|
||||
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
@@ -140,7 +139,7 @@ class StrictLineReader implements Closeable {
|
||||
for (int i = pos; i != end; ++i) {
|
||||
if (buf[i] == LF) {
|
||||
int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
|
||||
String res = new String(buf, pos, lineEnd - pos, charset.name());
|
||||
String res = new String(buf, pos, lineEnd - pos, charset);
|
||||
pos = i + 1;
|
||||
return res;
|
||||
}
|
||||
@@ -152,11 +151,7 @@ class StrictLineReader implements Closeable {
|
||||
@Override
|
||||
public String toString() {
|
||||
int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
|
||||
try {
|
||||
return new String(buf, 0, length, charset.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e); // Since we control the charset this will never happen.
|
||||
}
|
||||
return new String(buf, 0, length, charset);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import androidx.annotation.UiThread;
|
||||
|
||||
import com.hippo.beerbelly.SimpleDiskCache;
|
||||
import com.hippo.okhttp.ChromeRequestBuilder;
|
||||
import com.hippo.streampipe.InputStreamPipe;
|
||||
import com.hippo.streampipe.OutputStreamPipe;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -35,8 +34,6 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.hippo.drawable;
|
||||
|
||||
import android.graphics.drawable.AnimatedImageDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -25,7 +26,6 @@ import com.hippo.conaco.Conaco;
|
||||
import com.hippo.conaco.ConacoTask;
|
||||
import com.hippo.conaco.Unikery;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.image.ImageDrawable;
|
||||
import com.hippo.widget.ObservedTextView;
|
||||
|
||||
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
|
||||
@@ -37,6 +37,8 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
||||
private int mTaskId = Unikery.INVALID_ID;
|
||||
private String mUrl;
|
||||
|
||||
private ImageBitmap imageBitmap;
|
||||
|
||||
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
|
||||
mTextView = textView;
|
||||
mTextView.setOnWindowAttachListener(this);
|
||||
@@ -62,11 +64,11 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
||||
}
|
||||
|
||||
private void clearDrawable() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable instanceof ImageDrawable) {
|
||||
((ImageDrawable) drawable).recycle();
|
||||
}
|
||||
setDrawable(null);
|
||||
if (imageBitmap != null) {
|
||||
imageBitmap.release();
|
||||
imageBitmap = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,9 +125,9 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
||||
|
||||
@Override
|
||||
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
|
||||
ImageDrawable drawable;
|
||||
Drawable drawable;
|
||||
try {
|
||||
drawable = new ImageDrawable(value);
|
||||
drawable = value.getDrawable();
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "The ImageBitmap is recycled", e);
|
||||
return false;
|
||||
@@ -134,7 +136,9 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
|
||||
clearDrawable();
|
||||
|
||||
setDrawable(drawable);
|
||||
drawable.start();
|
||||
imageBitmap = value;
|
||||
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable)
|
||||
animatedImageDrawable.start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,7 @@ public final class LayoutManagerUtils {
|
||||
RecyclerView.LayoutManager layoutManager, Context context, int position,
|
||||
int millisecondsPerInch) {
|
||||
SimpleSmoothScroller smoothScroller;
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
||||
if (layoutManager instanceof final LinearLayoutManager linearLayoutManager) {
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
@@ -73,8 +72,7 @@ public final class LayoutManagerUtils {
|
||||
}
|
||||
};
|
||||
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
|
||||
} else if (layoutManager instanceof final StaggeredGridLayoutManager staggeredGridLayoutManager) {
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
|
||||
@@ -62,7 +62,6 @@ import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.EhActivity;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.network.StatusCodeException;
|
||||
import com.hippo.scene.SceneApplication;
|
||||
@@ -187,10 +186,9 @@ public class EhApplication extends SceneApplication {
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager trustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||
}
|
||||
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), trustManager);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -492,9 +490,6 @@ public class EhApplication extends SceneApplication {
|
||||
Log.i(TAG, "Native memory: " + FileUtils.humanReadableByteCount(
|
||||
Debug.getNativeHeapAllocatedSize(), false));
|
||||
}
|
||||
if (DEBUG_PRINT_IMAGE_COUNT) {
|
||||
Log.i(TAG, "Image count: " + Image.getImageCount());
|
||||
}
|
||||
SimpleHandler.getInstance().postDelayed(this, DEBUG_PRINT_INTERVAL);
|
||||
}
|
||||
}.run();
|
||||
|
||||
@@ -495,13 +495,15 @@ public class EhDB {
|
||||
try {
|
||||
List<DownloadLabel> downloadLabelList = oldRoomDatabase.downloadLabelDao().list();
|
||||
manager.addDownloadLabel(downloadLabelList);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Downloads
|
||||
try {
|
||||
List<DownloadInfo> downloadInfoList = oldRoomDatabase.downloadsDao().list();
|
||||
manager.addDownload(downloadInfoList, false);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Download dirname
|
||||
try {
|
||||
@@ -509,13 +511,15 @@ public class EhDB {
|
||||
for (DownloadDirname dirname : downloadDirnameList) {
|
||||
putDownloadDirname(dirname.getGid(), dirname.getDirname());
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// History
|
||||
try {
|
||||
List<HistoryInfo> historyInfoList = oldRoomDatabase.historyDao().list();
|
||||
putHistoryInfo(historyInfoList);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// QuickSearch
|
||||
try {
|
||||
@@ -537,7 +541,8 @@ public class EhDB {
|
||||
importList.add(quickSearch);
|
||||
}
|
||||
importQuickSearch(importList);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// LocalFavorites
|
||||
try {
|
||||
@@ -545,7 +550,8 @@ public class EhDB {
|
||||
for (LocalFavoriteInfo info : localFavoriteInfoList) {
|
||||
putLocalFavorites(info);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// Filter
|
||||
try {
|
||||
@@ -556,7 +562,8 @@ public class EhDB {
|
||||
addFilter(filter);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -36,7 +36,7 @@ public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
|
||||
try {
|
||||
isPipe.obtain();
|
||||
FileInputStream is = (FileInputStream) isPipe.open();
|
||||
return ImageBitmap.decode(is.getFD());
|
||||
return ImageBitmap.decode(is);
|
||||
} catch (OutOfMemoryError | IOException e) {
|
||||
return null;
|
||||
} finally {
|
||||
|
||||
@@ -100,16 +100,13 @@ public class Settings {
|
||||
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts_2";
|
||||
public static final String KEY_DOMAIN_FRONTING = "domain_fronting";
|
||||
public static final String KEY_APP_LANGUAGE = "app_language";
|
||||
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
|
||||
private static final String TAG = Settings.class.getSimpleName();
|
||||
private static final String KEY_VERSION_CODE = "version_code";
|
||||
private static final int DEFAULT_VERSION_CODE = 0;
|
||||
private static final String KEY_DISPLAY_NAME = "display_name";
|
||||
private static final String DEFAULT_DISPLAY_NAME = null;
|
||||
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
|
||||
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
|
||||
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||
private static final String KEY_AVATAR = "avatar";
|
||||
private static final String DEFAULT_AVATAR = null;
|
||||
private static final String KEY_SHOW_WARNING = "show_warning";
|
||||
@@ -131,8 +128,6 @@ public class Settings {
|
||||
private static final int DEFAULT_DETAIL_SIZE = 0;
|
||||
private static final int DEFAULT_THUMB_SIZE = 1;
|
||||
private static final int DEFAULT_THUMB_RESOLUTION = 0;
|
||||
private static final String KEY_FIX_THUMB_URL = "fix_thumb_url";
|
||||
private static final boolean DEFAULT_FIX_THUMB_URL = false;
|
||||
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
|
||||
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
|
||||
private static final String KEY_SHOW_GALLERY_PAGES = "show_gallery_pages";
|
||||
@@ -282,11 +277,9 @@ public class Settings {
|
||||
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
|
||||
private static final String KEY_REQUEST_NEWS = "request_news";
|
||||
private static final boolean DEFAULT_REQUEST_NEWS = true;
|
||||
|
||||
|
||||
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
|
||||
|
||||
|
||||
public static boolean LIST_THUMB_SIZE_INITED = false;
|
||||
private static int LIST_THUMB_SIZE = 40;
|
||||
private static Context sContext;
|
||||
private static SharedPreferences sSettingsPre;
|
||||
private static EhConfig sEhConfig;
|
||||
@@ -564,10 +557,6 @@ public class Settings {
|
||||
return getIntFromStr(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION);
|
||||
}
|
||||
|
||||
public static boolean getFixThumbUrl() {
|
||||
return getBoolean(KEY_FIX_THUMB_URL, DEFAULT_FIX_THUMB_URL);
|
||||
}
|
||||
|
||||
public static boolean getRequestNews() {
|
||||
return getBoolean(KEY_REQUEST_NEWS, DEFAULT_REQUEST_NEWS);
|
||||
}
|
||||
@@ -979,11 +968,11 @@ public class Settings {
|
||||
boolean writeFile = false;
|
||||
String userID = getString(KEY_USER_ID, null);
|
||||
File file = AppConfig.getFileInExternalAppDir(FILENAME_USER_ID);
|
||||
if (null == userID || !isValidUserID(userID)) {
|
||||
if (!isValidUserID(userID)) {
|
||||
writeXml = true;
|
||||
// Get use ID from out sd card file
|
||||
userID = FileUtils.read(file);
|
||||
if (null == userID || !isValidUserID(userID)) {
|
||||
if (!isValidUserID(userID)) {
|
||||
writeFile = true;
|
||||
userID = generateUserID();
|
||||
}
|
||||
@@ -1067,6 +1056,7 @@ public class Settings {
|
||||
public static void putSecurity(boolean value) {
|
||||
putBoolean(KEY_SECURITY, value);
|
||||
}
|
||||
|
||||
public static int getSecurityDelay() {
|
||||
return getInt(KEY_SECURITY_DELAY, 0);
|
||||
}
|
||||
|
||||
@@ -245,12 +245,6 @@ public class EhEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Settings.getFixThumbUrl()) {
|
||||
for (GalleryInfo info : list) {
|
||||
info.thumb = EhUrl.getFixedPreviewThumbUrl(info.thumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static GalleryListParser.Result getGalleryList(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
|
||||
|
||||
@@ -21,11 +21,6 @@ import androidx.annotation.NonNull;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.network.UrlBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class EhUrl {
|
||||
|
||||
public static final int SITE_E = 0;
|
||||
@@ -59,8 +54,6 @@ public class EhUrl {
|
||||
public static final String ORIGIN_EX = REFERER_EX;
|
||||
public static final String REFERER_E = "https://" + DOMAIN_E;
|
||||
public static final String ORIGIN_E = REFERER_E;
|
||||
private static final String URL_PREFIX_THUMB_E = "https://ehgt.org/";
|
||||
private static final String URL_PREFIX_THUMB_EX = "https://exhentai.org/t/";
|
||||
|
||||
public static String getGalleryDetailUrl(long gid, String token) {
|
||||
return getGalleryDetailUrl(gid, token, 0, false);
|
||||
@@ -200,44 +193,4 @@ public class EhUrl {
|
||||
return URL_WATCHED_EX;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getThumbUrlPrefix() {
|
||||
switch (Settings.getGallerySite()) {
|
||||
default:
|
||||
//case SITE_E:
|
||||
return URL_PREFIX_THUMB_E;
|
||||
//case SITE_EX:
|
||||
// return URL_PREFIX_THUMB_EX;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFixedPreviewThumbUrl(String originUrl) {
|
||||
HttpUrl url = HttpUrl.parse(originUrl);
|
||||
if (url == null) return originUrl;
|
||||
List<String> pathSegments = url.pathSegments();
|
||||
if (pathSegments.size() < 3) return originUrl;
|
||||
|
||||
ListIterator<String> iterator = pathSegments.listIterator(pathSegments.size());
|
||||
// The last segments, like
|
||||
// 317a1a254cd9c3269e71b2aa2671fe8d28c91097-260198-640-480-png_250.jpg
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String lastSegment = iterator.previous();
|
||||
// The second last segments, like
|
||||
// 7a
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String secondLastSegment = iterator.previous();
|
||||
// The third last segments, like
|
||||
// 31
|
||||
if (!iterator.hasPrevious()) return originUrl;
|
||||
String thirdLastSegment = iterator.previous();
|
||||
// Check path segments
|
||||
if (lastSegment != null && secondLastSegment != null
|
||||
&& thirdLastSegment != null
|
||||
&& lastSegment.startsWith(thirdLastSegment)
|
||||
&& lastSegment.startsWith(secondLastSegment, thirdLastSegment.length())) {
|
||||
return getThumbUrlPrefix() + thirdLastSegment + "/" + secondLastSegment + "/" + lastSegment;
|
||||
} else {
|
||||
return originUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,17 @@
|
||||
|
||||
package com.hippo.ehviewer.client.data;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.network.UrlBuilder;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FavListUrlBuilder implements Parcelable {
|
||||
|
||||
@@ -105,17 +106,21 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
ub.addQuery("favcat", "all");
|
||||
}
|
||||
if (!TextUtils.isEmpty(mKeyword)) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"));
|
||||
// Name
|
||||
ub.addQuery("sn", "on");
|
||||
// Tags
|
||||
ub.addQuery("st", "on");
|
||||
// Note
|
||||
ub.addQuery("sf", "on");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(TAG, "Can't URLEncoder.encode " + mKeyword);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8));
|
||||
} else {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, StandardCharsets.UTF_8.displayName()));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
// Name
|
||||
ub.addQuery("sn", "on");
|
||||
// Tags
|
||||
ub.addQuery("st", "on");
|
||||
// Note
|
||||
ub.addQuery("sf", "on");
|
||||
}
|
||||
if (mPrev != null && !mPrev.isEmpty()) {
|
||||
ub.addQuery("prev", mPrev);
|
||||
|
||||
@@ -19,8 +19,6 @@ package com.hippo.ehviewer.client.parser;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryComment;
|
||||
import com.hippo.ehviewer.client.data.GalleryCommentList;
|
||||
@@ -646,9 +644,6 @@ public class GalleryDetailParser {
|
||||
String pageUrl = element.attr("href");
|
||||
element = element.child(0);
|
||||
String imageUrl = element.attr("src");
|
||||
if (Settings.getFixThumbUrl()) {
|
||||
imageUrl = EhUrl.getFixedPreviewThumbUrl(imageUrl);
|
||||
}
|
||||
int index = Integer.parseInt(element.attr("alt")) - 1;
|
||||
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
||||
}
|
||||
@@ -674,9 +669,6 @@ public class GalleryDetailParser {
|
||||
}
|
||||
String imageUrl = ParserUtils.trim(m.group(3));
|
||||
String pageUrl = ParserUtils.trim(m.group(1));
|
||||
if (Settings.getFixThumbUrl()) {
|
||||
imageUrl = EhUrl.getFixedPreviewThumbUrl(imageUrl);
|
||||
}
|
||||
largePreviewSet.addItem(index, imageUrl, pageUrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -309,8 +309,10 @@ public class GalleryListParser {
|
||||
assert next != null;
|
||||
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
|
||||
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
|
||||
if (matcherPrev.find()) result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
|
||||
if (matcherNext.find()) result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
|
||||
if (matcherPrev.find())
|
||||
result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
|
||||
if (matcherNext.find())
|
||||
result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.hippo.ehviewer.client.parser;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
@@ -27,6 +28,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class GalleryListUrlParser {
|
||||
|
||||
@@ -91,10 +93,15 @@ public final class GalleryListUrlParser {
|
||||
uploader = path.substring(prefixLength, index);
|
||||
}
|
||||
|
||||
try {
|
||||
uploader = URLDecoder.decode(uploader, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
uploader = URLDecoder.decode(uploader, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
try {
|
||||
uploader = URLDecoder.decode(uploader, StandardCharsets.UTF_8.displayName());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(uploader)) {
|
||||
@@ -120,10 +127,15 @@ public final class GalleryListUrlParser {
|
||||
tag = path.substring(prefixLength, index);
|
||||
}
|
||||
|
||||
try {
|
||||
tag = URLDecoder.decode(tag, "utf-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
tag = URLDecoder.decode(tag, StandardCharsets.UTF_8);
|
||||
} else {
|
||||
try {
|
||||
tag = URLDecoder.decode(tag, StandardCharsets.UTF_8.displayName());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(tag)) {
|
||||
|
||||
@@ -4,5 +4,6 @@ import java.util.List;
|
||||
|
||||
public interface BasicDao<T> {
|
||||
List<T> list();
|
||||
|
||||
long insert(T t);
|
||||
}
|
||||
@@ -58,10 +58,9 @@ public class Filter {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Filter)) {
|
||||
if (!(o instanceof Filter filter)) {
|
||||
return false;
|
||||
}
|
||||
Filter filter = (Filter) o;
|
||||
return filter.mode == mode && ObjectUtils.equal(filter.text, text);
|
||||
}
|
||||
}
|
||||
@@ -498,6 +498,7 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
|
||||
}
|
||||
mNotifyManager.notify(mId, mBuilder.build())
|
||||
}
|
||||
|
||||
OPS_CANCEL -> mNotifyManager.cancel(mId)
|
||||
OPS_START_FOREGROUND -> mService.startForeground(mId, mBuilder.build())
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.hippo.ehviewer.gallery;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -35,8 +36,10 @@ import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.thread.PVLock;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -51,15 +54,14 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
public static PVLock pv = new PVLock(0);
|
||||
private final UriArchiveAccessor archiveAccessor;
|
||||
private final Stack<Integer> requests = new Stack<>();
|
||||
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
|
||||
private Thread archiveThread;
|
||||
private Thread[] decodeThread = new Thread[] {
|
||||
private final LinkedHashMap<Integer, ByteBuffer> streams = new LinkedHashMap<>();
|
||||
private final Thread[] decodeThread = new Thread[]{
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask()),
|
||||
new Thread(new DecodeTask())
|
||||
};
|
||||
|
||||
private Thread archiveThread;
|
||||
private volatile int size = STATE_WAIT;
|
||||
private String error;
|
||||
|
||||
@@ -161,7 +163,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
int fd;
|
||||
FileOutputStream stream;
|
||||
try {
|
||||
stream = (FileOutputStream)file.openOutputStream();
|
||||
stream = (FileOutputStream) file.openOutputStream();
|
||||
fd = Native.getFd(stream.getFD());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@@ -187,7 +189,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
}
|
||||
|
||||
private class ArchiveHostTask implements Runnable {
|
||||
public final Thread[] archiveThreads = new Thread[] {
|
||||
public final Thread[] archiveThreads = new Thread[]{
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask()),
|
||||
new Thread(new ArchiveTask()),
|
||||
@@ -298,10 +300,10 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
}
|
||||
}
|
||||
|
||||
long addr = archiveAccessor.extractToAddr(index);
|
||||
ByteBuffer buffer = archiveAccessor.extractToByteBuffer(index);
|
||||
|
||||
synchronized (streams) {
|
||||
streams.put(index, addr);
|
||||
streams.put(index, buffer);
|
||||
streams.notify();
|
||||
}
|
||||
}
|
||||
@@ -313,7 +315,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
public void run() {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
int index;
|
||||
Long addr;
|
||||
ByteBuffer buffer;
|
||||
synchronized (streams) {
|
||||
if (streams.isEmpty()) {
|
||||
try {
|
||||
@@ -324,14 +326,20 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
continue;
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, Long> entry = iterator.next();
|
||||
Iterator<Map.Entry<Integer, ByteBuffer>> iterator = streams.entrySet().iterator();
|
||||
Map.Entry<Integer, ByteBuffer> entry = iterator.next();
|
||||
iterator.remove();
|
||||
index = entry.getKey();
|
||||
addr = entry.getValue();
|
||||
buffer = entry.getValue();
|
||||
}
|
||||
|
||||
Image image = Image.decodeAddr(addr, true);
|
||||
Image image = null;
|
||||
if (buffer != null) {
|
||||
image = Image.decode(buffer, false, () -> {
|
||||
archiveAccessor.releaseByteBuffer(buffer);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
|
||||
@@ -259,7 +259,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = files[index].openInputStream();
|
||||
Image image = Image.decode(((FileInputStream) is).getFD(), true);
|
||||
Image image = Image.decode((FileInputStream) is, false);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.preference
|
||||
|
||||
import android.content.*
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.text.Html
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@@ -28,10 +29,9 @@ import com.hippo.ehviewer.ui.SettingsActivity
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
import com.hippo.preference.MessagePreference
|
||||
import com.hippo.util.addTextToClipboard
|
||||
import com.hippo.util.getClipboardManager
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import java.util.*
|
||||
import java.util.LinkedList
|
||||
|
||||
class AccountPreference @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
|
||||
@@ -1678,9 +1678,7 @@ public final class SpiderQueen implements Runnable {
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
image = Image.decode(((FileInputStream) is).getFD(), true);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
image = Image.decode(((FileInputStream) is), false);
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
|
||||
@@ -303,16 +303,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
break;
|
||||
}
|
||||
super.attachBaseContext(newBase);
|
||||
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> {
|
||||
if (result && mSavingPage != -1) {
|
||||
saveImage(mSavingPage);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSavingPage = -1;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"WrongConstant"})
|
||||
@@ -404,26 +395,25 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
if (mGalleryView == null) {
|
||||
return false;
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_SCROLL:
|
||||
float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 300;
|
||||
if (scroll < 0.0f) {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
|
||||
mGalleryView.pageRight();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
|
||||
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
|
||||
}
|
||||
} else {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageRight();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
|
||||
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
|
||||
}
|
||||
if (event.getAction() == MotionEvent.ACTION_SCROLL) {
|
||||
float scroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) * 300;
|
||||
if (scroll < 0.0f) {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
|
||||
mGalleryView.pageRight();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
|
||||
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
|
||||
}
|
||||
} else {
|
||||
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
|
||||
mGalleryView.pageRight();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_LEFT_TO_RIGHT) {
|
||||
mGalleryView.pageLeft();
|
||||
} else if (mLayoutMode == GalleryView.LAYOUT_TOP_TO_BOTTOM) {
|
||||
mGalleryView.onScroll(0, -scroll, 0, -scroll, 0, -scroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
@@ -521,7 +511,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
|
||||
mSeekBar = null;
|
||||
|
||||
SimpleHandler.getInstance().removeCallbacks(mHideSliderRunnable);
|
||||
}
|
||||
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.RequestPermission(),
|
||||
result -> {
|
||||
if (result && mSavingPage != -1) {
|
||||
saveImage(mSavingPage);
|
||||
} else {
|
||||
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSavingPage = -1;
|
||||
});
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -31,7 +32,6 @@ import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.util.addTextToClipboard
|
||||
import com.hippo.util.getClipboardManager
|
||||
|
||||
class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment() {
|
||||
private var mKeys: ArrayList<String> = arrayListOf()
|
||||
@@ -47,9 +47,11 @@ class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment(
|
||||
parseDetail()
|
||||
val view = inflater.inflate(R.layout.scene_gallery_info, container, false)
|
||||
dialog?.window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
}
|
||||
val recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView
|
||||
val adapter = InfoAdapter()
|
||||
|
||||
@@ -477,8 +477,7 @@ public final class MainActivity extends StageActivity
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
public void createDrawerView(SceneFragment scene) {
|
||||
if (scene instanceof BaseScene && mRightDrawer != null && mDrawerLayout != null) {
|
||||
BaseScene baseScene = (BaseScene) scene;
|
||||
if (scene instanceof BaseScene baseScene && mRightDrawer != null && mDrawerLayout != null) {
|
||||
mRightDrawer.removeAllViews();
|
||||
View drawerView = baseScene.createDrawerView(
|
||||
baseScene.getLayoutInflater(), mRightDrawer, null);
|
||||
@@ -495,8 +494,7 @@ public final class MainActivity extends StageActivity
|
||||
public void onSceneViewDestroyed(SceneFragment scene) {
|
||||
super.onSceneViewDestroyed(scene);
|
||||
|
||||
if (scene instanceof BaseScene) {
|
||||
BaseScene baseScene = (BaseScene) scene;
|
||||
if (scene instanceof BaseScene baseScene) {
|
||||
baseScene.destroyDrawerView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,21 +18,29 @@ package com.hippo.ehviewer.ui
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.ui.fragment.SettingsFragment
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
|
||||
class SettingsActivity : EhActivity() {
|
||||
private var mFab: FloatingActionButton? = null
|
||||
private var mContentText: TextView? = null
|
||||
private var mAppbarLayout: AppBarLayout? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_preference)
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
val bar = supportActionBar
|
||||
bar?.setDisplayHomeAsUpEnabled(true)
|
||||
mContentText = findViewById(R.id.tip)
|
||||
mFab = findViewById(R.id.fab)
|
||||
mAppbarLayout = findViewById(R.id.appbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
|
||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
|
||||
if (savedInstanceState == null) {
|
||||
@@ -62,4 +70,16 @@ class SettingsActivity : EhActivity() {
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
fun getMFab(): FloatingActionButton {
|
||||
return mFab!!
|
||||
}
|
||||
|
||||
fun getMContentText(): TextView {
|
||||
return mContentText!!
|
||||
}
|
||||
|
||||
fun resetAppbarLiftStatus() {
|
||||
mAppbarLayout?.setExpanded(true)
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
@@ -59,7 +58,6 @@ import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@@ -71,7 +69,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
private static final String KEY_EXPORT_DATA = "export_data";
|
||||
private static final String KEY_OPEN_BY_DEFAULT = "open_by_default";
|
||||
private static final String KEY_BACKUP_FAVORITE = "backup_favorite";
|
||||
|
||||
ActivityResultLauncher<String> exportLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.CreateDocument(),
|
||||
uri -> {
|
||||
@@ -227,6 +224,8 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
}
|
||||
}
|
||||
});
|
||||
private int favTotal;
|
||||
private int favIndex;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
@@ -238,8 +237,8 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
appLanguage.setVisible(false);
|
||||
Preference importData = findPreference(KEY_IMPORT_DATA);
|
||||
Preference exportData = findPreference(KEY_EXPORT_DATA);
|
||||
Preference openByDefault = findPreference(KEY_OPEN_BY_DEFAULT);
|
||||
Preference backupFavorite = findPreference(KEY_BACKUP_FAVORITE);
|
||||
Preference openByDefault = findPreference(KEY_OPEN_BY_DEFAULT);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
openByDefault.setVisible(false);
|
||||
@@ -282,6 +281,14 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
return true;
|
||||
} else if (KEY_BACKUP_FAVORITE.equals(key)) {
|
||||
try {
|
||||
backupFavorite();
|
||||
} catch (Exception e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
return true;
|
||||
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
|
||||
try {
|
||||
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
|
||||
@@ -293,26 +300,15 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
startActivity(intent);
|
||||
}
|
||||
return true;
|
||||
} else if (KEY_BACKUP_FAVORITE.equals(key)) {
|
||||
try {
|
||||
showTip(R.string.backup_favorite_start, BaseScene.LENGTH_SHORT);
|
||||
backupFavorite();
|
||||
} catch (Exception e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void backupFavorite() {
|
||||
AtomicInteger page = new AtomicInteger(0);
|
||||
Context context = requireContext();
|
||||
Activity activity = requireActivity();
|
||||
EhClient mClient = EhApplication.getEhClient(context);
|
||||
EhClient mClient = EhApplication.getEhClient(requireContext());
|
||||
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
|
||||
// favListUrlBuilder.setNext(page.get());
|
||||
favTotal = 0;
|
||||
favIndex = 1;
|
||||
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||
@@ -320,31 +316,53 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
@Override
|
||||
public void onSuccess(Object result) {
|
||||
try {
|
||||
FavoritesParser.Result result1 = (FavoritesParser.Result) result;
|
||||
EhDB.putLocalFavorites(result1.galleryInfoList);
|
||||
Log.d("LocalFavorites", "now backup page " + page.get());
|
||||
if (!result1.galleryInfoList.isEmpty()) {
|
||||
// favListUrlBuilder.setNext(page.incrementAndGet());
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
FavoritesParser.Result favResult = (FavoritesParser.Result) result;
|
||||
if (favResult.galleryInfoList.isEmpty()) {
|
||||
showTip(R.string.settings_advanced_backup_favorite_nothing, BaseScene.LENGTH_SHORT);
|
||||
} else {
|
||||
showTip(R.string.backup_favorite_success, BaseScene.LENGTH_SHORT);
|
||||
if (favTotal == 0 && favResult.countArray != null) {
|
||||
int totalFav = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
totalFav = totalFav + favResult.countArray[i];
|
||||
}
|
||||
favTotal = (int) Math.ceil((double) totalFav / favResult.galleryInfoList.size());
|
||||
}
|
||||
|
||||
String status = "(" + favIndex + "/" + favTotal + ")";
|
||||
showTip(GetText.getString(R.string.settings_advanced_backup_favorite_start, status), BaseScene.LENGTH_SHORT);
|
||||
Log.d("LocalFavorites", "now backup page " + status);
|
||||
EhDB.putLocalFavorites(favResult.galleryInfoList);
|
||||
|
||||
if (favResult.nextPage != null) {
|
||||
try {
|
||||
Thread.sleep(Settings.getDownloadDelay());
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
favIndex++;
|
||||
favListUrlBuilder.setNext(favResult.nextPage);
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
} else {
|
||||
showTip(R.string.settings_advanced_backup_favorite_success, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
showTip(R.string.backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
showTip(R.string.settings_advanced_backup_favorite_failed, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
});
|
||||
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.hippo.ehviewer.ui.SettingsActivity;
|
||||
|
||||
public class BaseFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
setTitle(getFragmentTitle());
|
||||
}
|
||||
|
||||
@StringRes
|
||||
public int getFragmentTitle() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void setTitle(@StringRes int string) {
|
||||
requireActivity().setTitle(string);
|
||||
}
|
||||
|
||||
public void showTip(@StringRes int id, int length) {
|
||||
((SettingsActivity) requireActivity()).showTip(getString(id), length);
|
||||
}
|
||||
|
||||
public void showTip(CharSequence message, int length) {
|
||||
((SettingsActivity) requireActivity()).showTip(message, length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.fragment
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.hippo.ehviewer.ui.SettingsActivity
|
||||
|
||||
abstract class BaseFragment : Fragment() {
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setTitle(getFragmentTitle())
|
||||
(requireActivity() as SettingsActivity).resetAppbarLiftStatus()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
hideFabAndClearTip()
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
abstract fun getFragmentTitle(): Int
|
||||
|
||||
private fun setTitle(@StringRes string: Int) {
|
||||
requireActivity().setTitle(string)
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, length: Int) {
|
||||
(requireActivity() as SettingsActivity).showTip(getString(id), length)
|
||||
}
|
||||
|
||||
fun showTip(message: CharSequence?, length: Int) {
|
||||
(requireActivity() as SettingsActivity).showTip(message, length)
|
||||
}
|
||||
|
||||
fun getTipView(@StringRes res: Int? = null): TextView {
|
||||
return (requireActivity() as SettingsActivity).getMContentText().apply {
|
||||
visibility = View.VISIBLE
|
||||
res?.let { text = getString(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun getFabViewAndShow(): FloatingActionButton {
|
||||
return (requireActivity() as SettingsActivity).getMFab().apply { visibility = View.VISIBLE }
|
||||
}
|
||||
|
||||
private fun hideFabAndClearTip() {
|
||||
getFabViewAndShow().apply {
|
||||
visibility = View.GONE
|
||||
setOnClickListener(null)
|
||||
}
|
||||
getTipView().apply {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,12 @@ package com.hippo.ehviewer.ui.fragment
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
@@ -31,10 +36,8 @@ import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.checkbox.MaterialCheckBox
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhFilter
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
@@ -66,26 +69,23 @@ class FilterFragment : BaseFragment() {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.activity_filter, container, false)
|
||||
val recyclerView: RecyclerView =
|
||||
ViewUtils.`$$`(view, R.id.recycler_view) as EasyRecyclerView
|
||||
val tip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
): View {
|
||||
val recyclerView =
|
||||
inflater.inflate(R.layout.rv_layout, container, false) as RecyclerView
|
||||
val tip = getTipView(R.string.filter)
|
||||
mViewTransition = ViewTransition(recyclerView, tip)
|
||||
val fab = view.findViewById<FloatingActionButton>(R.id.fab)
|
||||
val fab = getFabViewAndShow()
|
||||
val drawable = ContextCompat.getDrawable(requireActivity(), R.drawable.big_filter)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
drawable?.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
tip.setCompoundDrawables(null, drawable, null, null)
|
||||
mAdapter.setHasStableIds(true)
|
||||
recyclerView.adapter = mAdapter
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.clipChildren = false
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
recyclerView.setHasFixedSize(true)
|
||||
val defaultItemAnimator = recyclerView.itemAnimator as DefaultItemAnimator?
|
||||
defaultItemAnimator?.supportsChangeAnimations = false
|
||||
fab.setOnClickListener { showAddFilterDialog() }
|
||||
return view
|
||||
return recyclerView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -106,9 +106,9 @@ class FilterFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mViewTransition = null
|
||||
requireActivity().removeMenuProvider(mMenuProvider)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun showTipDialog() {
|
||||
|
||||
@@ -1,283 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.Hosts;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class HostsFragment extends BaseFragment
|
||||
implements View.OnClickListener {
|
||||
|
||||
private static final String DIALOG_TAG_ADD_HOST = AddHostDialogFragment.class.getName();
|
||||
private static final String DIALOG_TAG_EDIT_HOST = EditHostDialogFragment.class.getName();
|
||||
|
||||
private static final String KEY_HOST = "com.hippo.ehviewer.ui.fragment.HostsFragment.HOST";
|
||||
private static final String KEY_IP = "com.hippo.ehviewer.ui.fragment.HostsFragment.IP";
|
||||
|
||||
private Hosts hosts;
|
||||
private List<Pair<String, String>> data;
|
||||
|
||||
private ViewTransition mViewTransition;
|
||||
private HostsAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
hosts = EhApplication.getHosts(requireContext());
|
||||
data = hosts.getAll();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.activity_hosts, container, false);
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recycler_view);
|
||||
TextView tip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
mViewTransition = new ViewTransition(recyclerView, tip);
|
||||
FloatingActionButton fab = view.findViewById(R.id.fab);
|
||||
|
||||
adapter = new HostsAdapter();
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false));
|
||||
recyclerView.setHasFixedSize(true);
|
||||
|
||||
fab.setOnClickListener(this);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
updateView(false);
|
||||
}
|
||||
|
||||
public boolean onItemClick(int position) {
|
||||
Pair<String, String> pair = data.get(position);
|
||||
Bundle args = new Bundle();
|
||||
args.putString(KEY_HOST, pair.first);
|
||||
args.putString(KEY_IP, pair.second);
|
||||
|
||||
DialogFragment fragment = new EditHostDialogFragment();
|
||||
fragment.setArguments(args);
|
||||
fragment.show(getChildFragmentManager(), DIALOG_TAG_EDIT_HOST);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
new AddHostDialogFragment().show(getChildFragmentManager(), DIALOG_TAG_ADD_HOST);
|
||||
}
|
||||
|
||||
private void updateView(boolean animation) {
|
||||
if (null == mViewTransition) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = hosts.getAll();
|
||||
if (data.isEmpty()) {
|
||||
mViewTransition.showView(1, animation);
|
||||
} else {
|
||||
mViewTransition.showView(0, animation);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFragmentTitle() {
|
||||
return R.string.hosts;
|
||||
}
|
||||
|
||||
public abstract static class HostDialogFragment extends DialogFragment {
|
||||
|
||||
private HostsFragment hostsFragment;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
hostsFragment = (HostsFragment) getParentFragment();
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_hosts, null, false);
|
||||
TextView host = view.findViewById(R.id.host);
|
||||
TextView ip = view.findViewById(R.id.ip);
|
||||
|
||||
Bundle arguments = getArguments();
|
||||
if (savedInstanceState == null && arguments != null) {
|
||||
host.setText(arguments.getString(KEY_HOST));
|
||||
ip.setText(arguments.getString(KEY_IP));
|
||||
}
|
||||
|
||||
BaseDialogBuilder builder = new BaseDialogBuilder(requireContext());
|
||||
builder.setView(view);
|
||||
onCreateDialogBuilder(builder);
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
protected abstract void onCreateDialogBuilder(AlertDialog.Builder builder);
|
||||
|
||||
protected abstract void onCreateDialog(AlertDialog dialog);
|
||||
|
||||
protected void put(AlertDialog dialog) {
|
||||
TextView host = dialog.findViewById(R.id.host);
|
||||
TextView ip = dialog.findViewById(R.id.ip);
|
||||
if (host == null || ip == null) {
|
||||
return;
|
||||
}
|
||||
String hostString = host.getText().toString().trim().toLowerCase(Locale.US);
|
||||
String ipString = ip.getText().toString().trim();
|
||||
|
||||
if (!Hosts.isValidHost(hostString)) {
|
||||
TextInputLayout hostInputLayout = dialog.findViewById(R.id.host_input_layout);
|
||||
if (hostInputLayout == null) {
|
||||
return;
|
||||
}
|
||||
hostInputLayout.setError(getString(R.string.invalid_host));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Hosts.isValidIp(ipString)) {
|
||||
TextInputLayout ipInputLayout = dialog.findViewById(R.id.ip_input_layout);
|
||||
if (ipInputLayout == null) {
|
||||
return;
|
||||
}
|
||||
ipInputLayout.setError(getString(R.string.invalid_ip));
|
||||
return;
|
||||
}
|
||||
|
||||
hostsFragment.hosts.put(hostString, ipString);
|
||||
hostsFragment.updateView(true);
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
protected void delete(AlertDialog dialog) {
|
||||
TextView host = dialog.findViewById(R.id.host);
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
String hostString = host.getText().toString().trim().toLowerCase(Locale.US);
|
||||
|
||||
hostsFragment.hosts.delete(hostString);
|
||||
hostsFragment.updateView(true);
|
||||
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AddHostDialogFragment extends HostDialogFragment {
|
||||
|
||||
@Override
|
||||
protected void onCreateDialogBuilder(AlertDialog.Builder builder) {
|
||||
builder.setTitle(R.string.add_host);
|
||||
builder.setPositiveButton(R.string.add_host_add, null);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateDialog(AlertDialog dialog) {
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog));
|
||||
}
|
||||
}
|
||||
|
||||
public static class EditHostDialogFragment extends HostDialogFragment {
|
||||
|
||||
@Override
|
||||
protected void onCreateDialogBuilder(AlertDialog.Builder builder) {
|
||||
builder.setTitle(R.string.edit_host);
|
||||
builder.setPositiveButton(R.string.edit_host_confirm, null);
|
||||
builder.setNegativeButton(R.string.edit_host_delete, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreateDialog(AlertDialog dialog) {
|
||||
TextInputLayout hostInputLayout = dialog.findViewById(R.id.host_input_layout);
|
||||
if (hostInputLayout == null) {
|
||||
return;
|
||||
}
|
||||
hostInputLayout.setEnabled(false);
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog));
|
||||
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> delete(dialog));
|
||||
}
|
||||
}
|
||||
|
||||
private static class HostsHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final TextView host;
|
||||
public final TextView ip;
|
||||
|
||||
public HostsHolder(View itemView) {
|
||||
super(itemView);
|
||||
host = itemView.findViewById(R.id.host);
|
||||
ip = itemView.findViewById(R.id.ip);
|
||||
}
|
||||
}
|
||||
|
||||
private class HostsAdapter extends RecyclerView.Adapter<HostsHolder> {
|
||||
|
||||
private final LayoutInflater inflater = getLayoutInflater();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public HostsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new HostsHolder(inflater.inflate(R.layout.item_hosts, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull HostsHolder holder, int position) {
|
||||
Pair<String, String> pair = data.get(position);
|
||||
holder.host.setText(pair.first);
|
||||
holder.ip.setText(pair.second);
|
||||
holder.itemView.setOnClickListener(v -> onItemClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.fragment
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.util.Pair
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.Hosts
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.view.ViewTransition
|
||||
|
||||
class HostsFragment : BaseFragment(), View.OnClickListener {
|
||||
private var hosts: Hosts? = null
|
||||
private var data: List<Pair<String, String>>? = null
|
||||
private var mViewTransition: ViewTransition? = null
|
||||
private var adapter: HostsAdapter? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
EhApplication.getHosts(requireContext()).apply {
|
||||
hosts = this
|
||||
data = all
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val recyclerView = inflater.inflate(R.layout.rv_layout, container, false) as RecyclerView
|
||||
val tip = getTipView(R.string.hosts)
|
||||
mViewTransition = ViewTransition(recyclerView, tip)
|
||||
val fab = getFabViewAndShow()
|
||||
adapter = HostsAdapter()
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.layoutManager =
|
||||
LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false)
|
||||
recyclerView.setHasFixedSize(true)
|
||||
fab.setOnClickListener(this)
|
||||
return recyclerView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
updateView(false)
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
val pair = data!![position]
|
||||
val args = Bundle()
|
||||
args.putString(KEY_HOST, pair.first)
|
||||
args.putString(KEY_IP, pair.second)
|
||||
val fragment: DialogFragment = EditHostDialogFragment()
|
||||
fragment.arguments = args
|
||||
fragment.show(childFragmentManager, DIALOG_TAG_EDIT_HOST)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
AddHostDialogFragment().show(childFragmentManager, DIALOG_TAG_ADD_HOST)
|
||||
}
|
||||
|
||||
private fun updateView(animation: Boolean) {
|
||||
if (null == mViewTransition) {
|
||||
return
|
||||
}
|
||||
data = hosts!!.all
|
||||
if ((data as MutableList<Pair<String, String>>?)?.isEmpty() == true) {
|
||||
mViewTransition!!.showView(1, animation)
|
||||
} else {
|
||||
mViewTransition!!.showView(0, animation)
|
||||
}
|
||||
adapter!!.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getFragmentTitle(): Int {
|
||||
return R.string.hosts
|
||||
}
|
||||
|
||||
abstract class HostDialogFragment : DialogFragment() {
|
||||
private var hostsFragment: HostsFragment? = null
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
hostsFragment = parentFragment as HostsFragment?
|
||||
val view = layoutInflater.inflate(R.layout.dialog_hosts, null, false)
|
||||
val host = view.findViewById<TextView>(R.id.host)
|
||||
val ip = view.findViewById<TextView>(R.id.ip)
|
||||
val arguments = arguments
|
||||
if (savedInstanceState == null && arguments != null) {
|
||||
host.text = arguments.getString(KEY_HOST)
|
||||
ip.text = arguments.getString(KEY_IP)
|
||||
}
|
||||
val builder = BaseDialogBuilder(requireContext())
|
||||
builder.setView(view)
|
||||
onCreateDialogBuilder(builder)
|
||||
val dialog = builder.create()
|
||||
dialog.setOnShowListener { d: DialogInterface -> onCreateDialog(d as AlertDialog) }
|
||||
return dialog
|
||||
}
|
||||
|
||||
protected abstract fun onCreateDialogBuilder(builder: AlertDialog.Builder)
|
||||
protected abstract fun onCreateDialog(dialog: AlertDialog)
|
||||
protected fun put(dialog: AlertDialog) {
|
||||
val host = dialog.findViewById<TextView>(R.id.host)
|
||||
val ip = dialog.findViewById<TextView>(R.id.ip)
|
||||
if (host == null || ip == null) {
|
||||
return
|
||||
}
|
||||
val hostString = host.text.toString().trim { it <= ' ' }.lowercase()
|
||||
val ipString = ip.text.toString().trim { it <= ' ' }
|
||||
if (!Hosts.isValidHost(hostString)) {
|
||||
val hostInputLayout =
|
||||
dialog.findViewById<TextInputLayout>(R.id.host_input_layout) ?: return
|
||||
hostInputLayout.error = getString(R.string.invalid_host)
|
||||
return
|
||||
}
|
||||
if (!Hosts.isValidIp(ipString)) {
|
||||
val ipInputLayout =
|
||||
dialog.findViewById<TextInputLayout>(R.id.ip_input_layout) ?: return
|
||||
ipInputLayout.error = getString(R.string.invalid_ip)
|
||||
return
|
||||
}
|
||||
hostsFragment!!.hosts!!.put(hostString, ipString)
|
||||
hostsFragment!!.updateView(true)
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
protected fun delete(dialog: AlertDialog) {
|
||||
val host = dialog.findViewById<TextView>(R.id.host) ?: return
|
||||
val hostString = host.text.toString().trim { it <= ' ' }.lowercase()
|
||||
hostsFragment!!.hosts!!.delete(hostString)
|
||||
hostsFragment!!.updateView(true)
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
class AddHostDialogFragment : HostDialogFragment() {
|
||||
override fun onCreateDialogBuilder(builder: AlertDialog.Builder) {
|
||||
builder.setTitle(R.string.add_host)
|
||||
builder.setPositiveButton(R.string.add_host_add, null)
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(dialog: AlertDialog) {
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
.setOnClickListener { put(dialog) }
|
||||
}
|
||||
}
|
||||
|
||||
class EditHostDialogFragment : HostDialogFragment() {
|
||||
override fun onCreateDialogBuilder(builder: AlertDialog.Builder) {
|
||||
builder.setTitle(R.string.edit_host)
|
||||
builder.setPositiveButton(R.string.edit_host_confirm, null)
|
||||
builder.setNegativeButton(R.string.edit_host_delete, null)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(dialog: AlertDialog) {
|
||||
val hostInputLayout =
|
||||
dialog.findViewById<TextInputLayout>(R.id.host_input_layout) ?: return
|
||||
hostInputLayout.isEnabled = false
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
.setOnClickListener { put(dialog) }
|
||||
dialog.getButton(DialogInterface.BUTTON_NEGATIVE)
|
||||
.setOnClickListener { delete(dialog) }
|
||||
}
|
||||
}
|
||||
|
||||
private class HostsHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val host: TextView
|
||||
val ip: TextView
|
||||
|
||||
init {
|
||||
host = itemView.findViewById(R.id.host)
|
||||
ip = itemView.findViewById(R.id.ip)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class HostsAdapter : RecyclerView.Adapter<HostsHolder>() {
|
||||
private val inflater = layoutInflater
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HostsHolder {
|
||||
return HostsHolder(inflater.inflate(R.layout.item_hosts, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: HostsHolder, position: Int) {
|
||||
val pair = data!![position]
|
||||
holder.host.text = pair.first
|
||||
holder.ip.text = pair.second
|
||||
holder.itemView.setOnClickListener { onItemClick(position) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return data!!.size
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DIALOG_TAG_ADD_HOST = AddHostDialogFragment::class.java.name
|
||||
private val DIALOG_TAG_EDIT_HOST = EditHostDialogFragment::class.java.name
|
||||
private const val KEY_HOST = "com.hippo.ehviewer.ui.fragment.HostsFragment.HOST"
|
||||
private const val KEY_IP = "com.hippo.ehviewer.ui.fragment.HostsFragment.IP"
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,6 @@ import androidx.fragment.app.FragmentActivity;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
public abstract class BaseScene extends SceneFragment {
|
||||
|
||||
public static final int LENGTH_SHORT = 0;
|
||||
|
||||
@@ -11,7 +11,8 @@ abstract class CollapsingToolbarScene : ToolbarScene() {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.scene_collapsing_toolbar, container, false) as ViewGroup
|
||||
val view =
|
||||
inflater.inflate(R.layout.scene_collapsing_toolbar, container, false) as ViewGroup
|
||||
mToolbar = view.findViewById(R.id.toolbar)
|
||||
mAppBarLayout = view.findViewById(R.id.appbar)
|
||||
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
|
||||
|
||||
@@ -41,6 +41,10 @@ import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.datepicker.CalendarConstraints;
|
||||
import com.google.android.material.datepicker.CompositeDateValidator;
|
||||
import com.google.android.material.datepicker.DateValidatorPointBackward;
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.annotation.Implemented;
|
||||
@@ -77,6 +81,9 @@ import com.hippo.yorozuya.ObjectUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -111,19 +118,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private FabLayout mFabLayout;
|
||||
private final Runnable showNormalFabsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mFabLayout != null) {
|
||||
mFabLayout.setSecondaryFabVisibilityAt(0, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(2, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(3, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(4, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(5, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
@Nullable
|
||||
@ViewLifeCircle
|
||||
private FavoritesAdapter mAdapter;
|
||||
@@ -154,6 +148,21 @@ public class FavoritesScene extends BaseScene implements
|
||||
@Nullable
|
||||
@WholeLifeCircle
|
||||
private FavListUrlBuilder mUrlBuilder;
|
||||
private final Runnable showNormalFabsRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mFabLayout != null) {
|
||||
mFabLayout.setSecondaryFabVisibilityAt(0, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(2, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(3, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(4, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(5, false);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(6, false);
|
||||
}
|
||||
updateJumpFab();
|
||||
}
|
||||
};
|
||||
private int mFavLocalCount = 0;
|
||||
private int mFavCountSum = 0;
|
||||
private boolean mHasFirstRefresh;
|
||||
@@ -280,6 +289,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
mSearchBar.setHelper(this);
|
||||
mSearchBar.setAllowEmptySearch(false);
|
||||
updateSearchBar();
|
||||
updateJumpFab();
|
||||
mSearchBarMover = new SearchBarMover(this, mSearchBar, mRecyclerView);
|
||||
|
||||
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
|
||||
@@ -341,8 +351,6 @@ public class FavoritesScene extends BaseScene implements
|
||||
mSearchBar.setEditTextHint(getString(R.string.favorites_search_bar_hint, favCatName));
|
||||
}
|
||||
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, favCat != FavListUrlBuilder.FAV_CAT_LOCAL);
|
||||
|
||||
mOldFavCat = favCatName;
|
||||
mOldKeyword = keyword;
|
||||
|
||||
@@ -350,6 +358,13 @@ public class FavoritesScene extends BaseScene implements
|
||||
Settings.putRecentFavCat(mUrlBuilder.getFavCat());
|
||||
}
|
||||
|
||||
// Hide jump fab on local fav cat
|
||||
private void updateJumpFab() {
|
||||
if (mFabLayout != null && mUrlBuilder != null) {
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, mUrlBuilder.getFavCat() != FavListUrlBuilder.FAV_CAT_LOCAL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -485,6 +500,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
mUrlBuilder.setKeyword(null);
|
||||
mUrlBuilder.setFavCat(newFavCat);
|
||||
updateSearchBar();
|
||||
updateJumpFab();
|
||||
mHelper.refresh();
|
||||
|
||||
closeDrawer(Gravity.RIGHT);
|
||||
@@ -645,9 +661,23 @@ public class FavoritesScene extends BaseScene implements
|
||||
return;
|
||||
}
|
||||
|
||||
LocalDateTime local = LocalDateTime.of(2007, 3, 21, 0, 0);
|
||||
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
|
||||
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
|
||||
|
||||
ArrayList listValidators = new ArrayList<>();
|
||||
listValidators.add(DateValidatorPointForward.from(fromDate));
|
||||
listValidators.add(DateValidatorPointBackward.before(toDate));
|
||||
|
||||
var constraintsBuilder = new CalendarConstraints.Builder()
|
||||
.setStart(fromDate)
|
||||
.setEnd(toDate)
|
||||
.setValidator(CompositeDateValidator.allOf(listValidators));
|
||||
|
||||
var datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setCalendarConstraints(constraintsBuilder.build())
|
||||
.setTitleText(R.string.go_to)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.setSelection(toDate)
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
@@ -693,7 +723,11 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
switch (position) {
|
||||
case 3: { // Download
|
||||
case 3: { // Check all
|
||||
mRecyclerView.checkAll();
|
||||
break;
|
||||
}
|
||||
case 4: { // Download
|
||||
Activity activity = getMainActivity();
|
||||
if (activity != null) {
|
||||
CommonOperations.startDownload(getMainActivity(), mModifyGiList, false);
|
||||
@@ -704,7 +738,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: { // Delete
|
||||
case 5: { // Delete
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper();
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.delete_favorites_dialog_title)
|
||||
@@ -714,7 +748,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
case 5: { // Move
|
||||
case 6: { // Move
|
||||
MoveDialogHelper helper = new MoveDialogHelper();
|
||||
// First is local favorite, the other 10 is cloud favorite
|
||||
String[] array = new String[11];
|
||||
@@ -746,6 +780,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
mFabLayout.setSecondaryFabVisibilityAt(3, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(4, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(5, true);
|
||||
mFabLayout.setSecondaryFabVisibilityAt(6, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -387,8 +387,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
}
|
||||
|
||||
RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
|
||||
if (holder instanceof ActualCommentHolder) {
|
||||
ActualCommentHolder commentHolder = (ActualCommentHolder) holder;
|
||||
if (holder instanceof ActualCommentHolder commentHolder) {
|
||||
ClickableSpan span = commentHolder.comment.getCurrentSpan();
|
||||
commentHolder.comment.clearCurrentSpan();
|
||||
|
||||
|
||||
@@ -41,15 +41,12 @@ import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewAnimationUtils;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.RatingBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -135,6 +132,7 @@ import java.util.List;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
public class GalleryDetailScene extends CollapsingToolbarScene implements View.OnClickListener,
|
||||
com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener,
|
||||
View.OnLongClickListener {
|
||||
@@ -284,6 +282,21 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void deleteFileAsync(UniFile... files) {
|
||||
//noinspection deprecation
|
||||
new AsyncTask<UniFile, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(UniFile... params) {
|
||||
for (UniFile file : params) {
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance(), files);
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private int getRatingText(float rating) {
|
||||
int resId;
|
||||
@@ -424,21 +437,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteFileAsync(UniFile... files) {
|
||||
//noinspection deprecation
|
||||
new AsyncTask<UniFile, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(UniFile... params) {
|
||||
for (UniFile file : params) {
|
||||
if (file != null) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(IoThreadPoolExecutor.getInstance(), files);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -1294,8 +1292,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
}
|
||||
} else {
|
||||
Object o = v.getTag(R.id.tag);
|
||||
if (o instanceof String) {
|
||||
String tag = (String) o;
|
||||
if (o instanceof String tag) {
|
||||
ListUrlBuilder lub = new ListUrlBuilder();
|
||||
lub.setMode(ListUrlBuilder.MODE_TAG);
|
||||
lub.setKeyword(tag);
|
||||
@@ -2113,7 +2110,8 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
}
|
||||
}
|
||||
private class DeleteDialogHelper implements DialogInterface.OnClickListener{
|
||||
|
||||
private class DeleteDialogHelper implements DialogInterface.OnClickListener {
|
||||
private final com.hippo.ehviewer.download.DownloadManager mDownloadManager;
|
||||
private final GalleryInfo mGalleryInfo;
|
||||
private final CheckBoxDialogBuilder mBuilder;
|
||||
|
||||
@@ -55,6 +55,10 @@ import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.datepicker.CalendarConstraints;
|
||||
import com.google.android.material.datepicker.CompositeDateValidator;
|
||||
import com.google.android.material.datepicker.DateValidatorPointBackward;
|
||||
import com.google.android.material.datepicker.DateValidatorPointForward;
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
@@ -109,6 +113,9 @@ import com.hippo.yorozuya.ViewUtils;
|
||||
import java.io.File;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -136,6 +143,8 @@ public final class GalleryListScene extends BaseScene
|
||||
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
|
||||
---------------*/
|
||||
@@ -216,8 +225,6 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
}
|
||||
};
|
||||
// Double click back exit
|
||||
private final long mPressBackTime = 0;
|
||||
private boolean mHasFirstRefresh = false;
|
||||
private int mNavCheckedId = 0;
|
||||
private DownloadManager mDownloadManager;
|
||||
@@ -892,9 +899,23 @@ public final class GalleryListScene extends BaseScene
|
||||
dialog.dismiss();
|
||||
});
|
||||
} else {
|
||||
LocalDateTime local = LocalDateTime.of(2007, 3, 21, 0, 0);
|
||||
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
|
||||
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
|
||||
|
||||
ArrayList listValidators = new ArrayList<>();
|
||||
listValidators.add(DateValidatorPointForward.from(fromDate));
|
||||
listValidators.add(DateValidatorPointBackward.before(toDate));
|
||||
|
||||
var constraintsBuilder = new CalendarConstraints.Builder()
|
||||
.setStart(fromDate)
|
||||
.setEnd(toDate)
|
||||
.setValidator(CompositeDateValidator.allOf(listValidators));
|
||||
|
||||
var datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setCalendarConstraints(constraintsBuilder.build())
|
||||
.setTitleText(R.string.go_to)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.setSelection(toDate)
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
@@ -1214,7 +1235,7 @@ public final class GalleryListScene extends BaseScene
|
||||
break;
|
||||
}
|
||||
}
|
||||
((StageActivity)requireActivity()).updateBackPressCallBackStatus();
|
||||
((StageActivity) requireActivity()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -191,7 +191,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setTitle(R.string.gallery_previews);
|
||||
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
|
||||
if (((GalleryDetail)mGalleryInfo).previewPages > 2)
|
||||
if (((GalleryDetail) mGalleryInfo).previewPages > 2)
|
||||
showMenu(R.menu.scene_gallery_previews);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,12 @@ import android.util.AttributeSet
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.yorozuya.NumberUtils
|
||||
|
||||
@@ -30,10 +30,12 @@ import java.util.Locale
|
||||
abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
@JvmField
|
||||
var maxGid = -1
|
||||
|
||||
@JvmField
|
||||
var minGid = -1
|
||||
|
||||
@JvmField
|
||||
var jumpTo : String? = null
|
||||
var jumpTo: String? = null
|
||||
private val listener: FavouriteStatusRouter.Listener
|
||||
|
||||
@SuppressLint("UseSparseArrays")
|
||||
|
||||
@@ -476,8 +476,7 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
if (state instanceof Bundle) {
|
||||
final Bundle savedState = (Bundle) state;
|
||||
if (state instanceof final Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
|
||||
setState(savedState.getInt(STATE_KEY_STATE), false);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public final class SearchDatabase {
|
||||
*
|
||||
* @param maxEntries Max entries to leave in the table. 0 means remove all entries.
|
||||
*/
|
||||
protected void truncateHistory(int maxEntries) {
|
||||
private void truncateHistory(int maxEntries) {
|
||||
if (maxEntries < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
@@ -187,12 +187,15 @@ class SearchLayout @JvmOverloads constructor(
|
||||
R.id.search_subscription_search -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_SUBSCRIPTION
|
||||
}
|
||||
|
||||
R.id.search_specify_uploader -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_UPLOADER
|
||||
}
|
||||
|
||||
R.id.search_specify_tag -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_TAG
|
||||
}
|
||||
|
||||
else -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_NORMAL
|
||||
}
|
||||
@@ -208,6 +211,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
urlBuilder.pageTo = mTableAdvanceSearch.pageTo
|
||||
}
|
||||
}
|
||||
|
||||
SEARCH_MODE_IMAGE -> {
|
||||
urlBuilder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
|
||||
mImageView.formatListUrlBuilder(urlBuilder)
|
||||
@@ -242,7 +246,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
override fun onTabReselected(tab: TabLayout.Tab) {}
|
||||
|
||||
@IntDef(SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE)
|
||||
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class SearchMode
|
||||
interface Helper {
|
||||
fun onChangeSearchMode()
|
||||
@@ -291,11 +295,13 @@ class SearchLayout @JvmOverloads constructor(
|
||||
ViewUtils.removeFromParent(mNormalView)
|
||||
content.addView(mNormalView)
|
||||
}
|
||||
|
||||
ITEM_TYPE_NORMAL_ADVANCE -> {
|
||||
title.setText(R.string.search_advance)
|
||||
ViewUtils.removeFromParent(mAdvanceView)
|
||||
content.addView(mAdvanceView)
|
||||
}
|
||||
|
||||
ITEM_TYPE_IMAGE -> {
|
||||
title.setText(R.string.search_image)
|
||||
ViewUtils.removeFromParent(mImageView)
|
||||
|
||||
@@ -57,7 +57,6 @@ public abstract class GalleryProvider {
|
||||
public void stop() {
|
||||
OSUtils.checkMainLoop();
|
||||
mImageCache.evictAll();
|
||||
Image.lazyEvictAll();
|
||||
}
|
||||
|
||||
public void setGLRoot(GLRoot glRoot) {
|
||||
@@ -246,7 +245,7 @@ public abstract class GalleryProvider {
|
||||
}
|
||||
|
||||
public void add(Integer key, ImageWrapper value) {
|
||||
if (value.getFormat() != Image.FORMAT_ANIMATED)
|
||||
if (!value.getAnimated())
|
||||
if (value.obtain())
|
||||
put(key, value);
|
||||
}
|
||||
|
||||
@@ -983,15 +983,13 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste
|
||||
|
||||
private final Context mContext;
|
||||
private final Adapter mAdapter;
|
||||
private final int mEdgeColor = Color.WHITE;
|
||||
private Listener mListener;
|
||||
|
||||
private int mLayoutMode = LAYOUT_LEFT_TO_RIGHT;
|
||||
private int mScaleMode = SCALE_FIT;
|
||||
private int mStartPosition = START_POSITION_TOP_LEFT;
|
||||
private int mStartPage = 0;
|
||||
|
||||
private int mBackgroundColor = Color.BLACK;
|
||||
private int mEdgeColor = Color.WHITE;
|
||||
private int mPagerInterval = 48;
|
||||
private int mScrollInterval = 24;
|
||||
private int mPageMinHeight = 256;
|
||||
|
||||
@@ -62,8 +62,8 @@ public class GLES11Canvas implements GLCanvas {
|
||||
private static final int MSKEW_Y = 1;
|
||||
private static final int MSCALE_X = 0;
|
||||
private static final int MSCALE_Y = 5;
|
||||
private static float[] sCropRect = new float[4];
|
||||
private static GLId mGLId = new GLES11IdImpl();
|
||||
private static final float[] sCropRect = new float[4];
|
||||
private static final GLId mGLId = new GLES11IdImpl();
|
||||
|
||||
static {
|
||||
float[] temp = {
|
||||
@@ -87,23 +87,23 @@ public class GLES11Canvas implements GLCanvas {
|
||||
float value = (float) Math.sin(MathUtils.radians(90f / n * i)) / 2;
|
||||
float positive = value + 0.5f;
|
||||
float negative = -value + 0.5f;
|
||||
BOX_COORDINATES[arrayOffset + n * 0 + i * 2 + 1] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 2 - i * 2 + 0] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 2 + i * 2 + 0] = negative;
|
||||
BOX_COORDINATES[arrayOffset + i * 2 + 1] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 2 - i * 2] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 2 + i * 2] = negative;
|
||||
BOX_COORDINATES[arrayOffset + n * 4 - i * 2 + 1] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 4 + i * 2 + 1] = negative;
|
||||
BOX_COORDINATES[arrayOffset + n * 6 - i * 2 + 0] = negative;
|
||||
BOX_COORDINATES[arrayOffset + n * 6 + i * 2 + 0] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 6 - i * 2] = negative;
|
||||
BOX_COORDINATES[arrayOffset + n * 6 + i * 2] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 8 - i * 2 + 1] = negative;
|
||||
}
|
||||
}
|
||||
|
||||
private final float mMatrixValues[] = new float[16];
|
||||
private final float mTextureMatrixValues[] = new float[16];
|
||||
private final float[] mMatrixValues = new float[16];
|
||||
private final float[] mTextureMatrixValues = new float[16];
|
||||
// The results of mapPoints are stored in this buffer, and the order is
|
||||
// x1, y1, x2, y2.
|
||||
private final float mMapPointsBuffer[] = new float[4];
|
||||
private final float mTextureColor[] = new float[4];
|
||||
private final float[] mMapPointsBuffer = new float[4];
|
||||
private final float[] mTextureColor = new float[4];
|
||||
private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
|
||||
private final ArrayList<ConfigState> mRestoreStack = new ArrayList<ConfigState>();
|
||||
private final RectF mDrawTextureSourceRect = new RectF();
|
||||
@@ -111,21 +111,21 @@ public class GLES11Canvas implements GLCanvas {
|
||||
private final float[] mTempMatrix = new float[32];
|
||||
private final IntList mUnboundTextures = new IntList();
|
||||
private final IntList mDeleteBuffers = new IntList();
|
||||
private final GL11 mGL;
|
||||
private final int mBoxCoords;
|
||||
private final GLState mGLState;
|
||||
private final boolean mBlendEnabled = true;
|
||||
private final int[] mFrameBuffer = new int[1];
|
||||
// Drawing statistics
|
||||
int mCountDrawLine;
|
||||
int mCountFillRect;
|
||||
int mCountDrawMesh;
|
||||
int mCountTextureRect;
|
||||
int mCountTextureOES;
|
||||
private GL11 mGL;
|
||||
private int mBoxCoords;
|
||||
private GLState mGLState;
|
||||
private float mAlpha;
|
||||
private ConfigState mRecycledRestoreAction;
|
||||
private int mScreenWidth;
|
||||
private int mScreenHeight;
|
||||
private boolean mBlendEnabled = true;
|
||||
private int mFrameBuffer[] = new int[1];
|
||||
private RawTexture mTargetTexture;
|
||||
|
||||
public GLES11Canvas(GL11 gl) {
|
||||
@@ -191,7 +191,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
|
||||
private static boolean isMatrixRotatedOrFlipped(float[] matrix) {
|
||||
final float eps = 1e-5f;
|
||||
return Math.abs(matrix[MSKEW_X]) > eps
|
||||
|| Math.abs(matrix[MSKEW_Y]) > eps
|
||||
@@ -247,7 +247,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
gl.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
gl.glLoadIdentity();
|
||||
|
||||
float matrix[] = mMatrixValues;
|
||||
float[] matrix = mMatrixValues;
|
||||
Matrix.setIdentityM(matrix, 0);
|
||||
// to match the graphic coordinate system in android, we flip it vertically.
|
||||
if (mTargetTexture == null) {
|
||||
@@ -408,7 +408,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multiplyMatrix(float matrix[], int offset) {
|
||||
public void multiplyMatrix(float[] matrix, int offset) {
|
||||
float[] temp = mTempMatrix;
|
||||
Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset);
|
||||
System.arraycopy(temp, 0, mMatrixValues, 0, 16);
|
||||
@@ -467,7 +467,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
|
||||
// Transforms two points by the given matrix m. The result
|
||||
// {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned.
|
||||
private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) {
|
||||
private float[] mapPoints(float[] m, int x1, int y1, int x2, int y2) {
|
||||
float[] r = mMapPointsBuffer;
|
||||
|
||||
// Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused.
|
||||
@@ -506,7 +506,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
textureRect(x, y, width, height);
|
||||
} else {
|
||||
// draw the rect from bottom-left to top-right
|
||||
float points[] = mapPoints(
|
||||
float[] points = mapPoints(
|
||||
mMatrixValues, x, y + height, x + width, y);
|
||||
x = (int) (points[0] + 0.5f);
|
||||
y = (int) (points[1] + 0.5f);
|
||||
@@ -958,13 +958,12 @@ public class GLES11Canvas implements GLCanvas {
|
||||
private static class GLState {
|
||||
|
||||
private final GL11 mGL;
|
||||
|
||||
private final boolean mLineSmooth = false;
|
||||
private int mTexEnvMode = GL11.GL_REPLACE;
|
||||
private float mTextureAlpha = 1.0f;
|
||||
private int mTextureTarget = GL11.GL_TEXTURE_2D;
|
||||
private boolean mBlendEnabled = true;
|
||||
private float mLineWidth = 1.0f;
|
||||
private boolean mLineSmooth = false;
|
||||
|
||||
public GLState(GL11 gl) {
|
||||
mGL = gl;
|
||||
@@ -1059,7 +1058,7 @@ public class GLES11Canvas implements GLCanvas {
|
||||
|
||||
private static class ConfigState {
|
||||
float mAlpha;
|
||||
float mMatrix[] = new float[16];
|
||||
float[] mMatrix = new float[16];
|
||||
ConfigState mNextFree;
|
||||
|
||||
public void restore(GLES11Canvas canvas) {
|
||||
|
||||
@@ -146,13 +146,13 @@ public class GLES20Canvas implements GLCanvas {
|
||||
float value = (float) Math.sin(MathUtils.radians(90f / n * i)) / 2;
|
||||
float positive = value + 0.5f;
|
||||
float negative = -value + 0.5f;
|
||||
BOX_COORDINATES[arrayOffset + n * 0 + i * 2 + 1] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 2 - i * 2 + 0] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 2 + i * 2 + 0] = negative;
|
||||
BOX_COORDINATES[arrayOffset + i * 2 + 1] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 2 - i * 2] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 2 + i * 2] = negative;
|
||||
BOX_COORDINATES[arrayOffset + n * 4 - i * 2 + 1] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 4 + i * 2 + 1] = negative;
|
||||
BOX_COORDINATES[arrayOffset + n * 6 - i * 2 + 0] = negative;
|
||||
BOX_COORDINATES[arrayOffset + n * 6 + i * 2 + 0] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 6 - i * 2] = negative;
|
||||
BOX_COORDINATES[arrayOffset + n * 6 + i * 2] = positive;
|
||||
BOX_COORDINATES[arrayOffset + n * 8 - i * 2 + 1] = negative;
|
||||
}
|
||||
}
|
||||
@@ -433,7 +433,7 @@ public class GLES20Canvas implements GLCanvas {
|
||||
public void translate(float x, float y) {
|
||||
int index = mCurrentMatrixIndex;
|
||||
float[] m = mMatrices;
|
||||
m[index + 12] += m[index + 0] * x + m[index + 4] * y;
|
||||
m[index + 12] += m[index] * x + m[index + 4] * y;
|
||||
m[index + 13] += m[index + 1] * x + m[index + 5] * y;
|
||||
m[index + 14] += m[index + 2] * x + m[index + 6] * y;
|
||||
m[index + 15] += m[index + 3] * x + m[index + 7] * y;
|
||||
|
||||
@@ -96,7 +96,7 @@ public final class MovableTextTexture extends SpriteTexture {
|
||||
int y = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int offset = i * 4;
|
||||
rects[offset + 0] = x;
|
||||
rects[offset] = x;
|
||||
rects[offset + 1] = y;
|
||||
rects[offset + 2] = (int) widths[i];
|
||||
rects[offset + 3] = height;
|
||||
|
||||
@@ -30,9 +30,9 @@ class NinePatchChunk {
|
||||
|
||||
public Rect mPaddings = new Rect();
|
||||
|
||||
public int mDivX[];
|
||||
public int mDivY[];
|
||||
public int mColor[];
|
||||
public int[] mDivX;
|
||||
public int[] mDivY;
|
||||
public int[] mColor;
|
||||
|
||||
private static void readIntArray(int[] data, ByteBuffer buffer) {
|
||||
for (int i = 0, n = data.length; i < n; ++i) {
|
||||
|
||||
@@ -35,9 +35,9 @@ import java.nio.FloatBuffer;
|
||||
public class NinePatchTexture extends ResourceTexture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "NinePatchTexture";
|
||||
private NinePatchChunk mChunk;
|
||||
private SmallCache<NinePatchInstance> mInstanceCache
|
||||
private final SmallCache<NinePatchInstance> mInstanceCache
|
||||
= new SmallCache<NinePatchInstance>();
|
||||
private NinePatchChunk mChunk;
|
||||
|
||||
public NinePatchTexture(Context context, int resId) {
|
||||
super(context, resId);
|
||||
@@ -120,8 +120,8 @@ public class NinePatchTexture extends ResourceTexture {
|
||||
private static class SmallCache<V> {
|
||||
private static final int CACHE_SIZE = 16;
|
||||
private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
|
||||
private int[] mKey = new int[CACHE_SIZE];
|
||||
private V[] mValue = (V[]) new Object[CACHE_SIZE];
|
||||
private final int[] mKey = new int[CACHE_SIZE];
|
||||
private final V[] mValue = (V[]) new Object[CACHE_SIZE];
|
||||
private int mCount; // number of items in this cache
|
||||
|
||||
// Puts a value into the cache. If the cache is full, also returns
|
||||
@@ -217,10 +217,10 @@ class NinePatchInstance {
|
||||
throw new RuntimeException("unsupported nine patch");
|
||||
}
|
||||
|
||||
float divX[] = new float[4];
|
||||
float divY[] = new float[4];
|
||||
float divU[] = new float[4];
|
||||
float divV[] = new float[4];
|
||||
float[] divX = new float[4];
|
||||
float[] divY = new float[4];
|
||||
float[] divU = new float[4];
|
||||
float[] divV = new float[4];
|
||||
|
||||
int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
|
||||
int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
|
||||
@@ -261,7 +261,7 @@ class NinePatchInstance {
|
||||
* @return the number of these dividers.
|
||||
*/
|
||||
private static int stretch(
|
||||
float x[], float u[], int div[], int source, int target) {
|
||||
float[] x, float[] u, int[] div, int source, int target) {
|
||||
int textureSize = MathUtils.nextPowerOf2(source);
|
||||
float textureBound = (float) source / textureSize;
|
||||
|
||||
@@ -313,7 +313,7 @@ class NinePatchInstance {
|
||||
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
private void prepareVertexData(float x[], float y[], float u[], float v[],
|
||||
private void prepareVertexData(float[] x, float[] y, float[] u, float[] v,
|
||||
int nx, int ny, int[] color) {
|
||||
/*
|
||||
* Given a 3x3 nine-patch image, the vertex order is defined as the
|
||||
@@ -335,8 +335,8 @@ class NinePatchInstance {
|
||||
* index: 04152637B6A5948C9DAEBF
|
||||
*/
|
||||
int pntCount = 0;
|
||||
float xy[] = new float[VERTEX_BUFFER_SIZE];
|
||||
float uv[] = new float[VERTEX_BUFFER_SIZE];
|
||||
float[] xy = new float[VERTEX_BUFFER_SIZE];
|
||||
float[] uv = new float[VERTEX_BUFFER_SIZE];
|
||||
for (int j = 0; j < ny; ++j) {
|
||||
for (int i = 0; i < nx; ++i) {
|
||||
int xIndex = (pntCount++) << 1;
|
||||
@@ -350,7 +350,7 @@ class NinePatchInstance {
|
||||
|
||||
int idxCount = 1;
|
||||
boolean isForward = false;
|
||||
byte index[] = new byte[INDEX_BUFFER_SIZE];
|
||||
byte[] index = new byte[INDEX_BUFFER_SIZE];
|
||||
for (int row = 0; row < ny - 1; row++) {
|
||||
--idxCount;
|
||||
isForward = !isForward;
|
||||
|
||||
@@ -279,8 +279,7 @@ public abstract class UploadedTexture extends BasicTexture {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (!(object instanceof BorderKey)) return false;
|
||||
BorderKey o = (BorderKey) object;
|
||||
if (!(object instanceof BorderKey o)) return false;
|
||||
return vertical == o.vertical
|
||||
&& config == o.config && length == o.length;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class ImageMovableTextTexture extends ImageSpriteTexture {
|
||||
int y = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
int offset = i * 4;
|
||||
rects[offset + 0] = x;
|
||||
rects[offset] = x;
|
||||
rects[offset + 1] = y;
|
||||
rects[offset + 2] = (int) widths[i];
|
||||
rects[offset + 3] = height;
|
||||
@@ -112,7 +112,6 @@ public class ImageMovableTextTexture extends ImageSpriteTexture {
|
||||
}
|
||||
|
||||
Image image = Image.create(bitmap);
|
||||
bitmap.recycle();
|
||||
if (image == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ public class ImageTexture implements Texture, Animatable {
|
||||
}
|
||||
|
||||
boolean end = mReleased.get() || mImage.isImageRecycled() || mNeedRelease.get() ||
|
||||
(mImage.getFormat() == 0) || mRunning.get();
|
||||
(!mImage.getAnimated()) || mRunning.get();
|
||||
|
||||
synchronized (mImage) {
|
||||
mImageBusy = false;
|
||||
@@ -595,7 +595,7 @@ public class ImageTexture implements Texture, Animatable {
|
||||
// Release image
|
||||
mImageBusy = false;
|
||||
// Check need release, frameCount <= 1
|
||||
if (mNeedRelease.get() || mImage.getFormat() == 0) {
|
||||
if (mNeedRelease.get() || !mImage.getAnimated()) {
|
||||
mAnimateRunnable = null;
|
||||
return;
|
||||
}
|
||||
@@ -617,7 +617,7 @@ public class ImageTexture implements Texture, Animatable {
|
||||
mImageBusy = true;
|
||||
}
|
||||
|
||||
mImage.advance();
|
||||
mImage.start();
|
||||
long delay = mImage.getDelay();
|
||||
long time = System.nanoTime();
|
||||
if (-1L != lastDelay) {
|
||||
|
||||
@@ -102,14 +102,14 @@ public class ImageWrapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getFormat()
|
||||
* @see Image#getAnimated()
|
||||
*/
|
||||
public int getFormat() {
|
||||
return mImage.getFormat();
|
||||
public Boolean getAnimated() {
|
||||
return mImage.getAnimated();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#getFormat()
|
||||
* @see Image#getAnimated()
|
||||
*/
|
||||
public int getWidth() {
|
||||
return mCut.width();
|
||||
@@ -123,12 +123,12 @@ public class ImageWrapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int)
|
||||
* @see Image#render(int, int, Bitmap, int, int, int, int)
|
||||
*/
|
||||
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height, boolean fillBlank, int defaultColor) {
|
||||
mImage.render(srcX + mCut.left, srcY + mCut.top, dst, dstX, dstY,
|
||||
width, height, fillBlank, defaultColor);
|
||||
width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,10 +139,10 @@ public class ImageWrapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see Image#advance()
|
||||
* @see Image#start()
|
||||
*/
|
||||
public void advance() {
|
||||
mImage.advance();
|
||||
public void start() {
|
||||
mImage.start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,21 +54,17 @@ public class GLProgressView extends GLView {
|
||||
}
|
||||
|
||||
private final GLPaint mGLPaint;
|
||||
|
||||
private final List<Animation> mAnimations;
|
||||
private float mCx;
|
||||
private float mCy;
|
||||
private float mRadiusX;
|
||||
private float mRadiusY;
|
||||
|
||||
private float mTrimStart = 0.0f;
|
||||
private float mTrimEnd = 0.0f;
|
||||
private float mTrimOffset = 0.0f;
|
||||
private float mTrimRotation = 0.0f;
|
||||
|
||||
private boolean mIndeterminate = false;
|
||||
|
||||
private List<Animation> mAnimations;
|
||||
|
||||
public GLProgressView() {
|
||||
mGLPaint = new GLPaint();
|
||||
mGLPaint.setColor(Color.WHITE);
|
||||
|
||||
@@ -1,267 +1,191 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* 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
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* 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.image
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import com.hippo.Native.getFd
|
||||
import java.io.FileDescriptor
|
||||
import java.util.function.Consumer
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ImageDecoder
|
||||
import android.graphics.ImageDecoder.ALLOCATOR_DEFAULT
|
||||
import android.graphics.ImageDecoder.ALLOCATOR_SOFTWARE
|
||||
import android.graphics.ImageDecoder.Source
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.AnimatedImageDrawable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import java.io.FileInputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
|
||||
/**
|
||||
* The `Image` is a image which stored pixel data in native heap
|
||||
*/
|
||||
class Image private constructor(nativePtr: Long) {
|
||||
private var mNativePtr: Long
|
||||
private var needRecycle = false
|
||||
private var nativeProcessCnt = 0
|
||||
class Image private constructor(
|
||||
source: Source?, drawable: Drawable? = null,
|
||||
val hardware: Boolean = true,
|
||||
val release: () -> Unit? = {}
|
||||
) {
|
||||
internal var mObtainedDrawable: Drawable?
|
||||
private var mBitmap: Bitmap? = null
|
||||
|
||||
init {
|
||||
if (needEvictAll) evictAll()
|
||||
mNativePtr = nativePtr
|
||||
imageList.add(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
val format: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetFormat(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
mObtainedDrawable = null
|
||||
source?.let {
|
||||
mObtainedDrawable = ImageDecoder.decodeDrawable(source) { decoder: ImageDecoder, _, _ ->
|
||||
decoder.allocator = if (hardware) ALLOCATOR_DEFAULT else ALLOCATOR_SOFTWARE
|
||||
// Sadly we must use software memory since we need copy it to tile buffer, fuck glgallery
|
||||
// Idk it will cause how much performance regression
|
||||
} // Should we lazy decode it?
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the image
|
||||
*/
|
||||
val width: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetWidth(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
if (mObtainedDrawable == null) {
|
||||
mObtainedDrawable = drawable!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the image
|
||||
*/
|
||||
val height: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetHeight(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun checkRecycledAndLock() {
|
||||
check(mNativePtr != 0L) { "The image is recycled." }
|
||||
nativeProcessCnt++
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun releaseNativeLock() {
|
||||
nativeProcessCnt--
|
||||
if (needRecycle) recycle()
|
||||
}
|
||||
val animated = mObtainedDrawable is AnimatedImageDrawable
|
||||
val width = mObtainedDrawable!!.intrinsicWidth
|
||||
val height = mObtainedDrawable!!.intrinsicHeight
|
||||
val isRecycled = mObtainedDrawable == null
|
||||
|
||||
/**
|
||||
* Render the image to `Bitmap`
|
||||
*/
|
||||
fun render(
|
||||
srcX: Int, srcY: Int, dst: Bitmap, dstX: Int, dstY: Int,
|
||||
width: Int, height: Int, fillBlank: Boolean, defaultColor: Int
|
||||
) {
|
||||
checkRecycledAndLock()
|
||||
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height)
|
||||
releaseNativeLock()
|
||||
}
|
||||
var started = false
|
||||
|
||||
/**
|
||||
* Call `glTexImage2D` for init is true and
|
||||
* call `glTexSubImage2D` for init is false.
|
||||
* width * height must <= 512 * 512 or do nothing
|
||||
*/
|
||||
fun texImage(init: Boolean, offsetX: Int, offsetY: Int, width: Int, height: Int) {
|
||||
checkRecycledAndLock()
|
||||
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height)
|
||||
releaseNativeLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to next frame. Do nothing for non-animation image
|
||||
*/
|
||||
fun advance() {
|
||||
checkRecycledAndLock()
|
||||
nativeAdvance(mNativePtr)
|
||||
releaseNativeLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current frame delay. 0 for non-animation image
|
||||
*/
|
||||
val delay: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val delay = nativeGetDelay(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return if (delay <= 10) 100 else delay
|
||||
}
|
||||
|
||||
/**
|
||||
* Return is the image opaque
|
||||
*/
|
||||
val isOpaque: Boolean
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeIsOpaque(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the native object associated with this image.
|
||||
* It must be called when the image will not be used.
|
||||
* The image can't be used after this method is called.
|
||||
*/
|
||||
@Synchronized
|
||||
fun recycle() {
|
||||
if (mNativePtr != 0L) {
|
||||
if (nativeProcessCnt != 0) {
|
||||
needRecycle = true
|
||||
return
|
||||
}
|
||||
nativeRecycle(mNativePtr)
|
||||
mNativePtr = 0
|
||||
imageList.remove(this)
|
||||
if (mObtainedDrawable is AnimatedImageDrawable) {
|
||||
(mObtainedDrawable as AnimatedImageDrawable?)?.stop()
|
||||
}
|
||||
if (mObtainedDrawable is BitmapDrawable) {
|
||||
(mObtainedDrawable as BitmapDrawable?)?.bitmap?.recycle()
|
||||
}
|
||||
mObtainedDrawable?.callback = null
|
||||
mObtainedDrawable = null
|
||||
mBitmap?.recycle()
|
||||
mBitmap = null
|
||||
release()
|
||||
}
|
||||
|
||||
private fun prepareBitmap() {
|
||||
if (mBitmap != null) return
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
|
||||
private fun updateBitmap() {
|
||||
prepareBitmap()
|
||||
mObtainedDrawable!!.draw(Canvas(mBitmap!!))
|
||||
}
|
||||
|
||||
fun render(
|
||||
srcX: Int, srcY: Int, dst: Bitmap, dstX: Int, dstY: Int,
|
||||
width: Int, height: Int
|
||||
) {
|
||||
check(!hardware) { "Hardware buffer cannot be used in glgallery" }
|
||||
val bitmap: Bitmap = if (animated) {
|
||||
updateBitmap()
|
||||
mBitmap!!
|
||||
} else {
|
||||
(mObtainedDrawable as BitmapDrawable).bitmap
|
||||
}
|
||||
nativeRender(
|
||||
bitmap,
|
||||
srcX,
|
||||
srcY,
|
||||
dst,
|
||||
dstX,
|
||||
dstY,
|
||||
width,
|
||||
height
|
||||
)
|
||||
}
|
||||
|
||||
fun texImage(init: Boolean, offsetX: Int, offsetY: Int, width: Int, height: Int) {
|
||||
check(!hardware) { "Hardware buffer cannot be used in glgallery" }
|
||||
val bitmap: Bitmap = if (animated) {
|
||||
updateBitmap()
|
||||
mBitmap!!
|
||||
} else {
|
||||
(mObtainedDrawable as BitmapDrawable).bitmap
|
||||
}
|
||||
nativeTexImage(
|
||||
bitmap,
|
||||
init,
|
||||
offsetX,
|
||||
offsetY,
|
||||
width,
|
||||
height
|
||||
)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
if (!started) {
|
||||
(mObtainedDrawable as AnimatedImageDrawable?)?.start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this image has been recycled.
|
||||
*/
|
||||
val isRecycled: Boolean
|
||||
get() = mNativePtr == 0L
|
||||
val delay: Int
|
||||
get() {
|
||||
if (animated)
|
||||
return 100
|
||||
return 0
|
||||
}
|
||||
|
||||
val isOpaque: Boolean
|
||||
get() {
|
||||
return mObtainedDrawable?.opacity == PixelFormat.OPAQUE
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FORMAT_NORMAL = 0
|
||||
const val FORMAT_ANIMATED = 1
|
||||
private val imageList = ArrayList<Image>()
|
||||
private var needEvictAll = false
|
||||
private fun newFromAddr(addr: Long): Image? {
|
||||
return if (addr == 0L) null else Image(addr)
|
||||
@JvmStatic
|
||||
fun decode(stream: FileInputStream, hardware: Boolean = true): Image {
|
||||
val src = ImageDecoder.createSource(
|
||||
stream.channel.map(
|
||||
FileChannel.MapMode.READ_ONLY, 0,
|
||||
stream.available().toLong()
|
||||
)
|
||||
)
|
||||
return Image(src, hardware = hardware)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun lazyEvictAll() {
|
||||
needEvictAll = true
|
||||
fun decode(buffer: ByteBuffer, hardware: Boolean = true, release: () -> Unit? = {}): Image {
|
||||
val src = ImageDecoder.createSource(buffer)
|
||||
return Image(src, hardware = hardware) {
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun evictAll() {
|
||||
ArrayList(imageList).forEach(
|
||||
Consumer { obj: Image -> obj.recycle() })
|
||||
needEvictAll = false
|
||||
@JvmStatic
|
||||
fun create(bitmap: Bitmap): Image {
|
||||
return Image(null, bitmap.toDrawable(Resources.getSystem()), false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: FileDescriptor?, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecode(getFd(fd)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: Int, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecode(fd))
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decodeAddr(addr: Long, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecodeAddr(addr))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@JvmStatic
|
||||
fun create(bitmap: Bitmap): Image? {
|
||||
return newFromAddr(nativeCreate(bitmap))
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled `Image` instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
@JvmStatic
|
||||
val imageCount: Int
|
||||
get() = imageList.size
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeDecode(fd: Int): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeDecodeAddr(addr: Long): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeCreate(bitmap: Bitmap): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeRender(
|
||||
nativePtr: Long,
|
||||
bitmap: Bitmap,
|
||||
srcX: Int, srcY: Int, dst: Bitmap, dstX: Int, dstY: Int,
|
||||
width: Int, height: Int
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeTexImage(
|
||||
nativePtr: Long,
|
||||
bitmap: Bitmap,
|
||||
init: Boolean,
|
||||
offsetX: Int,
|
||||
offsetY: Int,
|
||||
width: Int,
|
||||
height: Int
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeAdvance(nativePtr: Long)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetDelay(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeIsOpaque(nativePtr: Long): Boolean
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeRecycle(nativePtr: Long)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetFormat(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetWidth(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetHeight(nativePtr: Long): Int
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* 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
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* 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.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import java.io.FileDescriptor
|
||||
import java.lang.ref.WeakReference
|
||||
import android.graphics.drawable.Drawable
|
||||
import java.io.FileInputStream
|
||||
|
||||
/**
|
||||
* A image with [Image] for data and [Bitmap] for render.
|
||||
*/
|
||||
class ImageBitmap : Animatable, Runnable {
|
||||
private val mBitmap: Bitmap
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
val format: Int
|
||||
|
||||
/**
|
||||
* Return image is opaque
|
||||
*/
|
||||
val isOpaque: Boolean
|
||||
private val mCallbackSet: MutableSet<WeakReference<Callback>> = LinkedHashSet()
|
||||
private var mImage: Image? = null
|
||||
class ImageBitmap private constructor(image: Image) {
|
||||
private var mImage: Image? = image
|
||||
private var mReferences = 0
|
||||
private var mAnimationReferences = 0
|
||||
private var mRunning = false
|
||||
|
||||
private constructor(image: Image) {
|
||||
val width = image.width
|
||||
val height = image.height
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
image.render(0, 0, mBitmap, 0, 0, width, height, false, 0)
|
||||
format = image.format
|
||||
isOpaque = image.isOpaque
|
||||
if (format == 1) {
|
||||
// For animated image, save image object
|
||||
mImage = image
|
||||
} else {
|
||||
// Free the image
|
||||
image.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(bitmap: Bitmap) {
|
||||
format = Image.FORMAT_NORMAL
|
||||
mBitmap = bitmap
|
||||
isOpaque = !bitmap.hasAlpha()
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the image bitmap
|
||||
*
|
||||
* @return false for the image is recycled and obtain failed
|
||||
*/
|
||||
@Synchronized
|
||||
fun obtain(): Boolean {
|
||||
return if (mBitmap.isRecycled) {
|
||||
return if (mImage?.isRecycled == true) {
|
||||
false
|
||||
} else {
|
||||
++mReferences
|
||||
@@ -84,220 +34,31 @@ class ImageBitmap : Animatable, Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the image bitmap
|
||||
*/
|
||||
@Synchronized
|
||||
fun release() {
|
||||
--mReferences
|
||||
if (mReferences <= 0 && !mBitmap.isRecycled) {
|
||||
mBitmap.recycle()
|
||||
if (mImage != null) {
|
||||
mImage!!.recycle()
|
||||
}
|
||||
if (mReferences <= 0 && !mImage!!.isRecycled) {
|
||||
mImage!!.recycle()
|
||||
mImage = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback for invalidating
|
||||
*/
|
||||
fun addCallback(callback: Callback) {
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var c: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
c = iterator.next().get()
|
||||
if (c == null) {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
} else if (c === callback) {
|
||||
return
|
||||
}
|
||||
}
|
||||
mCallbackSet.add(WeakReference(callback))
|
||||
fun getDrawable(): Drawable {
|
||||
check(obtain()) { "Recycled!" }
|
||||
return mImage!!.mObtainedDrawable as Drawable
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback
|
||||
*/
|
||||
fun removeCallback(callback: Callback) {
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var c: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
c = iterator.next().get()
|
||||
if (c == null) {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
} else if (c === callback) {
|
||||
iterator.remove()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image width
|
||||
*/
|
||||
val width: Int
|
||||
get() = mBitmap.width
|
||||
get() = mImage!!.width
|
||||
|
||||
/**
|
||||
* Return image height
|
||||
*/
|
||||
val height: Int
|
||||
get() = mBitmap.height
|
||||
|
||||
/**
|
||||
* Return image is animated
|
||||
*/
|
||||
val isAnimated: Boolean
|
||||
get() = mImage != null
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, left: Float, top: Float, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, left, top, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, src: Rect?, dst: Rect, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, src: Rect?, dst: RectF, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `start()` and `stop()` is a pair
|
||||
*/
|
||||
override fun start() {
|
||||
mAnimationReferences++
|
||||
if (mBitmap.isRecycled || mImage == null || mRunning) {
|
||||
return
|
||||
}
|
||||
mRunning = true
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage!!.delay).toLong())
|
||||
}
|
||||
|
||||
/**
|
||||
* `start()` and `stop()` is a pair
|
||||
*/
|
||||
override fun stop() {
|
||||
mAnimationReferences--
|
||||
if (mAnimationReferences <= 0) {
|
||||
mRunning = false
|
||||
HANDLER.removeCallbacks(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRunning(): Boolean {
|
||||
return mRunning
|
||||
}
|
||||
|
||||
private fun notifyUpdate(): Boolean {
|
||||
var hasCallback = false
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var callback: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
callback = iterator.next().get()
|
||||
if (callback != null) {
|
||||
// Render bitmap int the first time
|
||||
if (!hasCallback) {
|
||||
hasCallback = true
|
||||
mImage!!.render(0, 0, mBitmap, 0, 0, mImage!!.width, mImage!!.height, false, 0)
|
||||
}
|
||||
callback.invalidateImage(this)
|
||||
} else {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
return hasCallback
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
// Check recycled
|
||||
if (mBitmap.isRecycled || mImage == null) {
|
||||
mRunning = false
|
||||
return
|
||||
}
|
||||
mImage!!.advance()
|
||||
if (notifyUpdate()) {
|
||||
if (mRunning) {
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage!!.delay).toLong())
|
||||
}
|
||||
} else {
|
||||
mRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun invalidateImage(who: ImageBitmap?)
|
||||
}
|
||||
get() = mImage!!.height
|
||||
|
||||
companion object {
|
||||
private val HANDLER = Handler(Looper.getMainLooper())
|
||||
|
||||
/**
|
||||
* Decode `InputStream`, then create image.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: FileDescriptor): ImageBitmap? {
|
||||
val image = Image.decode(fd, false)
|
||||
return if (image != null) {
|
||||
create(image)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `ImageBitmap` from `Image`.
|
||||
* It is not recommended. Use [.decode] if you can.
|
||||
*
|
||||
* @param image the image should not be used before and
|
||||
* it must not be recycled. And the image should
|
||||
* not be used directly anymore.
|
||||
*/
|
||||
fun create(image: Image): ImageBitmap? {
|
||||
return if (!image.isRecycled) {
|
||||
try {
|
||||
ImageBitmap(image)
|
||||
} catch (e: OutOfMemoryError) {
|
||||
image.recycle()
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `ImageBitmap` from `Bitmap`.
|
||||
*
|
||||
* @param bitmap the bitmap should not be recycled
|
||||
*/
|
||||
fun create(bitmap: Bitmap): ImageBitmap? {
|
||||
return if (!bitmap.isRecycled) {
|
||||
ImageBitmap(bitmap)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
fun decode(stream: FileInputStream): ImageBitmap {
|
||||
val image = Image.decode(stream)
|
||||
return ImageBitmap(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.image
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
* A drawable to draw [ImageBitmap]
|
||||
*/
|
||||
class ImageDrawable(imageBitmap: ImageBitmap) : Drawable(), Animatable, ImageBitmap.Callback {
|
||||
private val mImageBitmap: ImageBitmap
|
||||
private val mPaint: Paint
|
||||
|
||||
init {
|
||||
if (!imageBitmap.obtain()) {
|
||||
throw Exception()
|
||||
}
|
||||
mImageBitmap = imageBitmap
|
||||
mPaint = Paint(Paint.FILTER_BITMAP_FLAG)
|
||||
|
||||
// Add callback
|
||||
imageBitmap.addCallback(this)
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
mImageBitmap.removeCallback(this)
|
||||
mImageBitmap.release()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
mImageBitmap.draw(canvas, null, bounds, mPaint)
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
mPaint.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
mPaint.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int {
|
||||
return if (mImageBitmap.isOpaque) PixelFormat.OPAQUE else PixelFormat.UNKNOWN
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return mImageBitmap.width
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
return mImageBitmap.height
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
mImageBitmap.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
mImageBitmap.stop()
|
||||
}
|
||||
|
||||
override fun isRunning(): Boolean {
|
||||
return mImageBitmap.isRunning
|
||||
}
|
||||
|
||||
override fun invalidateImage(who: ImageBitmap?) {
|
||||
invalidateSelf()
|
||||
}
|
||||
}
|
||||
@@ -589,31 +589,6 @@ public abstract class StageActivity extends EhActivity {
|
||||
callback.setEnabled(shouldEnable);
|
||||
}
|
||||
|
||||
class BackPressCallBack extends OnBackPressedCallback {
|
||||
public BackPressCallBack(boolean enabled) {
|
||||
super(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
int size = mSceneTagList.size();
|
||||
String tag = mSceneTagList.get(size - 1);
|
||||
SceneFragment scene;
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
Log.e(TAG, "onBackPressed: Can't find scene by tag: " + tag);
|
||||
return;
|
||||
}
|
||||
if (!(fragment instanceof SceneFragment)) {
|
||||
Log.e(TAG, "onBackPressed: The fragment is not SceneFragment");
|
||||
return;
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvideAssistContent(AssistContent outContent) {
|
||||
super.onProvideAssistContent(outContent);
|
||||
@@ -656,6 +631,31 @@ public abstract class StageActivity extends EhActivity {
|
||||
return fragment.getClass();
|
||||
}
|
||||
|
||||
class BackPressCallBack extends OnBackPressedCallback {
|
||||
public BackPressCallBack(boolean enabled) {
|
||||
super(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
int size = mSceneTagList.size();
|
||||
String tag = mSceneTagList.get(size - 1);
|
||||
SceneFragment scene;
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
Log.e(TAG, "onBackPressed: Can't find scene by tag: " + tag);
|
||||
return;
|
||||
}
|
||||
if (!(fragment instanceof SceneFragment)) {
|
||||
Log.e(TAG, "onBackPressed: The fragment is not SceneFragment");
|
||||
return;
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private final class SceneViewComparator implements Comparator<View> {
|
||||
|
||||
private int getIndex(View view) {
|
||||
|
||||
@@ -104,17 +104,13 @@ final class DocumentsContractApi19 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Writable normal files considered writable
|
||||
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(type)
|
||||
&& (flags & DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
|
||||
// Directories that allow create considered writable
|
||||
return true;
|
||||
} else if (!TextUtils.isEmpty(type)
|
||||
&& (flags & DocumentsContract.Document.FLAG_SUPPORTS_WRITE) != 0) {
|
||||
// Writable normal files considered writable
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else return !TextUtils.isEmpty(type)
|
||||
&& (flags & DocumentsContract.Document.FLAG_SUPPORTS_WRITE) != 0;
|
||||
}
|
||||
|
||||
public static boolean delete(Context context, Uri self) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.io.RandomAccessFile;
|
||||
|
||||
class RawRandomAccessFile implements UniRandomAccessFile {
|
||||
|
||||
private RandomAccessFile mFile;
|
||||
private final RandomAccessFile mFile;
|
||||
|
||||
RawRandomAccessFile(RandomAccessFile file) {
|
||||
mFile = file;
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.io.OutputStream;
|
||||
// TODO need more test
|
||||
class UriOutputStream extends FileOutputStream {
|
||||
|
||||
private ParcelFileDescriptor mPfd;
|
||||
private final ParcelFileDescriptor mPfd;
|
||||
|
||||
private UriOutputStream(ParcelFileDescriptor pfd, FileDescriptor fd) {
|
||||
super(fd);
|
||||
|
||||
@@ -42,7 +42,7 @@ public final class BitmapUtils {
|
||||
final Runtime runtime = Runtime.getRuntime();
|
||||
final long used = runtime.totalMemory() - runtime.freeMemory();
|
||||
|
||||
final long total = activityManager.getMemoryClass() * 1024 * 1024;
|
||||
final long total = (long) activityManager.getMemoryClass() * 1024 * 1024;
|
||||
|
||||
return total - used;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ public final class BitmapUtils {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (width * height * 3 > m) {
|
||||
if ((long) width * height * 3 > m) {
|
||||
scaleM = (int) Math.ceil(Math.sqrt(width * height * 3 / (float) m));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ fun ClipboardManager.getTextFromClipboard(context: Context): String? {
|
||||
}
|
||||
|
||||
fun ClipboardManager.getUrlFromClipboard(context: Context): String? {
|
||||
if (primaryClipDescription?.classificationStatus == ClipDescription.CLASSIFICATION_COMPLETE) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && primaryClipDescription?.classificationStatus == ClipDescription.CLASSIFICATION_COMPLETE) {
|
||||
if ((primaryClipDescription?.getConfidenceScore(TextClassifier.TYPE_URL)
|
||||
?.let { it <= 0 }) == true
|
||||
) {
|
||||
|
||||
@@ -42,8 +42,7 @@ public final class ExceptionUtils {
|
||||
return GetText.getString(R.string.error_timeout);
|
||||
} else if (e instanceof UnknownHostException) {
|
||||
return GetText.getString(R.string.error_unknown_host);
|
||||
} else if (e instanceof StatusCodeException) {
|
||||
StatusCodeException sce = (StatusCodeException) e;
|
||||
} else if (e instanceof StatusCodeException sce) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(GetText.getString(R.string.error_bad_status_code, sce.getResponseCode()));
|
||||
if (sce.isIdentifiedResponseCode()) {
|
||||
|
||||
@@ -89,20 +89,7 @@ public class BatteryView extends AppCompatTextView {
|
||||
getHandler().post(mCharger);
|
||||
mIsChargerWorking = true;
|
||||
}
|
||||
} private final Runnable mCharger = new Runnable() {
|
||||
|
||||
private int level = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
level += 2;
|
||||
if (level > 100) {
|
||||
level = 0;
|
||||
}
|
||||
mDrawable.setElect(level, false);
|
||||
getHandler().postDelayed(mCharger, 200);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void stopCharger() {
|
||||
if (mIsChargerWorking) {
|
||||
@@ -120,7 +107,20 @@ public class BatteryView extends AppCompatTextView {
|
||||
|
||||
registerReceiver();
|
||||
}
|
||||
}
|
||||
} private final Runnable mCharger = new Runnable() {
|
||||
|
||||
private int level = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
level += 2;
|
||||
if (level > 100) {
|
||||
level = 0;
|
||||
}
|
||||
mDrawable.setElect(level, false);
|
||||
getHandler().postDelayed(mCharger, 200);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.hippo.widget;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
@@ -915,8 +914,7 @@ public class ContentLayout extends FrameLayout {
|
||||
}
|
||||
|
||||
protected Parcelable restoreInstanceState(Parcelable state) {
|
||||
if (state instanceof Bundle) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
if (state instanceof Bundle bundle) {
|
||||
mViewTransition.showView(bundle.getInt(KEY_SHOWN_VIEW), false);
|
||||
mTipView.setText(bundle.getString(KEY_TIP));
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ public class FabLayout extends ViewGroup implements View.OnClickListener {
|
||||
mOnExpandListener.onExpand(expanded);
|
||||
}
|
||||
}
|
||||
((StageActivity)getContext()).updateBackPressCallBackStatus();
|
||||
((StageActivity) getContext()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -427,8 +427,7 @@ public class FabLayout extends ViewGroup implements View.OnClickListener {
|
||||
|
||||
@Override
|
||||
public void onRestoreInstanceState(Parcelable state) {
|
||||
if (state instanceof Bundle) {
|
||||
final Bundle savedState = (Bundle) state;
|
||||
if (state instanceof final Bundle savedState) {
|
||||
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
|
||||
setAutoCancel(savedState.getBoolean(STATE_KEY_AUTO_CANCEL));
|
||||
setExpanded(savedState.getBoolean(STATE_KEY_EXPANDED), false);
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.AnimatedImageDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
@@ -39,7 +40,6 @@ import com.hippo.drawable.PreciselyClipDrawable;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.image.ImageDrawable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -63,6 +63,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
|
||||
private int mRetryType;
|
||||
private boolean mFailed;
|
||||
private boolean mLoadFromDrawable = false;
|
||||
private ImageBitmap imageBitmap;
|
||||
|
||||
public LoadImageView(Context context) {
|
||||
super(context);
|
||||
@@ -130,10 +131,9 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
|
||||
}
|
||||
}
|
||||
|
||||
private ImageDrawable getImageDrawable() {
|
||||
private Drawable getImageDrawable() {
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable instanceof TransitionDrawable) {
|
||||
TransitionDrawable transitionDrawable = (TransitionDrawable) drawable;
|
||||
if (drawable instanceof TransitionDrawable transitionDrawable) {
|
||||
if (transitionDrawable.getNumberOfLayers() == 2) {
|
||||
drawable = transitionDrawable.getDrawable(1);
|
||||
}
|
||||
@@ -141,22 +141,17 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
|
||||
if (drawable instanceof PreciselyClipDrawable) {
|
||||
drawable = ((PreciselyClipDrawable) drawable).getWrappedDrawable();
|
||||
}
|
||||
if (drawable instanceof ImageDrawable) {
|
||||
return (ImageDrawable) drawable;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
|
||||
private void clearDrawable() {
|
||||
// Recycle ImageDrawable
|
||||
ImageDrawable imageDrawable = getImageDrawable();
|
||||
if (imageDrawable != null) {
|
||||
imageDrawable.recycle();
|
||||
}
|
||||
|
||||
// Set drawable null
|
||||
setImageDrawable(null);
|
||||
// Recycle ImageDrawable
|
||||
if (imageBitmap != null) {
|
||||
imageBitmap.release();
|
||||
imageBitmap = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearRetry() {
|
||||
@@ -274,7 +269,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
|
||||
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
|
||||
Drawable drawable;
|
||||
try {
|
||||
drawable = new ImageDrawable(value);
|
||||
drawable = value.getDrawable();
|
||||
} catch (Exception e) {
|
||||
// The image might be recycled because it is removed from memory cache.
|
||||
Log.d(TAG, "The image is recycled", e);
|
||||
@@ -298,6 +293,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
|
||||
} else {
|
||||
setImageDrawable(drawable);
|
||||
}
|
||||
imageBitmap = value;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -327,25 +323,25 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
ImageDrawable drawable = getImageDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.start();
|
||||
Drawable drawable = getImageDrawable();
|
||||
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable) {
|
||||
animatedImageDrawable.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
ImageDrawable drawable = getImageDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.stop();
|
||||
Drawable drawable = getImageDrawable();
|
||||
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable) {
|
||||
animatedImageDrawable.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
ImageDrawable drawable = getImageDrawable();
|
||||
if (drawable != null) {
|
||||
return drawable.isRunning();
|
||||
Drawable drawable = getImageDrawable();
|
||||
if (drawable instanceof AnimatedImageDrawable animatedImageDrawable) {
|
||||
return animatedImageDrawable.isRunning();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -94,8 +94,7 @@ public class RadioGridGroup extends SimpleGridLayout {
|
||||
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
if (child instanceof RadioButton) {
|
||||
final RadioButton button = (RadioButton) child;
|
||||
if (child instanceof final RadioButton button) {
|
||||
if (button.isChecked()) {
|
||||
mProtectFromCheckedChange = true;
|
||||
if (mCheckedId != -1) {
|
||||
|
||||
@@ -101,6 +101,17 @@ public class TextClock extends AppCompatTextView {
|
||||
|
||||
public CharSequence getFormat12Hour() {
|
||||
return mFormat12;
|
||||
}
|
||||
|
||||
public void setFormat12Hour(CharSequence format) {
|
||||
mFormat12 = format;
|
||||
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
public CharSequence getFormat24Hour() {
|
||||
return mFormat24;
|
||||
} private final Runnable mTicker = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -113,17 +124,6 @@ public class TextClock extends AppCompatTextView {
|
||||
}
|
||||
};
|
||||
|
||||
public void setFormat12Hour(CharSequence format) {
|
||||
mFormat12 = format;
|
||||
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
public CharSequence getFormat24Hour() {
|
||||
return mFormat24;
|
||||
}
|
||||
|
||||
public void setFormat24Hour(CharSequence format) {
|
||||
mFormat24 = format;
|
||||
|
||||
@@ -158,19 +158,7 @@ public class TextClock extends AppCompatTextView {
|
||||
|
||||
public CharSequence getFormat() {
|
||||
return mFormat;
|
||||
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
|
||||
@@ -239,7 +227,19 @@ public class TextClock extends AppCompatTextView {
|
||||
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
|
||||
|
||||
getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
|
||||
}
|
||||
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, Uri uri) {
|
||||
chooseFormat();
|
||||
onTimeChanged();
|
||||
}
|
||||
};
|
||||
|
||||
private void registerObserver() {
|
||||
final ContentResolver resolver = getContext().getContentResolver();
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class FileUtils {
|
||||
@@ -154,7 +155,7 @@ public final class FileUtils {
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = new FileOutputStream(file);
|
||||
os.write(str.getBytes("utf-8"));
|
||||
os.write(str.getBytes(StandardCharsets.UTF_8));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public final class ObjectUtils {
|
||||
@@ -33,7 +34,7 @@ public final class ObjectUtils {
|
||||
* Returns true if two possibly-null objects are equal.
|
||||
*/
|
||||
public static boolean equal(@Nullable Object a, @Nullable Object b) {
|
||||
return a == b || (a != null && a.equals(b));
|
||||
return Objects.equals(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,48 +110,39 @@ public final class ObjectUtils {
|
||||
writer.write('[');
|
||||
writer.write('\n');
|
||||
|
||||
if (array instanceof Object[]) {
|
||||
Object[] a = (Object[]) array;
|
||||
if (array instanceof Object[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dump(a[i], writer, newPrefix, false);
|
||||
}
|
||||
} else if (array instanceof boolean[]) {
|
||||
boolean[] a = (boolean[]) array;
|
||||
} else if (array instanceof boolean[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dumpBoolean(a[i], writer, newPrefix);
|
||||
}
|
||||
} else if (array instanceof byte[]) {
|
||||
byte[] a = (byte[]) array;
|
||||
} else if (array instanceof byte[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dumpByte(a[i], writer, newPrefix);
|
||||
}
|
||||
} else if (array instanceof char[]) {
|
||||
char[] a = (char[]) array;
|
||||
} else if (array instanceof char[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dumpChar(a[i], writer, newPrefix);
|
||||
}
|
||||
} else if (array instanceof short[]) {
|
||||
short[] a = (short[]) array;
|
||||
} else if (array instanceof short[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dumpShort(a[i], writer, newPrefix);
|
||||
}
|
||||
} else if (array instanceof int[]) {
|
||||
int[] a = (int[]) array;
|
||||
} else if (array instanceof int[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dumpInt(a[i], writer, newPrefix);
|
||||
}
|
||||
} else if (array instanceof long[]) {
|
||||
long[] a = (long[]) array;
|
||||
} else if (array instanceof long[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dumpLong(a[i], writer, newPrefix);
|
||||
}
|
||||
} else if (array instanceof float[]) {
|
||||
float[] a = (float[]) array;
|
||||
} else if (array instanceof float[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dumpFloat(a[i], writer, newPrefix);
|
||||
}
|
||||
} else if (array instanceof double[]) {
|
||||
double[] a = (double[]) array;
|
||||
} else if (array instanceof double[] a) {
|
||||
for (int i = 0, l = a.length; i < l; i++) {
|
||||
dumpDouble(a[i], writer, newPrefix);
|
||||
}
|
||||
@@ -162,8 +154,7 @@ public final class ObjectUtils {
|
||||
for (Object o : (Set) array) {
|
||||
dump(o, writer, newPrefix);
|
||||
}
|
||||
} else if (array instanceof Map) {
|
||||
Map map = (Map) array;
|
||||
} else if (array instanceof Map map) {
|
||||
for (Object key : map.keySet()) {
|
||||
Object value = map.get(key);
|
||||
writer.write(newPrefix);
|
||||
|
||||
@@ -202,8 +202,7 @@ public final class ViewUtils {
|
||||
return view;
|
||||
}
|
||||
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
if (view instanceof ViewGroup viewGroup) {
|
||||
for (int i = 0, n = viewGroup.getChildCount(); i < n; i++) {
|
||||
View child = viewGroup.getChildAt(i);
|
||||
View result = getChild(child, id);
|
||||
@@ -379,8 +378,7 @@ public final class ViewUtils {
|
||||
*/
|
||||
public static int getIndexInParent(View view) {
|
||||
ViewParent parent = view.getParent();
|
||||
if (parent instanceof ViewGroup) {
|
||||
ViewGroup viewParent = (ViewGroup) parent;
|
||||
if (parent instanceof ViewGroup viewParent) {
|
||||
int count = viewParent.getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View v = viewParent.getChildAt(i);
|
||||
@@ -405,8 +403,7 @@ public final class ViewUtils {
|
||||
}
|
||||
|
||||
public static void setEnabledRecursively(View view, boolean enabled) {
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
if (view instanceof ViewGroup viewGroup) {
|
||||
for (int i = 0, n = viewGroup.getChildCount(); i < n; i++) {
|
||||
setEnabledRecursively(viewGroup.getChildAt(i), enabled);
|
||||
}
|
||||
@@ -422,9 +419,8 @@ public final class ViewUtils {
|
||||
writer.write(prefix);
|
||||
writer.write(view.getClass().getName());
|
||||
writer.write('\n');
|
||||
if (view instanceof ViewGroup) {
|
||||
if (view instanceof ViewGroup viewGroup) {
|
||||
String newPrefix = prefix + " ";
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {
|
||||
View child = viewGroup.getChildAt(i);
|
||||
dumpViewHierarchy(child, writer, newPrefix);
|
||||
|
||||
@@ -20,7 +20,7 @@ import android.os.Process;
|
||||
|
||||
public class PriorityThread extends Thread {
|
||||
|
||||
private int mPriority;
|
||||
private final int mPriority;
|
||||
|
||||
public PriorityThread(Runnable runnable, int priority) {
|
||||
super(runnable);
|
||||
|
||||
@@ -28,9 +28,9 @@ public class SerialThreadExecutor implements Executor, Runnable {
|
||||
|
||||
private final Lock mThreadLock = new ReentrantLock();
|
||||
private final Object mWaitLock = new Object();
|
||||
private long mKeepAliveMillis;
|
||||
private Queue<Runnable> mWorkQueue;
|
||||
private ThreadFactory mThreadFactory;
|
||||
private final long mKeepAliveMillis;
|
||||
private final Queue<Runnable> mWorkQueue;
|
||||
private final ThreadFactory mThreadFactory;
|
||||
// The worker
|
||||
private Thread mThread;
|
||||
|
||||
|
||||
@@ -20,49 +20,50 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="0.7425"
|
||||
android:scaleY="0.7425"
|
||||
android:translateX="30.68"
|
||||
android:translateY="30.68">
|
||||
<path
|
||||
android:pathData="M11,9H34"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,54H23"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,9L11,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#35AA53"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,31L52,31"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M52,31L52,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#4586F3"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M34,21L34,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#EB4334"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z"
|
||||
android:fillColor="#FBBD06"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
<group
|
||||
android:scaleX="0.7425"
|
||||
android:scaleY="0.7425"
|
||||
android:translateX="30.68"
|
||||
android:translateY="30.68">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,9H34"
|
||||
android:strokeWidth="8"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,54H23"
|
||||
android:strokeWidth="8"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,9L11,54"
|
||||
android:strokeWidth="8"
|
||||
android:strokeColor="#35AA53"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,31L52,31"
|
||||
android:strokeWidth="8"
|
||||
android:strokeColor="#FBBD06"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M52,31L52,54"
|
||||
android:strokeWidth="8"
|
||||
android:strokeColor="#4586F3"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M34,21L34,54"
|
||||
android:strokeWidth="8"
|
||||
android:strokeColor="#EB4334"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#FBBD06"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -20,53 +20,54 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="0.7425"
|
||||
android:scaleY="0.7425"
|
||||
android:translateX="30.68"
|
||||
android:translateY="30.68">
|
||||
<path
|
||||
android:pathData="M11,9H34"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,54H23"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,9L11,54"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M11,31L52,31"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M52,31L52,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M34,21L34,54"
|
||||
android:strokeWidth="8"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
<group
|
||||
android:scaleX="0.7425"
|
||||
android:scaleY="0.7425"
|
||||
android:translateX="30.68"
|
||||
android:translateY="30.68">
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,9H34"
|
||||
android:strokeWidth="8"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,54H23"
|
||||
android:strokeWidth="8"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,9L11,54"
|
||||
android:strokeWidth="8"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M11,31L52,31"
|
||||
android:strokeWidth="8"
|
||||
android:strokeAlpha="0.8"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M52,31L52,54"
|
||||
android:strokeWidth="8"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M34,21L34,54"
|
||||
android:strokeWidth="8"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M15,35H11C8.791,35 7,33.209 7,31C7,28.791 8.791,27 11,27H15V35Z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="144dp"
|
||||
android:height="144dp"
|
||||
android:tint="@color/red_500"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_alert" />
|
||||
|
||||
</vector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_dots_vertical" />
|
||||
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/slider_bubble_width"
|
||||
android:height="@dimen/slider_bubble_height"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="26"
|
||||
android:viewportHeight="32">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_slider_bubble" />
|
||||
|
||||
</vector>
|
||||
@@ -107,9 +107,9 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingTop="24dp"
|
||||
android:maxWidth="@dimen/single_max_width"
|
||||
android:text="@string/app_waring"/>
|
||||
android:paddingTop="24dp"
|
||||
android:text="@string/app_waring" />
|
||||
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tip"
|
||||
style="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="228dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/no_filter"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/corner_fab_margin"
|
||||
android:layout_marginBottom="@dimen/corner_fab_margin"
|
||||
app:layout_fitsSystemWindowsInsets="bottom"
|
||||
app:srcCompat="@drawable/v_plus_dark_x24" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2018 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="@string/hosts_tip"
|
||||
android:textSize="@dimen/text_super_large"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/corner_fab_margin"
|
||||
android:layout_marginBottom="@dimen/corner_fab_margin"
|
||||
app:layout_fitsSystemWindowsInsets="bottom"
|
||||
app:srcCompat="@drawable/v_plus_dark_x24" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -15,17 +15,16 @@
|
||||
~ You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:orientation="vertical"
|
||||
app:consumeSystemWindowsInsets="start|end"
|
||||
app:edgeToEdge="true"
|
||||
app:fitsSystemWindowsInsets="start|end">
|
||||
app:edgeToEdge="true">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
@@ -33,19 +32,37 @@
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_scrollEffect="compress"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/snackbar"
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
<TextView
|
||||
android:id="@+id/tip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="@dimen/text_super_large"
|
||||
android:visibility="gone"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
</LinearLayout>
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/corner_fab_margin"
|
||||
android:layout_marginBottom="@dimen/corner_fab_margin"
|
||||
android:visibility="gone"
|
||||
app:layout_fitsSystemWindowsInsets="bottom"
|
||||
app:srcCompat="@drawable/v_plus_dark_x24"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -40,7 +40,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:alwaysCutAndScale="true"
|
||||
app:retryType="longClick"/>
|
||||
app:retryType="longClick" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card"
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
@@ -40,7 +39,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:alwaysCutAndScale="true"
|
||||
app:retryType="click"/>
|
||||
app:retryType="click" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
+8
-13
@@ -13,16 +13,11 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="@color/red_500"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_alert" />
|
||||
|
||||
</vector>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="@dimen/fab_rv_padding_size"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
@@ -61,6 +61,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
app:srcCompat="@drawable/v_refresh_dark_x24" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/v_check_all_dark_x24" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
android:maxHeight="112dp"
|
||||
android:minHeight="56dp"
|
||||
android:padding="@dimen/keyline_margin"
|
||||
android:textColor="?colorOnPrimarySurface" />
|
||||
android:textColor="?colorOnPrimaryContainer" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/send"
|
||||
@@ -84,7 +84,7 @@
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
app:tint="?colorOnPrimarySurface" />
|
||||
app:tint="?colorOnPrimaryContainer" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
android:id="@+id/content_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
app:fitsSystemWindowsInsets="bottom"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
@@ -88,9 +88,9 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingTop="24dp"
|
||||
android:maxWidth="@dimen/single_max_width"
|
||||
android:text="@string/app_waring"/>
|
||||
android:paddingTop="24dp"
|
||||
android:text="@string/app_waring" />
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user