Compare commits
93
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9981f15f2 | ||
|
|
27b75746fd | ||
|
|
ee80a3a841 | ||
|
|
f8d639054f | ||
|
|
7f76e58e4e | ||
|
|
1b326a86a2 | ||
|
|
607bf4c67f | ||
|
|
3e4836c91e | ||
|
|
dae8f21388 | ||
|
|
84b328b60b | ||
|
|
b98d55b444 | ||
|
|
ee9413e54a | ||
|
|
76dedce5da | ||
|
|
37f99ae706 | ||
|
|
aee58d967d | ||
|
|
5ecabeb045 | ||
|
|
408c142aad | ||
|
|
a56d541380 | ||
|
|
ae79417845 | ||
|
|
010daa583e | ||
|
|
3034957c99 | ||
|
|
99134346ed | ||
|
|
9a70708300 | ||
|
|
50f0df5012 | ||
|
|
599bb80106 | ||
|
|
aa0db1f400 | ||
|
|
82b6fd22a9 | ||
|
|
b5c730d37a | ||
|
|
a2cb6444f4 | ||
|
|
8a144eabca | ||
|
|
75c2a3b0ec | ||
|
|
beec02a69b | ||
|
|
df78b45f69 | ||
|
|
e6a3c8f165 | ||
|
|
4d948e93a3 | ||
|
|
ece082727b | ||
|
|
5b5040f4af | ||
|
|
d9d88e690b | ||
|
|
bda123fc39 | ||
|
|
2200bb9b45 | ||
|
|
884d460b52 | ||
|
|
beb97270a0 | ||
|
|
52681bd386 | ||
|
|
d772a737fc | ||
|
|
638fcfea12 | ||
|
|
cae32cca5a | ||
|
|
c429e7054a | ||
|
|
f526f11c73 | ||
|
|
892016368f | ||
|
|
d9464ac278 | ||
|
|
a68a9995a1 | ||
|
|
1a0c2cb180 | ||
|
|
d7572f5a79 | ||
|
|
a208651e13 | ||
|
|
41c231b4f3 | ||
|
|
567b6f7659 | ||
|
|
6d1e0089e5 | ||
|
|
502f532fe7 | ||
|
|
ddc47f09ab | ||
|
|
5dbcdf2fce | ||
|
|
706588c39c | ||
|
|
3e3e64942e | ||
|
|
3700ac8a84 | ||
|
|
cc0dc0abf5 | ||
|
|
5861147d17 | ||
|
|
732035a0e2 | ||
|
|
643f71009d | ||
|
|
4643e37adb | ||
|
|
1bc8854685 | ||
|
|
66ebf6a391 | ||
|
|
1239277cec | ||
|
|
9cb8fb5527 | ||
|
|
819d67dca8 | ||
|
|
f895982fa0 | ||
|
|
505c097659 | ||
|
|
783fa10157 | ||
|
|
82ffbf2a1d | ||
|
|
3c3ae47511 | ||
|
|
858d7acf68 | ||
|
|
f8b7005690 | ||
|
|
33a8cb98e6 | ||
|
|
338a22f2ed | ||
|
|
f7cecaf709 | ||
|
|
c38b3c15e7 | ||
|
|
4665678e9f | ||
|
|
07416eb74d | ||
|
|
6f37dc53c7 | ||
|
|
55b963dcf9 | ||
|
|
1a433019b3 | ||
|
|
7b292cb1ab | ||
|
|
b2f5590c04 | ||
|
|
2cf2c87a8b | ||
|
|
01420f7e72 |
No files matched your search
@@ -17,11 +17,11 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
|
||||
@@ -12,11 +12,11 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
|
||||
@@ -15,11 +15,11 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 19
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 19
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
|
||||
+15
-15
@@ -31,8 +31,8 @@ android {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
targetSdkVersion 33
|
||||
versionCode 180013
|
||||
versionName "1.8.2.4"
|
||||
versionCode 180019
|
||||
versionName "1.8.3.0-beta01"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '11'
|
||||
jvmTarget = '17'
|
||||
}
|
||||
|
||||
lint {
|
||||
@@ -86,14 +86,14 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.0"
|
||||
kotlinCompilerExtensionVersion = "1.4.0-alpha02"
|
||||
}
|
||||
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.0"
|
||||
def activity_version = "1.6.1"
|
||||
implementation "androidx.activity:activity:$activity_version"
|
||||
implementation "androidx.activity:activity-ktx:$activity_version"
|
||||
|
||||
@@ -112,7 +112,7 @@ dependencies {
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
implementation "androidx.collection:collection-ktx:$collection_version"
|
||||
|
||||
def compose_version = "1.3.0-beta03"
|
||||
def compose_version = "1.3.1"
|
||||
implementation "androidx.compose.animation:animation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation:$compose_version"
|
||||
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
|
||||
@@ -120,7 +120,7 @@ dependencies {
|
||||
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
|
||||
implementation "androidx.compose.ui:ui:$compose_version"
|
||||
|
||||
implementation "androidx.compose.material3:material3:1.0.0-beta03"
|
||||
implementation "androidx.compose.material3:material3:1.0.1"
|
||||
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha04"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
@@ -132,11 +132,11 @@ dependencies {
|
||||
implementation "androidx.customview:customview:1.2.0-alpha02"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.6.0-alpha02"
|
||||
def fragment_version = "1.6.0-alpha03"
|
||||
implementation "androidx.fragment:fragment:$fragment_version"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
def lifecycle_version = "2.6.0-alpha02"
|
||||
def lifecycle_version = "2.6.0-alpha03"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
@@ -146,18 +146,18 @@ dependencies {
|
||||
|
||||
implementation "androidx.loader:loader:1.1.0"
|
||||
|
||||
def nav_version = "2.5.2"
|
||||
def nav_version = "2.5.3"
|
||||
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
implementation "androidx.paging:paging-runtime:3.2.0-alpha02"
|
||||
implementation "androidx.paging:paging-runtime:3.2.0-alpha03"
|
||||
|
||||
implementation 'androidx.preference:preference:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
|
||||
|
||||
def room_version = "2.5.0-alpha03"
|
||||
def room_version = "2.5.0-beta02"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-paging:$room_version"
|
||||
@@ -172,7 +172,7 @@ dependencies {
|
||||
implementation 'androidx.webkit:webkit:1.5.0'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.8.0-alpha01'
|
||||
implementation 'com.google.android.material:material:1.8.0-alpha02'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
android:allowBackup="true"
|
||||
android:appCategory="image"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_scheme"
|
||||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
||||
@@ -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 \
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto=thin \
|
||||
-mllvm -polly \
|
||||
-mllvm -polly-run-dce \
|
||||
-mllvm -polly-run-inliner \
|
||||
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
|
||||
add_subdirectory(libarchive)
|
||||
|
||||
# Build and link our app's native lib
|
||||
add_library(${PROJECT_NAME} SHARED main.c archive.c image.c fdutils.c)
|
||||
add_library(${PROJECT_NAME} SHARED archive.c image.c fdutils.c)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
|
||||
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
|
||||
+29
-26
@@ -32,12 +32,8 @@
|
||||
#include <archive_entry.h>
|
||||
|
||||
#define LOG_TAG "libarchive_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
|
||||
|
||||
#include "ehviewer.h"
|
||||
|
||||
typedef struct {
|
||||
int using;
|
||||
@@ -53,7 +49,7 @@ static archive_ctx **ctx_pool;
|
||||
static bool need_encrypt = false;
|
||||
static char *passwd = NULL;
|
||||
static void *archiveAddr = NULL;
|
||||
static jlong archiveSize = 0;
|
||||
static size_t archiveSize = 0;
|
||||
|
||||
const char supportExt[9][6] = {
|
||||
"jpeg",
|
||||
@@ -108,8 +104,12 @@ static int archive_alloc_ctx(archive_ctx **ctxptr) {
|
||||
free(ctx);
|
||||
return -ENOMEM;
|
||||
}
|
||||
archive_read_support_format_all(ctx->arc);
|
||||
archive_read_support_filter_all(ctx->arc);
|
||||
archive_read_support_format_tar(ctx->arc);
|
||||
archive_read_support_format_7zip(ctx->arc);
|
||||
archive_read_support_format_rar5(ctx->arc);
|
||||
archive_read_support_format_zip(ctx->arc);
|
||||
archive_read_support_filter_gzip(ctx->arc);
|
||||
archive_read_support_filter_xz(ctx->arc);
|
||||
archive_read_set_option(ctx->arc, "zip", "ignorecrc32", "1");
|
||||
if (passwd)
|
||||
archive_read_add_passphrase(ctx->arc, passwd);
|
||||
@@ -195,15 +195,16 @@ static int archive_get_ctx(archive_ctx **ctxptr, int idx) {
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd, jlong size) {
|
||||
(void) env; /* UNUSED */
|
||||
(void) thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
archiveAddr = mmap64(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (archiveAddr == MAP_FAILED) {
|
||||
LOGE("%s%d", "mmap64 failed with errno ", errno);
|
||||
LOGE("%s%s", "mmap64 failed with error ", strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
archiveSize = size;
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_WILLNEED);
|
||||
pthread_mutex_init(&ctx_lock, 0);
|
||||
ctx_pool = calloc(CTX_POOL_SIZE, sizeof(archive_ctx **));
|
||||
if (!ctx_pool) {
|
||||
@@ -231,25 +232,27 @@ Java_com_hippo_UriArchiveAccessor_openArchive(JNIEnv *env, jobject thiz, jint fd
|
||||
need_encrypt = false;
|
||||
}
|
||||
|
||||
// madvise for readahead optimization
|
||||
int format = archive_format(ctx->arc);
|
||||
switch (format) {
|
||||
case ARCHIVE_FORMAT_ZIP:
|
||||
case ARCHIVE_FORMAT_RAR:
|
||||
case ARCHIVE_FORMAT_RAR_V5:
|
||||
madvise(archiveAddr, archiveSize, MADV_SEQUENTIAL | MADV_WILLNEED);
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_SEQUENTIAL);
|
||||
break;
|
||||
case ARCHIVE_FORMAT_7ZIP: // Seek is bad
|
||||
madvise_log_if_error(archiveAddr, archiveSize, MADV_RANDOM);
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
archive_release_ctx(ctx);
|
||||
return r;
|
||||
}
|
||||
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToAddr(JNIEnv *env, jobject thiz, jint index) {
|
||||
(void) env; /* UNUSED */
|
||||
(void) thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
@@ -277,8 +280,8 @@ Java_com_hippo_UriArchiveAccessor_extractToAddr(JNIEnv *env, jobject thiz, jint
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||
(void) env; /* UNUSED */
|
||||
(void) thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
for (int i = 0; i < CTX_POOL_SIZE; i++)
|
||||
archive_release_ctx(ctx_pool[i]);
|
||||
free(passwd);
|
||||
@@ -291,14 +294,14 @@ Java_com_hippo_UriArchiveAccessor_closeArchive(JNIEnv *env, jobject thiz) {
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_needPassword(JNIEnv *env, jobject thiz) {
|
||||
(void) env; /* UNUSED */
|
||||
(void) thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
return need_encrypt;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jstring str) {
|
||||
(void) thiz; /* UNUSED */
|
||||
EH_UNUSED(thiz);
|
||||
struct archive_entry *entry;
|
||||
archive_ctx *ctx;
|
||||
jboolean ret = true;
|
||||
@@ -328,8 +331,8 @@ Java_com_hippo_UriArchiveAccessor_providePassword(JNIEnv *env, jobject thiz, jst
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_getFilename(JNIEnv *env, jobject thiz, jint index) {
|
||||
(void) env; /* UNUSED */
|
||||
(void) thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
@@ -342,8 +345,8 @@ Java_com_hippo_UriArchiveAccessor_getFilename(JNIEnv *env, jobject thiz, jint in
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_hippo_UriArchiveAccessor_extractToFd(JNIEnv *env, jobject thiz, jint index, jint fd) {
|
||||
(void) env; /* UNUSED */
|
||||
(void) thiz; /* UNUSED */
|
||||
EH_UNUSED(env);
|
||||
EH_UNUSED(thiz);
|
||||
archive_ctx *ctx = NULL;
|
||||
int ret;
|
||||
ret = archive_get_ctx(&ctx, index);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef EHVIEWER_EHVIEWER_H
|
||||
#define EHVIEWER_EHVIEWER_H
|
||||
|
||||
#define EH_UNUSED(x) (void)x
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG ,__VA_ARGS__)
|
||||
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG ,__VA_ARGS__)
|
||||
|
||||
#define madvise_log_if_error(addr, len, advice) \
|
||||
if (madvise(addr, len, advice)) \
|
||||
LOGE("%s%p%s%zu%s%d%s%s%s", "madvise addr:", addr, "len:", len, "with advice ", advice, " failed with error: ", strerror(errno), ", Ignored")
|
||||
|
||||
#endif /* EHVIEWER_EHVIEWER_H */
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#include "image.h"
|
||||
|
||||
#define TAG "ImageDecoder_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__)
|
||||
@@ -38,17 +36,7 @@
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static void *tile_buffer = NULL;
|
||||
|
||||
bool image_onLoad() {
|
||||
tile_buffer = malloc(IMAGE_TILE_MAX_SIZE * 4);
|
||||
return tile_buffer != NULL;
|
||||
}
|
||||
|
||||
void image_onUnload() {
|
||||
free(tile_buffer);
|
||||
tile_buffer = NULL;
|
||||
}
|
||||
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
|
||||
|
||||
typedef struct {
|
||||
AImageDecoder *decoder;
|
||||
|
||||
@@ -1,29 +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/>.
|
||||
*/
|
||||
|
||||
#ifndef IMAGE_IMAGE_H
|
||||
#define IMAGE_IMAGE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool image_onLoad();
|
||||
|
||||
void image_onUnload();
|
||||
|
||||
#endif /* IMAGE_IMAGE_H */
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <archive.h>
|
||||
#include <lzma.h>
|
||||
#include <zlib.h>
|
||||
#include <nettle/config.h>
|
||||
|
||||
#include "image.h"
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env;
|
||||
|
||||
if ((*vm)->GetEnv(vm, (void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
if (!image_onLoad(vm)) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
image_onUnload();
|
||||
}
|
||||
@@ -15,15 +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/>.
|
||||
*/
|
||||
package com.hippo
|
||||
|
||||
package com.hippo;
|
||||
import java.io.FileDescriptor
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
|
||||
public class Native {
|
||||
public static void initialize() {
|
||||
System.loadLibrary("ehviewer");
|
||||
object Native {
|
||||
@JvmStatic
|
||||
fun initialize() {
|
||||
System.loadLibrary("ehviewer")
|
||||
}
|
||||
|
||||
public static native int getFd(FileDescriptor fd);
|
||||
}
|
||||
@JvmStatic
|
||||
external fun getFd(fd: FileDescriptor?): Int
|
||||
}
|
||||
@@ -1,58 +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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
public class UriArchiveAccessor {
|
||||
ParcelFileDescriptor pfd;
|
||||
Context ctx;
|
||||
Uri uri;
|
||||
|
||||
public UriArchiveAccessor(Context ctx, Uri uri) {
|
||||
this.ctx = ctx;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public int open() throws Exception {
|
||||
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
|
||||
return openArchive(pfd.getFd(), pfd.getStatSize());
|
||||
}
|
||||
|
||||
private native int openArchive(int fd, long size);
|
||||
|
||||
public native long extractToAddr(int index);
|
||||
|
||||
public native void extractToFd(int index, int fd);
|
||||
|
||||
public native String getFilename(int index);
|
||||
|
||||
public native boolean needPassword();
|
||||
|
||||
public native boolean providePassword(String str);
|
||||
|
||||
private native void closeArchive();
|
||||
|
||||
public void close() throws Exception {
|
||||
closeArchive();
|
||||
pfd.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
||||
class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
val pfd by lazy { ctx.contentResolver.openFileDescriptor(uri, "r")!! }
|
||||
fun open(): Int {
|
||||
return openArchive(pfd.fd, pfd.statSize)
|
||||
}
|
||||
|
||||
private external fun openArchive(fd: Int, size: Long): Int
|
||||
external fun extractToAddr(index: Int): Long
|
||||
external fun extractToFd(index: Int, fd: Int)
|
||||
external fun getFilename(index: Int): String
|
||||
external fun needPassword(): Boolean
|
||||
external fun providePassword(str: String): Boolean
|
||||
private external fun closeArchive()
|
||||
fun close() {
|
||||
closeArchive()
|
||||
pfd.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.HapticFeedbackConstants
|
||||
import com.google.android.material.slider.Slider
|
||||
|
||||
class HapticSlider @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Slider(context, attrs) {
|
||||
init {
|
||||
addOnChangeListener { _, _, _ -> performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) }
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,6 @@ public final class ConacoTask<V> {
|
||||
} else if (mNetworkLoadTask != null) { // Getting from network
|
||||
mNetworkLoadTask.cancel(false);
|
||||
if (mCall != null) {
|
||||
mCall.cancel();
|
||||
mCall = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
@@ -94,6 +95,7 @@ public class EhUtils {
|
||||
return CATEGORY_VALUES[i];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getCategory(int type) {
|
||||
int i;
|
||||
for (i = 0; i < CATEGORY_VALUES.length - 1; i++) {
|
||||
|
||||
@@ -44,7 +44,9 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
}
|
||||
};
|
||||
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
|
||||
private int mIndex;
|
||||
private String mPrev;
|
||||
private String mNext;
|
||||
private String mJumpTo;
|
||||
private String mKeyword;
|
||||
private int mFavCat = FAV_CAT_ALL;
|
||||
|
||||
@@ -52,7 +54,9 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
}
|
||||
|
||||
protected FavListUrlBuilder(Parcel in) {
|
||||
this.mIndex = in.readInt();
|
||||
this.mPrev = in.readString();
|
||||
this.mNext = in.readString();
|
||||
this.mJumpTo = in.readString();
|
||||
this.mKeyword = in.readString();
|
||||
this.mFavCat = in.readInt();
|
||||
}
|
||||
@@ -61,8 +65,16 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
return favCat >= 0 && favCat <= 9;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
mIndex = index;
|
||||
public void setPrev(String prev) {
|
||||
mPrev = prev;
|
||||
}
|
||||
|
||||
public void setNext(String next) {
|
||||
mNext = next;
|
||||
}
|
||||
|
||||
public void setJumpTo(String jumpTo) {
|
||||
mJumpTo = jumpTo;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
@@ -105,8 +117,14 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
Log.e(TAG, "Can't URLEncoder.encode " + mKeyword);
|
||||
}
|
||||
}
|
||||
if (mIndex > 0) {
|
||||
ub.addQuery("page", Integer.toString(mIndex));
|
||||
if (mPrev != null && !mPrev.isEmpty()) {
|
||||
ub.addQuery("prev", mPrev);
|
||||
}
|
||||
if (mNext != null && !mNext.isEmpty()) {
|
||||
ub.addQuery("next", mNext);
|
||||
}
|
||||
if (mJumpTo != null && !mJumpTo.isEmpty()) {
|
||||
ub.addQuery("seek", mJumpTo);
|
||||
}
|
||||
return ub.build();
|
||||
}
|
||||
@@ -118,7 +136,9 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mIndex);
|
||||
dest.writeString(this.mPrev);
|
||||
dest.writeString(this.mNext);
|
||||
dest.writeString(this.mJumpTo);
|
||||
dest.writeString(this.mKeyword);
|
||||
dest.writeInt(this.mFavCat);
|
||||
}
|
||||
|
||||
@@ -1,636 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.client.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.client.EhConfig;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.dao.QuickSearch;
|
||||
import com.hippo.ehviewer.widget.AdvanceSearchTable;
|
||||
import com.hippo.network.UrlBuilder;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
|
||||
// Mode
|
||||
public static final int MODE_NORMAL = 0x0;
|
||||
public static final int MODE_UPLOADER = 0x1;
|
||||
public static final int MODE_TAG = 0x2;
|
||||
public static final int MODE_WHATS_HOT = 0x3;
|
||||
public static final int MODE_IMAGE_SEARCH = 0x4;
|
||||
public static final int MODE_SUBSCRIPTION = 0x5;
|
||||
public static final int MODE_TOPLIST = 0x6;
|
||||
public static final int DEFAULT_ADVANCE = AdvanceSearchTable.SNAME | AdvanceSearchTable.STAGS;
|
||||
public static final int DEFAULT_MIN_RATING = 2;
|
||||
public static final Creator<ListUrlBuilder> CREATOR = new Creator<ListUrlBuilder>() {
|
||||
@Override
|
||||
public ListUrlBuilder createFromParcel(Parcel source) {
|
||||
return new ListUrlBuilder(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListUrlBuilder[] newArray(int size) {
|
||||
return new ListUrlBuilder[size];
|
||||
}
|
||||
};
|
||||
@Mode
|
||||
private int mMode = MODE_NORMAL;
|
||||
|
||||
private int mPageIndex = 0;
|
||||
|
||||
private int mCategory = EhUtils.NONE;
|
||||
private String mKeyword = null;
|
||||
private String mSHash = null;
|
||||
|
||||
private int mAdvanceSearch = -1;
|
||||
private int mMinRating = -1;
|
||||
private int mPageFrom = -1;
|
||||
private int mPageTo = -1;
|
||||
|
||||
private String mImagePath;
|
||||
private boolean mUseSimilarityScan;
|
||||
private boolean mOnlySearchCovers;
|
||||
private boolean mShowExpunged;
|
||||
|
||||
public ListUrlBuilder() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("WrongConstant")
|
||||
protected ListUrlBuilder(Parcel in) {
|
||||
this.mMode = in.readInt();
|
||||
this.mPageIndex = in.readInt();
|
||||
this.mCategory = in.readInt();
|
||||
this.mKeyword = in.readString();
|
||||
this.mAdvanceSearch = in.readInt();
|
||||
this.mMinRating = in.readInt();
|
||||
this.mPageFrom = in.readInt();
|
||||
this.mPageTo = in.readInt();
|
||||
this.mImagePath = in.readString();
|
||||
this.mUseSimilarityScan = in.readByte() != 0;
|
||||
this.mOnlySearchCovers = in.readByte() != 0;
|
||||
this.mShowExpunged = in.readByte() != 0;
|
||||
this.mSHash = in.readString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this ListUrlBuilder point to homepage
|
||||
*/
|
||||
public void reset() {
|
||||
mMode = MODE_NORMAL;
|
||||
mPageIndex = 0;
|
||||
mCategory = EhUtils.NONE;
|
||||
mKeyword = null;
|
||||
mAdvanceSearch = -1;
|
||||
mMinRating = -1;
|
||||
mPageFrom = -1;
|
||||
mPageTo = -1;
|
||||
mImagePath = null;
|
||||
mUseSimilarityScan = false;
|
||||
mOnlySearchCovers = false;
|
||||
mShowExpunged = false;
|
||||
mSHash = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListUrlBuilder clone() {
|
||||
try {
|
||||
return (ListUrlBuilder) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Mode
|
||||
public int getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
public void setMode(@Mode int mode) {
|
||||
mMode = mode;
|
||||
}
|
||||
|
||||
public int getPageIndex() {
|
||||
return mPageIndex;
|
||||
}
|
||||
|
||||
public void setPageIndex(int pageIndex) {
|
||||
mPageIndex = pageIndex;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return mCategory;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
mCategory = category;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
return MODE_UPLOADER == mMode ? "uploader:" + mKeyword : mKeyword;
|
||||
}
|
||||
|
||||
public void setKeyword(String keyword) {
|
||||
mKeyword = keyword;
|
||||
}
|
||||
|
||||
public int getAdvanceSearch() {
|
||||
return mAdvanceSearch;
|
||||
}
|
||||
|
||||
public void setAdvanceSearch(int advanceSearch) {
|
||||
mAdvanceSearch = advanceSearch;
|
||||
}
|
||||
|
||||
public int getMinRating() {
|
||||
return mMinRating;
|
||||
}
|
||||
|
||||
public void setMinRating(int minRating) {
|
||||
mMinRating = minRating;
|
||||
}
|
||||
|
||||
public int getPageFrom() {
|
||||
return mPageFrom;
|
||||
}
|
||||
|
||||
public void setPageFrom(int pageFrom) {
|
||||
mPageFrom = pageFrom;
|
||||
}
|
||||
|
||||
public int getPageTo() {
|
||||
return mPageTo;
|
||||
}
|
||||
|
||||
public void setPageTo(int pageTo) {
|
||||
mPageTo = pageTo;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getImagePath() {
|
||||
return mImagePath;
|
||||
}
|
||||
|
||||
public void setImagePath(String imagePath) {
|
||||
mImagePath = imagePath;
|
||||
}
|
||||
|
||||
public boolean isUseSimilarityScan() {
|
||||
return mUseSimilarityScan;
|
||||
}
|
||||
|
||||
public void setUseSimilarityScan(boolean useSimilarityScan) {
|
||||
mUseSimilarityScan = useSimilarityScan;
|
||||
}
|
||||
|
||||
public boolean isOnlySearchCovers() {
|
||||
return mOnlySearchCovers;
|
||||
}
|
||||
|
||||
public void setOnlySearchCovers(boolean onlySearchCovers) {
|
||||
mOnlySearchCovers = onlySearchCovers;
|
||||
}
|
||||
|
||||
public boolean isShowExpunged() {
|
||||
return mShowExpunged;
|
||||
}
|
||||
|
||||
public void setShowExpunged(boolean showExpunged) {
|
||||
mShowExpunged = showExpunged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make them the same
|
||||
*
|
||||
* @param lub The template
|
||||
*/
|
||||
public void set(ListUrlBuilder lub) {
|
||||
mMode = lub.mMode;
|
||||
mPageIndex = lub.mPageIndex;
|
||||
mCategory = lub.mCategory;
|
||||
mKeyword = lub.mKeyword;
|
||||
mAdvanceSearch = lub.mAdvanceSearch;
|
||||
mMinRating = lub.mMinRating;
|
||||
mPageFrom = lub.mPageFrom;
|
||||
mPageTo = lub.mPageTo;
|
||||
mImagePath = lub.mImagePath;
|
||||
mUseSimilarityScan = lub.mUseSimilarityScan;
|
||||
mOnlySearchCovers = lub.mOnlySearchCovers;
|
||||
mShowExpunged = lub.mShowExpunged;
|
||||
mSHash = lub.mSHash;
|
||||
}
|
||||
|
||||
public void set(QuickSearch q) {
|
||||
mMode = q.mode;
|
||||
mCategory = q.category;
|
||||
mKeyword = q.keyword;
|
||||
mAdvanceSearch = q.advanceSearch;
|
||||
mMinRating = q.minRating;
|
||||
mPageFrom = q.pageFrom;
|
||||
mPageTo = q.pageTo;
|
||||
mImagePath = null;
|
||||
mUseSimilarityScan = false;
|
||||
mOnlySearchCovers = false;
|
||||
mShowExpunged = false;
|
||||
}
|
||||
|
||||
public QuickSearch toQuickSearch() {
|
||||
QuickSearch q = new QuickSearch();
|
||||
q.mode = mMode;
|
||||
q.category = mCategory;
|
||||
q.keyword = mKeyword;
|
||||
q.advanceSearch = mAdvanceSearch;
|
||||
q.minRating = mMinRating;
|
||||
q.pageFrom = mPageFrom;
|
||||
q.pageTo = mPageTo;
|
||||
return q;
|
||||
}
|
||||
|
||||
public boolean equalsQuickSearch(QuickSearch q) {
|
||||
if (null == q) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (q.mode != mMode) {
|
||||
return false;
|
||||
}
|
||||
if (q.category != mCategory) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.equals(q.keyword, mKeyword)) {
|
||||
return false;
|
||||
}
|
||||
if (q.advanceSearch != mAdvanceSearch) {
|
||||
return false;
|
||||
}
|
||||
if (q.minRating != mMinRating) {
|
||||
return false;
|
||||
}
|
||||
if (q.pageFrom != mPageFrom) {
|
||||
return false;
|
||||
}
|
||||
return q.pageTo == mPageTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param query xxx=yyy&mmm=nnn
|
||||
*/
|
||||
// TODO page
|
||||
public void setQuery(String query) {
|
||||
reset();
|
||||
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
return;
|
||||
}
|
||||
String[] querys = StringUtils.split(query, '&');
|
||||
int category = 0;
|
||||
String keyword = null;
|
||||
boolean enableAdvanceSearch = false;
|
||||
int advanceSearch = 0;
|
||||
boolean enableMinRating = false;
|
||||
int minRating = -1;
|
||||
boolean enablePage = false;
|
||||
int pageFrom = -1;
|
||||
int pageTo = -1;
|
||||
for (String str : querys) {
|
||||
int index = str.indexOf('=');
|
||||
if (index < 0) {
|
||||
continue;
|
||||
}
|
||||
String key = str.substring(0, index);
|
||||
String value = str.substring(index + 1);
|
||||
switch (key) {
|
||||
case "f_cats":
|
||||
int cats = NumberUtils.parseIntSafely(value, EhConfig.ALL_CATEGORY);
|
||||
category |= (~cats) & EhConfig.ALL_CATEGORY;
|
||||
break;
|
||||
case "f_doujinshi":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.DOUJINSHI;
|
||||
}
|
||||
break;
|
||||
case "f_manga":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.MANGA;
|
||||
}
|
||||
break;
|
||||
case "f_artistcg":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.ARTIST_CG;
|
||||
}
|
||||
break;
|
||||
case "f_gamecg":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.GAME_CG;
|
||||
}
|
||||
break;
|
||||
case "f_western":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.WESTERN;
|
||||
}
|
||||
break;
|
||||
case "f_non-h":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.NON_H;
|
||||
}
|
||||
break;
|
||||
case "f_imageset":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.IMAGE_SET;
|
||||
}
|
||||
break;
|
||||
case "f_cosplay":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.COSPLAY;
|
||||
}
|
||||
break;
|
||||
case "f_asianporn":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.ASIAN_PORN;
|
||||
}
|
||||
break;
|
||||
case "f_misc":
|
||||
if ("1".equals(value)) {
|
||||
category |= EhConfig.MISC;
|
||||
}
|
||||
break;
|
||||
case "f_search":
|
||||
try {
|
||||
keyword = URLDecoder.decode(value, "utf-8");
|
||||
} catch (UnsupportedEncodingException | IllegalArgumentException e) {
|
||||
// Ignore
|
||||
}
|
||||
break;
|
||||
case "advsearch":
|
||||
if ("1".equals(value)) {
|
||||
enableAdvanceSearch = true;
|
||||
}
|
||||
break;
|
||||
case "f_sname":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SNAME;
|
||||
}
|
||||
break;
|
||||
case "f_stags":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.STAGS;
|
||||
}
|
||||
break;
|
||||
case "f_sdesc":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SDESC;
|
||||
}
|
||||
break;
|
||||
case "f_storr":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.STORR;
|
||||
}
|
||||
break;
|
||||
case "f_sto":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.STO;
|
||||
}
|
||||
break;
|
||||
case "f_sdt1":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SDT1;
|
||||
}
|
||||
break;
|
||||
case "f_sdt2":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SDT2;
|
||||
}
|
||||
break;
|
||||
case "f_sh":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SH;
|
||||
}
|
||||
break;
|
||||
case "f_sfl":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SFL;
|
||||
}
|
||||
break;
|
||||
case "f_sfu":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SFU;
|
||||
}
|
||||
break;
|
||||
case "f_sft":
|
||||
if ("on".equals(value)) {
|
||||
advanceSearch |= AdvanceSearchTable.SFT;
|
||||
}
|
||||
break;
|
||||
case "f_sr":
|
||||
if ("on".equals(value)) {
|
||||
enableMinRating = true;
|
||||
}
|
||||
break;
|
||||
case "f_srdd":
|
||||
minRating = NumberUtils.parseIntSafely(value, -1);
|
||||
break;
|
||||
case "f_sp":
|
||||
if ("on".equals(value)) {
|
||||
enablePage = true;
|
||||
}
|
||||
break;
|
||||
case "f_spf":
|
||||
pageFrom = NumberUtils.parseIntSafely(value, -1);
|
||||
break;
|
||||
case "f_spt":
|
||||
pageTo = NumberUtils.parseIntSafely(value, -1);
|
||||
break;
|
||||
case "f_shash":
|
||||
mSHash = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mCategory = category;
|
||||
mKeyword = keyword;
|
||||
if (enableAdvanceSearch) {
|
||||
mAdvanceSearch = advanceSearch;
|
||||
if (enableMinRating) {
|
||||
mMinRating = minRating;
|
||||
} else {
|
||||
mMinRating = -1;
|
||||
}
|
||||
if (enablePage) {
|
||||
mPageFrom = pageFrom;
|
||||
mPageTo = pageTo;
|
||||
} else {
|
||||
mPageFrom = -1;
|
||||
mPageTo = -1;
|
||||
}
|
||||
} else {
|
||||
mAdvanceSearch = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public String build() {
|
||||
switch (mMode) {
|
||||
default:
|
||||
case MODE_NORMAL:
|
||||
case MODE_SUBSCRIPTION: {
|
||||
String url;
|
||||
if (mMode == MODE_NORMAL) {
|
||||
url = EhUrl.getHost();
|
||||
} else {
|
||||
url = EhUrl.getWatchedUrl();
|
||||
}
|
||||
|
||||
UrlBuilder ub = new UrlBuilder(url);
|
||||
if (mCategory != EhUtils.NONE) {
|
||||
ub.addQuery("f_cats", (~mCategory) & EhConfig.ALL_CATEGORY);
|
||||
}
|
||||
// Search key
|
||||
if (mKeyword != null) {
|
||||
String keyword = mKeyword.trim();
|
||||
if (!keyword.isEmpty()) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash);
|
||||
}
|
||||
// Page index
|
||||
if (mPageIndex != 0) {
|
||||
ub.addQuery("page", mPageIndex);
|
||||
}
|
||||
// Advance search
|
||||
if (mAdvanceSearch != -1) {
|
||||
ub.addQuery("advsearch", "1");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SNAME) != 0)
|
||||
ub.addQuery("f_sname", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.STAGS) != 0)
|
||||
ub.addQuery("f_stags", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SDESC) != 0)
|
||||
ub.addQuery("f_sdesc", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.STORR) != 0)
|
||||
ub.addQuery("f_storr", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.STO) != 0) ub.addQuery("f_sto", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SDT1) != 0)
|
||||
ub.addQuery("f_sdt1", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SDT2) != 0)
|
||||
ub.addQuery("f_sdt2", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SH) != 0) ub.addQuery("f_sh", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SFL) != 0) ub.addQuery("f_sfl", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SFU) != 0) ub.addQuery("f_sfu", "on");
|
||||
if ((mAdvanceSearch & AdvanceSearchTable.SFT) != 0) ub.addQuery("f_sft", "on");
|
||||
// Set min star
|
||||
if (mMinRating != -1) {
|
||||
ub.addQuery("f_sr", "on");
|
||||
ub.addQuery("f_srdd", mMinRating);
|
||||
}
|
||||
// Pages
|
||||
if (mPageFrom != -1 || mPageTo != -1) {
|
||||
ub.addQuery("f_sp", "on");
|
||||
ub.addQuery("f_spf", mPageFrom != -1 ? Integer.toString(mPageFrom) : "");
|
||||
ub.addQuery("f_spt", mPageTo != -1 ? Integer.toString(mPageTo) : "");
|
||||
}
|
||||
}
|
||||
return ub.build();
|
||||
}
|
||||
case MODE_UPLOADER: {
|
||||
StringBuilder sb = new StringBuilder(EhUrl.getHost());
|
||||
sb.append("uploader/");
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Empty
|
||||
}
|
||||
if (mPageIndex != 0) {
|
||||
sb.append('/').append(mPageIndex);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
case MODE_TAG: {
|
||||
StringBuilder sb = new StringBuilder(EhUrl.getHost());
|
||||
sb.append("tag/");
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Empty
|
||||
}
|
||||
if (mPageIndex != 0) {
|
||||
sb.append('/').append(mPageIndex);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
case MODE_WHATS_HOT:
|
||||
return EhUrl.getPopularUrl();
|
||||
case MODE_IMAGE_SEARCH:
|
||||
return EhUrl.getImageSearchUrl();
|
||||
case MODE_TOPLIST:
|
||||
StringBuilder sb = new StringBuilder(EhUrl.HOST_E);
|
||||
sb.append("toplist.php?tl=");
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Empty
|
||||
}
|
||||
if (mPageIndex != 0) {
|
||||
sb.append("&p=").append(mPageIndex);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mMode);
|
||||
dest.writeInt(this.mPageIndex);
|
||||
dest.writeInt(this.mCategory);
|
||||
dest.writeString(this.mKeyword);
|
||||
dest.writeInt(this.mAdvanceSearch);
|
||||
dest.writeInt(this.mMinRating);
|
||||
dest.writeInt(this.mPageFrom);
|
||||
dest.writeInt(this.mPageTo);
|
||||
dest.writeString(this.mImagePath);
|
||||
dest.writeByte(mUseSimilarityScan ? (byte) 1 : (byte) 0);
|
||||
dest.writeByte(mOnlySearchCovers ? (byte) 1 : (byte) 0);
|
||||
dest.writeByte(mShowExpunged ? (byte) 1 : (byte) 0);
|
||||
dest.writeString(this.mSHash);
|
||||
}
|
||||
|
||||
@IntDef({MODE_NORMAL, MODE_UPLOADER, MODE_TAG, MODE_WHATS_HOT, MODE_IMAGE_SEARCH, MODE_SUBSCRIPTION, MODE_TOPLIST})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Mode {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.Parcelable.Creator
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.IntDef
|
||||
import com.hippo.ehviewer.client.EhConfig
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.dao.QuickSearch
|
||||
import com.hippo.ehviewer.widget.AdvanceSearchTable
|
||||
import com.hippo.network.UrlBuilder
|
||||
import com.hippo.yorozuya.NumberUtils
|
||||
import com.hippo.yorozuya.StringUtils
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
|
||||
open class ListUrlBuilder : Cloneable, Parcelable {
|
||||
@get:Mode
|
||||
@Mode
|
||||
var mode = MODE_NORMAL
|
||||
private var mPrevGid = 0
|
||||
private var mNextGid = 0
|
||||
private var mJumpTo: String? = null
|
||||
var category = EhUtils.NONE
|
||||
private var mKeyword: String? = null
|
||||
private var mSHash: String? = null
|
||||
var advanceSearch = -1
|
||||
var minRating = -1
|
||||
var pageFrom = -1
|
||||
var pageTo = -1
|
||||
var imagePath: String? = null
|
||||
var isUseSimilarityScan = false
|
||||
var isOnlySearchCovers = false
|
||||
var isShowExpunged = false
|
||||
|
||||
constructor()
|
||||
protected constructor(parcel: Parcel) {
|
||||
mode = parcel.readInt()
|
||||
mPrevGid = parcel.readInt()
|
||||
mNextGid = parcel.readInt()
|
||||
mJumpTo = parcel.readString()
|
||||
this.category = parcel.readInt()
|
||||
mKeyword = parcel.readString()
|
||||
advanceSearch = parcel.readInt()
|
||||
minRating = parcel.readInt()
|
||||
pageFrom = parcel.readInt()
|
||||
pageTo = parcel.readInt()
|
||||
imagePath = parcel.readString()
|
||||
isUseSimilarityScan = parcel.readByte().toInt() != 0
|
||||
isOnlySearchCovers = parcel.readByte().toInt() != 0
|
||||
isShowExpunged = parcel.readByte().toInt() != 0
|
||||
mSHash = parcel.readString()
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
mode = MODE_NORMAL
|
||||
mPrevGid = 0
|
||||
mNextGid = 0
|
||||
mJumpTo = null
|
||||
this.category = EhUtils.NONE
|
||||
mKeyword = null
|
||||
advanceSearch = -1
|
||||
minRating = -1
|
||||
pageFrom = -1
|
||||
pageTo = -1
|
||||
imagePath = null
|
||||
isUseSimilarityScan = false
|
||||
isOnlySearchCovers = false
|
||||
isShowExpunged = false
|
||||
mSHash = null
|
||||
}
|
||||
|
||||
public override fun clone(): ListUrlBuilder {
|
||||
return try {
|
||||
super.clone() as ListUrlBuilder
|
||||
} catch (e: CloneNotSupportedException) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun setPrevGid(prevGid: Int) {
|
||||
mPrevGid = prevGid
|
||||
}
|
||||
|
||||
fun setNextGid(nextGid: Int) {
|
||||
mNextGid = nextGid
|
||||
}
|
||||
|
||||
fun setJumpTo(jumpTo: String?) {
|
||||
mJumpTo = jumpTo
|
||||
}
|
||||
|
||||
var keyword: String?
|
||||
get() = if (MODE_UPLOADER == mode) "uploader:$mKeyword" else mKeyword
|
||||
set(keyword) {
|
||||
mKeyword = keyword
|
||||
}
|
||||
|
||||
/**
|
||||
* Make them the same
|
||||
*
|
||||
* @param lub The template
|
||||
*/
|
||||
fun set(lub: ListUrlBuilder) {
|
||||
mode = lub.mode
|
||||
mPrevGid = lub.mPrevGid
|
||||
mNextGid = lub.mNextGid
|
||||
mJumpTo = lub.mJumpTo
|
||||
this.category = lub.category
|
||||
mKeyword = lub.mKeyword
|
||||
advanceSearch = lub.advanceSearch
|
||||
minRating = lub.minRating
|
||||
pageFrom = lub.pageFrom
|
||||
pageTo = lub.pageTo
|
||||
imagePath = lub.imagePath
|
||||
isUseSimilarityScan = lub.isUseSimilarityScan
|
||||
isOnlySearchCovers = lub.isOnlySearchCovers
|
||||
isShowExpunged = lub.isShowExpunged
|
||||
mSHash = lub.mSHash
|
||||
}
|
||||
|
||||
fun set(q: QuickSearch) {
|
||||
mode = q.mode
|
||||
this.category = q.category
|
||||
mKeyword = q.keyword
|
||||
advanceSearch = q.advanceSearch
|
||||
minRating = q.minRating
|
||||
pageFrom = q.pageFrom
|
||||
pageTo = q.pageTo
|
||||
imagePath = null
|
||||
isUseSimilarityScan = false
|
||||
isOnlySearchCovers = false
|
||||
isShowExpunged = false
|
||||
}
|
||||
|
||||
fun toQuickSearch(): QuickSearch {
|
||||
val q = QuickSearch()
|
||||
q.mode = mode
|
||||
q.category = this.category
|
||||
q.keyword = mKeyword
|
||||
q.advanceSearch = advanceSearch
|
||||
q.minRating = minRating
|
||||
q.pageFrom = pageFrom
|
||||
q.pageTo = pageTo
|
||||
return q
|
||||
}
|
||||
|
||||
fun equalsQuickSearch(q: QuickSearch?): Boolean {
|
||||
if (null == q) {
|
||||
return false
|
||||
}
|
||||
if (q.mode != mode) {
|
||||
return false
|
||||
}
|
||||
if (q.category != this.category) {
|
||||
return false
|
||||
}
|
||||
if (!StringUtils.equals(q.keyword, mKeyword)) {
|
||||
return false
|
||||
}
|
||||
if (q.advanceSearch != advanceSearch) {
|
||||
return false
|
||||
}
|
||||
if (q.minRating != minRating) {
|
||||
return false
|
||||
}
|
||||
return if (q.pageFrom != pageFrom) {
|
||||
false
|
||||
} else q.pageTo == pageTo
|
||||
}
|
||||
|
||||
/**
|
||||
* @param query xxx=yyy&mmm=nnn
|
||||
*/
|
||||
// TODO page
|
||||
fun setQuery(query: String?) {
|
||||
reset()
|
||||
if (TextUtils.isEmpty(query)) {
|
||||
return
|
||||
}
|
||||
val querys = StringUtils.split(query, '&')
|
||||
var category = 0
|
||||
var keyword: String? = null
|
||||
var enableAdvanceSearch = false
|
||||
var advanceSearch = 0
|
||||
var enableMinRating = false
|
||||
var minRating = -1
|
||||
var enablePage = false
|
||||
var pageFrom = -1
|
||||
var pageTo = -1
|
||||
for (str in querys) {
|
||||
val index = str.indexOf('=')
|
||||
if (index < 0) {
|
||||
continue
|
||||
}
|
||||
val key = str.substring(0, index)
|
||||
val value = str.substring(index + 1)
|
||||
when (key) {
|
||||
"f_cats" -> {
|
||||
val cats = NumberUtils.parseIntSafely(value, EhConfig.ALL_CATEGORY)
|
||||
category = category or (cats.inv() and EhConfig.ALL_CATEGORY)
|
||||
}
|
||||
|
||||
"f_doujinshi" -> if ("1" == value) {
|
||||
category = category or EhConfig.DOUJINSHI
|
||||
}
|
||||
|
||||
"f_manga" -> if ("1" == value) {
|
||||
category = category or EhConfig.MANGA
|
||||
}
|
||||
|
||||
"f_artistcg" -> if ("1" == value) {
|
||||
category = category or EhConfig.ARTIST_CG
|
||||
}
|
||||
|
||||
"f_gamecg" -> if ("1" == value) {
|
||||
category = category or EhConfig.GAME_CG
|
||||
}
|
||||
|
||||
"f_western" -> if ("1" == value) {
|
||||
category = category or EhConfig.WESTERN
|
||||
}
|
||||
|
||||
"f_non-h" -> if ("1" == value) {
|
||||
category = category or EhConfig.NON_H
|
||||
}
|
||||
|
||||
"f_imageset" -> if ("1" == value) {
|
||||
category = category or EhConfig.IMAGE_SET
|
||||
}
|
||||
|
||||
"f_cosplay" -> if ("1" == value) {
|
||||
category = category or EhConfig.COSPLAY
|
||||
}
|
||||
|
||||
"f_asianporn" -> if ("1" == value) {
|
||||
category = category or EhConfig.ASIAN_PORN
|
||||
}
|
||||
|
||||
"f_misc" -> if ("1" == value) {
|
||||
category = category or EhConfig.MISC
|
||||
}
|
||||
|
||||
"f_search" -> try {
|
||||
keyword = URLDecoder.decode(value, "utf-8")
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Ignore
|
||||
} catch (_: IllegalArgumentException) {
|
||||
}
|
||||
|
||||
"advsearch" -> if ("1" == value) {
|
||||
enableAdvanceSearch = true
|
||||
}
|
||||
|
||||
"f_sh" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.SH
|
||||
}
|
||||
|
||||
"f_sto" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.STO
|
||||
}
|
||||
|
||||
"f_sfl" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.SFL
|
||||
}
|
||||
|
||||
"f_sfu" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.SFU
|
||||
}
|
||||
|
||||
"f_sft" -> if ("on" == value) {
|
||||
advanceSearch = advanceSearch or AdvanceSearchTable.SFT
|
||||
}
|
||||
|
||||
"f_sr" -> if ("on" == value) {
|
||||
enableMinRating = true
|
||||
}
|
||||
|
||||
"f_srdd" -> minRating = NumberUtils.parseIntSafely(value, -1)
|
||||
"f_sp" -> if ("on" == value) {
|
||||
enablePage = true
|
||||
}
|
||||
|
||||
"f_spf" -> pageFrom = NumberUtils.parseIntSafely(value, -1)
|
||||
"f_spt" -> pageTo = NumberUtils.parseIntSafely(value, -1)
|
||||
"f_shash" -> mSHash = value
|
||||
}
|
||||
}
|
||||
this.category = category
|
||||
mKeyword = keyword
|
||||
if (enableAdvanceSearch) {
|
||||
this.advanceSearch = advanceSearch
|
||||
if (enableMinRating) {
|
||||
this.minRating = minRating
|
||||
} else {
|
||||
this.minRating = -1
|
||||
}
|
||||
if (enablePage) {
|
||||
this.pageFrom = pageFrom
|
||||
this.pageTo = pageTo
|
||||
} else {
|
||||
this.pageFrom = -1
|
||||
this.pageTo = -1
|
||||
}
|
||||
} else {
|
||||
this.advanceSearch = -1
|
||||
}
|
||||
}
|
||||
|
||||
fun build(): String {
|
||||
return when (mode) {
|
||||
MODE_NORMAL, MODE_SUBSCRIPTION -> {
|
||||
val url: String = if (mode == MODE_NORMAL) {
|
||||
EhUrl.getHost()
|
||||
} else {
|
||||
EhUrl.getWatchedUrl()
|
||||
}
|
||||
val ub = UrlBuilder(url)
|
||||
if (this.category != EhUtils.NONE) {
|
||||
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
|
||||
}
|
||||
// Search key
|
||||
if (mKeyword != null) {
|
||||
val keyword = mKeyword!!.trim { it <= ' ' }
|
||||
if (keyword.isNotEmpty()) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash!!)
|
||||
}
|
||||
mJumpTo?.let {
|
||||
ub.addQuery("seek", mJumpTo!!)
|
||||
mPrevGid = 0
|
||||
mNextGid = 0
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
ub.addQuery("prev", mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
ub.addQuery("next", mNextGid)
|
||||
}
|
||||
// Advance search
|
||||
if (advanceSearch != -1) {
|
||||
ub.addQuery("advsearch", "1")
|
||||
if (advanceSearch and AdvanceSearchTable.SH != 0) ub.addQuery("f_sh", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.STO != 0) ub.addQuery("f_sto", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFL != 0) ub.addQuery("f_sfl", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFU != 0) ub.addQuery("f_sfu", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFT != 0) ub.addQuery("f_sft", "on")
|
||||
// Set min star
|
||||
if (minRating != -1) {
|
||||
ub.addQuery("f_sr", "on")
|
||||
ub.addQuery("f_srdd", minRating)
|
||||
}
|
||||
// Pages
|
||||
if (pageFrom != -1 || pageTo != -1) {
|
||||
ub.addQuery("f_sp", "on")
|
||||
ub.addQuery("f_spf", if (pageFrom != -1) pageFrom.toString() else "")
|
||||
ub.addQuery("f_spt", if (pageTo != -1) pageTo.toString() else "")
|
||||
}
|
||||
}
|
||||
ub.build()
|
||||
}
|
||||
|
||||
MODE_UPLOADER -> {
|
||||
val sb = StringBuilder(EhUrl.getHost())
|
||||
sb.append("uploader/")
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
sb.append("?prev=").append(mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
MODE_TAG -> {
|
||||
val sb = StringBuilder(EhUrl.getHost())
|
||||
sb.append("tag/")
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
sb.append("?prev=").append(mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
MODE_WHATS_HOT -> EhUrl.getPopularUrl()
|
||||
MODE_IMAGE_SEARCH -> EhUrl.getImageSearchUrl()
|
||||
MODE_TOPLIST -> {
|
||||
val sb = StringBuilder(EhUrl.HOST_E)
|
||||
sb.append("toplist.php?tl=")
|
||||
try {
|
||||
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
// Empty
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
sb.append("&p=").append(mNextGid)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
else -> {
|
||||
val url: String = if (mode == MODE_NORMAL) {
|
||||
EhUrl.getHost()
|
||||
} else {
|
||||
EhUrl.getWatchedUrl()
|
||||
}
|
||||
val ub = UrlBuilder(url)
|
||||
if (this.category != EhUtils.NONE) {
|
||||
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
|
||||
}
|
||||
if (mKeyword != null) {
|
||||
val keyword = mKeyword!!.trim { it <= ' ' }
|
||||
if (keyword.isNotEmpty()) {
|
||||
try {
|
||||
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash!!)
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
ub.addQuery("prev", mPrevGid)
|
||||
}
|
||||
if (mNextGid != 0) {
|
||||
ub.addQuery("next", mNextGid)
|
||||
}
|
||||
if (advanceSearch != -1) {
|
||||
ub.addQuery("advsearch", "1")
|
||||
if (advanceSearch and AdvanceSearchTable.SH != 0) ub.addQuery("f_sh", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.STO != 0) ub.addQuery("f_sto", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFL != 0) ub.addQuery("f_sfl", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFU != 0) ub.addQuery("f_sfu", "on")
|
||||
if (advanceSearch and AdvanceSearchTable.SFT != 0) ub.addQuery("f_sft", "on")
|
||||
if (minRating != -1) {
|
||||
ub.addQuery("f_sr", "on")
|
||||
ub.addQuery("f_srdd", minRating)
|
||||
}
|
||||
if (pageFrom != -1 || pageTo != -1) {
|
||||
ub.addQuery("f_sp", "on")
|
||||
ub.addQuery("f_spf", if (pageFrom != -1) pageFrom.toString() else "")
|
||||
ub.addQuery("f_spt", if (pageTo != -1) pageTo.toString() else "")
|
||||
}
|
||||
}
|
||||
ub.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeInt(mode)
|
||||
dest.writeInt(mPrevGid)
|
||||
dest.writeInt(mNextGid)
|
||||
dest.writeString(mJumpTo)
|
||||
dest.writeInt(this.category)
|
||||
dest.writeString(mKeyword)
|
||||
dest.writeInt(advanceSearch)
|
||||
dest.writeInt(minRating)
|
||||
dest.writeInt(pageFrom)
|
||||
dest.writeInt(pageTo)
|
||||
dest.writeString(imagePath)
|
||||
dest.writeByte(if (isUseSimilarityScan) 1.toByte() else 0.toByte())
|
||||
dest.writeByte(if (isOnlySearchCovers) 1.toByte() else 0.toByte())
|
||||
dest.writeByte(if (isShowExpunged) 1.toByte() else 0.toByte())
|
||||
dest.writeString(mSHash)
|
||||
}
|
||||
|
||||
@IntDef(
|
||||
MODE_NORMAL,
|
||||
MODE_UPLOADER,
|
||||
MODE_TAG,
|
||||
MODE_WHATS_HOT,
|
||||
MODE_IMAGE_SEARCH,
|
||||
MODE_SUBSCRIPTION,
|
||||
MODE_TOPLIST
|
||||
)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class Mode
|
||||
companion object {
|
||||
const val MODE_NORMAL = 0x0
|
||||
const val MODE_UPLOADER = 0x1
|
||||
const val MODE_TAG = 0x2
|
||||
const val MODE_WHATS_HOT = 0x3
|
||||
const val MODE_IMAGE_SEARCH = 0x4
|
||||
const val MODE_SUBSCRIPTION = 0x5
|
||||
const val MODE_TOPLIST = 0x6
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Creator<ListUrlBuilder> = object : Creator<ListUrlBuilder> {
|
||||
override fun createFromParcel(source: Parcel): ListUrlBuilder {
|
||||
return ListUrlBuilder(source)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<ListUrlBuilder?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,18 +31,22 @@ import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FavoritesParser {
|
||||
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+(-\\d+)?)");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+(-\\d+)?)");
|
||||
|
||||
public static Result parse(String body) throws Exception {
|
||||
if (body.contains("This page requires you to log on.</p>")) {
|
||||
throw new EhException(GetText.getString(R.string.need_sign_in));
|
||||
}
|
||||
Document d = Jsoup.parse(body);
|
||||
|
||||
String[] catArray = new String[10];
|
||||
int[] countArray = new int[10];
|
||||
|
||||
try {
|
||||
Document d = Jsoup.parse(body);
|
||||
Element ido = JsoupUtils.getElementByClass(d, "ido");
|
||||
//noinspection ConstantConditions
|
||||
Elements fps = ido.getElementsByClass("fp");
|
||||
@@ -60,13 +64,24 @@ public class FavoritesParser {
|
||||
throw new ParseException("Parse favorites error", body);
|
||||
}
|
||||
|
||||
Result re = new Result();
|
||||
try {
|
||||
Element prev = d.getElementById("uprev");
|
||||
Element next = d.getElementById("unext");
|
||||
assert prev != null;
|
||||
assert next != null;
|
||||
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
|
||||
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
|
||||
if (matcherPrev.find()) re.prevPage = matcherPrev.group(1);
|
||||
if (matcherNext.find()) re.nextPage = matcherNext.group(1);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
GalleryListParser.Result result = GalleryListParser.parse(body);
|
||||
|
||||
Result re = new Result();
|
||||
re.catArray = catArray;
|
||||
re.countArray = countArray;
|
||||
re.pages = result.pages;
|
||||
re.nextPage = result.nextPage;
|
||||
re.galleryInfoList = result.galleryInfoList;
|
||||
|
||||
return re;
|
||||
@@ -75,8 +90,8 @@ public class FavoritesParser {
|
||||
public static class Result {
|
||||
public String[] catArray; // Size 10
|
||||
public int[] countArray; // Size 10
|
||||
public int pages;
|
||||
public int nextPage;
|
||||
public String prevPage;
|
||||
public String nextPage;
|
||||
public List<GalleryInfo> galleryInfoList;
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,8 @@ public class GalleryListParser {
|
||||
private static final Pattern PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px");
|
||||
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("background-color:rgba\\((\\d+),(\\d+),(\\d+),");
|
||||
private static final Pattern PATTERN_PAGES = Pattern.compile("(\\d+) page");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("page=(\\d+)");
|
||||
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+)");
|
||||
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+)");
|
||||
|
||||
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
|
||||
new String[]{"0", "0", "0"},
|
||||
@@ -298,37 +299,31 @@ public class GalleryListParser {
|
||||
}
|
||||
|
||||
public static Result parse(@NonNull String body) throws Exception {
|
||||
|
||||
Result result = new Result();
|
||||
Document d = Jsoup.parse(body);
|
||||
|
||||
try {
|
||||
Element ptt = d.getElementsByClass("ptt").first();
|
||||
Elements es = ptt.child(0).child(0).children();
|
||||
result.pages = Integer.parseInt(es.get(es.size() - 2).text().trim());
|
||||
|
||||
Element e = es.get(es.size() - 1);
|
||||
if (e != null) {
|
||||
e = e.children().first();
|
||||
if (e != null) {
|
||||
String href = e.attr("href");
|
||||
Matcher matcher = PATTERN_NEXT_PAGE.matcher(href);
|
||||
if (matcher.find()) {
|
||||
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Element prev = d.getElementById("uprev");
|
||||
Element next = d.getElementById("unext");
|
||||
assert prev != null;
|
||||
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);
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
|
||||
if (body.contains("No hits found</p>")) {
|
||||
result.pages = 0;
|
||||
result.founds = 0;
|
||||
//noinspection unchecked
|
||||
result.galleryInfoList = Collections.EMPTY_LIST;
|
||||
return result;
|
||||
} else if (d.getElementsByClass("ptt").isEmpty()) {
|
||||
result.pages = 1;
|
||||
result.founds = 1;
|
||||
} else {
|
||||
result.pages = Integer.MAX_VALUE;
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +345,7 @@ public class GalleryListParser {
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
if (es.size() < 2 || !NO_UNFILTERED_TEXT.equals(es.get(1).text())) {
|
||||
throw new ParseException("No gallery", body);
|
||||
Log.d(TAG, "No gallery found");
|
||||
}
|
||||
}
|
||||
result.galleryInfoList = list;
|
||||
@@ -364,8 +359,9 @@ public class GalleryListParser {
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public int pages;
|
||||
public int nextPage;
|
||||
public int founds;
|
||||
public int prevGid;
|
||||
public int nextGid;
|
||||
public boolean noWatchedTags;
|
||||
public List<GalleryInfo> galleryInfoList;
|
||||
}
|
||||
|
||||
@@ -81,9 +81,7 @@ class AccountPreference @JvmOverloads constructor(
|
||||
super.onPrepareDialogBuilder(builder)
|
||||
if (message != null) {
|
||||
builder.setNeutralButton(R.string.settings_eh_identity_cookies_copy) { dialog: DialogInterface?, which: Int ->
|
||||
context.getClipboardManager().addTextToClipboard(message, true) {
|
||||
mActivity.showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
|
||||
}
|
||||
mActivity.addTextToClipboard(message, true)
|
||||
this@AccountPreference.onClick(dialog, which)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.EhApplication.locked_last_leave_time
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
|
||||
import rikka.core.res.resolveColor
|
||||
import rikka.insets.WindowInsetsHelper
|
||||
import rikka.layoutinflater.view.LayoutInflaterFactory
|
||||
@@ -72,7 +71,6 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
WindowInsetsHelper.LISTENER
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
val window = window
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
window.decorView.post {
|
||||
val rootWindowInsets = window.decorView.rootWindowInsets
|
||||
@@ -103,9 +101,9 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
override fun onResume() {
|
||||
val locked_resume_time = System.currentTimeMillis() / 1000
|
||||
val locked_delay_time = locked_resume_time - locked_last_leave_time
|
||||
if (Settings.getSecurity() && locked_delay_time >= Settings.getSecurityDelay() * 60
|
||||
&& isAuthenticationSupported(this) && EhApplication.locked
|
||||
) {
|
||||
if (locked_delay_time < Settings.getSecurityDelay() * 60) {
|
||||
EhApplication.locked = false
|
||||
} else if (Settings.getSecurity() && isAuthenticationSupported() && EhApplication.locked) {
|
||||
startActivity(Intent(this, SecurityActivity::class.java))
|
||||
}
|
||||
super.onResume()
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.UrlOpener
|
||||
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()
|
||||
private var mValues: ArrayList<String> = arrayListOf()
|
||||
private var mRecyclerView: RecyclerView? = null
|
||||
private val mDetail: GalleryDetail = detail
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
parseDetail()
|
||||
val view = inflater.inflate(R.layout.scene_gallery_info, container, false)
|
||||
dialog?.window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
val recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView
|
||||
val adapter = InfoAdapter()
|
||||
recyclerView.adapter = adapter
|
||||
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.setHasFixedSize(true)
|
||||
mRecyclerView = recyclerView
|
||||
return view
|
||||
}
|
||||
|
||||
private fun parseDetail() {
|
||||
mKeys.add(getString(R.string.header_key))
|
||||
mValues.add(getString(R.string.header_value))
|
||||
mKeys.add(getString(R.string.key_gid))
|
||||
mValues.add(mDetail.gid.toString())
|
||||
mKeys.add(getString(R.string.key_token))
|
||||
mValues.add(mDetail.token)
|
||||
mKeys.add(getString(R.string.key_url))
|
||||
mValues.add(EhUrl.getGalleryDetailUrl(mDetail.gid, mDetail.token))
|
||||
mKeys.add(getString(R.string.key_title))
|
||||
mValues.add(mDetail.title)
|
||||
mKeys.add(getString(R.string.key_title_jpn))
|
||||
mValues.add(mDetail.titleJpn)
|
||||
mKeys.add(getString(R.string.key_thumb))
|
||||
mValues.add(mDetail.thumb)
|
||||
mKeys.add(getString(R.string.key_category))
|
||||
mValues.add(EhUtils.getCategory(mDetail.category))
|
||||
mKeys.add(getString(R.string.key_uploader))
|
||||
mValues.add(mDetail.uploader)
|
||||
mKeys.add(getString(R.string.key_posted))
|
||||
mValues.add(mDetail.posted)
|
||||
mKeys.add(getString(R.string.key_parent))
|
||||
mValues.add(mDetail.parent)
|
||||
mKeys.add(getString(R.string.key_visible))
|
||||
mValues.add(mDetail.visible)
|
||||
mKeys.add(getString(R.string.key_language))
|
||||
mValues.add(mDetail.language)
|
||||
mKeys.add(getString(R.string.key_pages))
|
||||
mValues.add(mDetail.pages.toString())
|
||||
mKeys.add(getString(R.string.key_size))
|
||||
mValues.add(mDetail.size)
|
||||
mKeys.add(getString(R.string.key_favorite_count))
|
||||
mValues.add(mDetail.favoriteCount.toString())
|
||||
mKeys.add(getString(R.string.key_favorited))
|
||||
mValues.add(java.lang.Boolean.toString(mDetail.isFavorited))
|
||||
mKeys.add(getString(R.string.key_rating_count))
|
||||
mValues.add(mDetail.ratingCount.toString())
|
||||
mKeys.add(getString(R.string.key_rating))
|
||||
mValues.add(mDetail.rating.toString())
|
||||
mKeys.add(getString(R.string.key_torrents))
|
||||
mValues.add(mDetail.torrentCount.toString())
|
||||
mKeys.add(getString(R.string.key_torrent_url))
|
||||
mValues.add(mDetail.torrentUrl)
|
||||
mKeys.add(getString(R.string.favorite_name))
|
||||
mValues.add(mDetail.favoriteName)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(view.findViewById(R.id.title) as TextView).setText(R.string.gallery_info)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mRecyclerView?.stopScroll()
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
val context = context
|
||||
return if (null != context && 0 != position) {
|
||||
if (position == INDEX_PARENT) {
|
||||
UrlOpener.openUrl(context, mValues[position], true)
|
||||
} else {
|
||||
activity?.addTextToClipboard(
|
||||
mValues[position],
|
||||
false,
|
||||
)
|
||||
if (position == INDEX_URL) {
|
||||
// Save it to avoid detect the gallery
|
||||
Settings.putClipboardTextHashCode(mValues[position].hashCode())
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private class InfoHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val key: TextView
|
||||
val value: TextView
|
||||
|
||||
init {
|
||||
key = itemView.findViewById(R.id.key)
|
||||
value = itemView.findViewById(R.id.value)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class InfoAdapter : RecyclerView.Adapter<InfoHolder>() {
|
||||
private val mInflater: LayoutInflater = layoutInflater
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position == 0) {
|
||||
TYPE_HEADER
|
||||
} else {
|
||||
TYPE_DATA
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InfoHolder {
|
||||
return InfoHolder(
|
||||
mInflater.inflate(
|
||||
if (viewType == TYPE_HEADER) R.layout.item_gallery_info_header else R.layout.item_gallery_info_data,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: InfoHolder, position: Int) {
|
||||
holder.key.text = mKeys[position]
|
||||
holder.value.text = mValues[position]
|
||||
holder.itemView.isEnabled = position != 0
|
||||
holder.itemView.setOnClickListener { onItemClick(position) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return mKeys.size.coerceAtMost(mValues.size)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "GalleryInfoBottomSheet"
|
||||
private const val INDEX_URL = 3
|
||||
private const val INDEX_PARENT = 10
|
||||
private const val TYPE_HEADER = 0
|
||||
private const val TYPE_DATA = 1
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,6 @@ import com.hippo.ehviewer.ui.scene.DownloadsScene;
|
||||
import com.hippo.ehviewer.ui.scene.FavoritesScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryCommentsScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryDetailScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryInfoScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryPreviewsScene;
|
||||
import com.hippo.ehviewer.ui.scene.HistoryScene;
|
||||
@@ -101,7 +100,6 @@ public final class MainActivity extends StageActivity
|
||||
registerLaunchMode(SelectSiteScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
registerLaunchMode(GalleryListScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TOP);
|
||||
registerLaunchMode(GalleryDetailScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||
registerLaunchMode(GalleryInfoScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||
registerLaunchMode(GalleryCommentsScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||
registerLaunchMode(GalleryPreviewsScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
|
||||
registerLaunchMode(DownloadsScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
|
||||
@@ -232,7 +230,6 @@ public final class MainActivity extends StageActivity
|
||||
builder.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
||||
builder.setImagePath(temp.getPath());
|
||||
builder.setUseSimilarityScan(true);
|
||||
builder.setShowExpunged(true);
|
||||
startScene(processAnnouncer(GalleryListScene.getStartAnnouncer(builder)));
|
||||
return true;
|
||||
}
|
||||
@@ -259,7 +256,7 @@ public final class MainActivity extends StageActivity
|
||||
String url = intent.getData().toString();
|
||||
new EditTextDialogBuilder(this, url, "")
|
||||
.setTitle(R.string.error_cannot_parse_the_url)
|
||||
.setPositiveButton(android.R.string.copy, (dialog, which) -> ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(this), url, false, () -> null))
|
||||
.setPositiveButton(android.R.string.copy, (dialog, which) -> ClipboardUtilKt.addTextToClipboard(this, url, false))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,16 @@ import androidx.fragment.app.FragmentActivity
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
|
||||
fun Context.isAuthenticationSupported(): Boolean {
|
||||
val authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||
return BiometricManager.from(this)
|
||||
.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
|
||||
class SecurityActivity : AppCompatActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isAuthenticationSupported(this))
|
||||
if (isAuthenticationSupported())
|
||||
startAuthentication(getString(R.string.settings_privacy_require_unlock))
|
||||
else
|
||||
onSuccess()
|
||||
@@ -70,14 +76,6 @@ class SecurityActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun isAuthenticationSupported(context: Context): Boolean {
|
||||
val authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||
return BiometricManager.from(context)
|
||||
.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSuccess() {
|
||||
EhApplication.locked = false
|
||||
finish()
|
||||
|
||||
@@ -57,7 +57,7 @@ class SettingsActivity : EhActivity() {
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
|
||||
@@ -66,7 +66,6 @@ import java.util.zip.ZipOutputStream;
|
||||
public class AdvancedFragment extends BasePreferenceFragment {
|
||||
|
||||
private static final String KEY_DUMP_LOGCAT = "dump_logcat";
|
||||
private static final String KEY_CLEAR_MEMORY_CACHE = "clear_memory_cache";
|
||||
private static final String KEY_APP_LANGUAGE = "app_language";
|
||||
private static final String KEY_IMPORT_DATA = "import_data";
|
||||
private static final String KEY_EXPORT_DATA = "export_data";
|
||||
@@ -234,7 +233,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
addPreferencesFromResource(R.xml.advanced_settings);
|
||||
|
||||
Preference dumpLogcat = findPreference(KEY_DUMP_LOGCAT);
|
||||
Preference clearMemoryCache = findPreference(KEY_CLEAR_MEMORY_CACHE);
|
||||
Preference appLanguage = findPreference(KEY_APP_LANGUAGE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
appLanguage.setVisible(false);
|
||||
@@ -250,7 +248,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
}
|
||||
|
||||
dumpLogcat.setOnPreferenceClickListener(this);
|
||||
clearMemoryCache.setOnPreferenceClickListener(this);
|
||||
importData.setOnPreferenceClickListener(this);
|
||||
exportData.setOnPreferenceClickListener(this);
|
||||
backupFavorite.setOnPreferenceClickListener(this);
|
||||
@@ -269,10 +266,6 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
showTip(R.string.error_cant_find_activity, BaseScene.LENGTH_SHORT);
|
||||
}
|
||||
return true;
|
||||
} else if (KEY_CLEAR_MEMORY_CACHE.equals(key)) {
|
||||
((EhApplication) requireContext().getApplicationContext()).clearMemoryCache();
|
||||
Runtime.getRuntime().gc();
|
||||
showTip(R.string.settings_advanced_clear_memory_cache_done, BaseScene.LENGTH_SHORT);
|
||||
} else if (KEY_IMPORT_DATA.equals(key)) {
|
||||
try {
|
||||
importDataLauncher.launch(new String[]{"*/*"});
|
||||
@@ -319,7 +312,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
Activity activity = requireActivity();
|
||||
EhClient mClient = EhApplication.getEhClient(context);
|
||||
FavListUrlBuilder favListUrlBuilder = new FavListUrlBuilder();
|
||||
favListUrlBuilder.setIndex(page.get());
|
||||
// favListUrlBuilder.setNext(page.get());
|
||||
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||
@@ -331,7 +324,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
|
||||
EhDB.putLocalFavorites(result1.galleryInfoList);
|
||||
Log.d("LocalFavorites", "now backup page " + page.get());
|
||||
if (!result1.galleryInfoList.isEmpty()) {
|
||||
favListUrlBuilder.setIndex(page.incrementAndGet());
|
||||
// favListUrlBuilder.setNext(page.incrementAndGet());
|
||||
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
|
||||
mClient.execute(request);
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,7 @@ import androidx.preference.SeekBarPreference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
|
||||
import com.hippo.ehviewer.ui.isAuthenticationSupported
|
||||
|
||||
/**
|
||||
* Created by Mo10 on 2018/2/10.
|
||||
@@ -48,7 +48,7 @@ class PrivacyFragment : BasePreferenceFragment() {
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!isAuthenticationSupported(requireContext())) {
|
||||
if (!requireContext().isAuthenticationSupported()) {
|
||||
Settings.putSecurity(false)
|
||||
requireUnlock.isEnabled = false
|
||||
requireUnlock.isChecked = false
|
||||
|
||||
@@ -108,12 +108,11 @@ public abstract class BaseScene extends SceneFragment {
|
||||
}
|
||||
}
|
||||
|
||||
public Unit showTip(@StringRes int id, int length) {
|
||||
public void showTip(@StringRes int id, int length) {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity instanceof MainActivity) {
|
||||
((MainActivity) activity).showTip(id, length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean needShowLeftDrawer() {
|
||||
|
||||
@@ -22,7 +22,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.Gravity;
|
||||
@@ -33,7 +33,6 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -42,10 +41,10 @@ import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.annotation.Implemented;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.drawable.AddDeleteDrawable;
|
||||
import com.hippo.drawable.DrawerArrowDrawable;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
@@ -342,6 +341,8 @@ 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;
|
||||
|
||||
@@ -644,35 +645,13 @@ public class FavoritesScene extends BaseScene implements
|
||||
return;
|
||||
}
|
||||
|
||||
final int page = mHelper.getPageForTop();
|
||||
final int pages = mHelper.getPages();
|
||||
String hint = getString(R.string.go_to_hint, page + 1, pages);
|
||||
final EditTextDialogBuilder builder = new EditTextDialogBuilder(context, null, hint);
|
||||
builder.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
final AlertDialog dialog = builder.setTitle(R.string.go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (null == mHelper) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
String text = builder.getText().trim();
|
||||
int goTo;
|
||||
try {
|
||||
goTo = Integer.parseInt(text) - 1;
|
||||
} catch (NumberFormatException e) {
|
||||
builder.setError(getString(R.string.error_invalid_number));
|
||||
return;
|
||||
}
|
||||
if (goTo < 0 || goTo >= pages) {
|
||||
builder.setError(getString(R.string.error_out_of_range));
|
||||
return;
|
||||
}
|
||||
builder.setError(null);
|
||||
mHelper.goTo(goTo);
|
||||
dialog.dismiss();
|
||||
var datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setTitleText(R.string.go_to)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
mHelper.goTo(v);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -850,7 +829,17 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
updateSearchBar();
|
||||
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
|
||||
assert mUrlBuilder != null;
|
||||
|
||||
int pages = 0;
|
||||
if (result.nextPage == null)
|
||||
pages = mHelper.pgCounter + 1;
|
||||
else
|
||||
pages = Integer.MAX_VALUE;
|
||||
|
||||
mHelper.prevPg = result.prevPage;
|
||||
mHelper.nextPg = result.nextPage;
|
||||
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
|
||||
|
||||
if (mDrawerAdapter != null) {
|
||||
mDrawerAdapter.notifyDataSetChanged();
|
||||
@@ -1169,6 +1158,9 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
private class FavoritesHelper extends GalleryInfoContentHelper {
|
||||
public int pgCounter = 0;
|
||||
public String prevPg = null;
|
||||
public String nextPg = null;
|
||||
|
||||
@Override
|
||||
protected void getPageData(final int taskId, int type, int page) {
|
||||
@@ -1176,6 +1168,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
if (null == activity || null == mUrlBuilder || null == mClient) {
|
||||
return;
|
||||
}
|
||||
pgCounter = page;
|
||||
|
||||
if (mEnableModify) {
|
||||
mEnableModify = false;
|
||||
@@ -1215,7 +1208,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
url = mUrlBuilder.build();
|
||||
}
|
||||
|
||||
mUrlBuilder.setIndex(page);
|
||||
mUrlBuilder.setNext(nextPg);
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_MODIFY_FAVORITES);
|
||||
request.setCallback(new GetFavoritesListener(getContext(),
|
||||
@@ -1228,7 +1221,19 @@ public class FavoritesScene extends BaseScene implements
|
||||
final String keyword = mUrlBuilder.getKeyword();
|
||||
SimpleHandler.getInstance().post(() -> onGetFavoritesLocal(keyword, taskId));
|
||||
} else {
|
||||
mUrlBuilder.setIndex(page);
|
||||
String prevPage = null, nextPage = null;
|
||||
if (page != 0) {
|
||||
if (page >= mHelper.getPageForTop())
|
||||
nextPage = nextPg;
|
||||
else
|
||||
prevPage = prevPg;
|
||||
}
|
||||
mUrlBuilder.setPrev(prevPage);
|
||||
mUrlBuilder.setNext(jumpTo == null ? nextPage : Integer.toString(minGid));
|
||||
|
||||
mUrlBuilder.setJumpTo(jumpTo);
|
||||
jumpTo = null;
|
||||
|
||||
String url = mUrlBuilder.build();
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||
@@ -1291,5 +1296,33 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClearData() {
|
||||
super.onClearData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeRefresh() {
|
||||
super.beforeRefresh();
|
||||
prevPg = null;
|
||||
nextPg = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable saveInstanceState(Parcelable superState) {
|
||||
Bundle bundle = (Bundle) super.saveInstanceState(superState);
|
||||
bundle.putString(KEY_PREV_PAGE, prevPg);
|
||||
bundle.putString(KEY_NEXT_PAGE, nextPg);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable restoreInstanceState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
prevPg = bundle.getString(KEY_PREV_PAGE);
|
||||
nextPg = bundle.getString(KEY_NEXT_PAGE);
|
||||
return super.restoreInstanceState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,7 +354,7 @@ public final class GalleryCommentsScene extends ToolbarScene
|
||||
}
|
||||
int id = menuId.get(which);
|
||||
if (id == R.id.copy) {
|
||||
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), comment.comment, false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
|
||||
ClipboardUtilKt.addTextToClipboard(requireActivity(), comment.comment, false);
|
||||
} else if (id == R.id.vote_up) {
|
||||
voteComment(comment.id, 1);
|
||||
} else if (id == R.id.vote_down) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
@@ -53,7 +54,6 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -65,6 +65,7 @@ import androidx.core.view.ViewCompat;
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.CheckBoxDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
import com.hippo.beerbelly.BeerBelly;
|
||||
import com.hippo.ehviewer.AppConfig;
|
||||
@@ -94,17 +95,21 @@ import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.dao.Filter;
|
||||
import com.hippo.ehviewer.gallery.EhGalleryProvider;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider2;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.ui.GalleryInfoBottomSheet;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
|
||||
import com.hippo.ehviewer.widget.GalleryRatingBar;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.text.URLImageGetter;
|
||||
import com.hippo.unifile.UniFile;
|
||||
import com.hippo.util.AppHelper;
|
||||
import com.hippo.util.ClipboardUtilKt;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.IoThreadPoolExecutor;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.view.ViewTransition;
|
||||
import com.hippo.widget.AutoWrapLayout;
|
||||
@@ -130,11 +135,9 @@ import java.util.List;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import rikka.core.res.ResourcesKt;
|
||||
|
||||
public class GalleryDetailScene extends ToolbarScene implements View.OnClickListener,
|
||||
com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener,
|
||||
View.OnLongClickListener {
|
||||
|
||||
public final static String KEY_ACTION = "action";
|
||||
public static final String ACTION_GALLERY_INFO = "action_gallery_info";
|
||||
public static final String ACTION_GID_TOKEN = "action_gid_token";
|
||||
@@ -203,8 +206,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
@Nullable
|
||||
private RatingBar mRating;
|
||||
@Nullable
|
||||
private View mHeartGroup;
|
||||
@Nullable
|
||||
private TextView mHeart;
|
||||
@Nullable
|
||||
private TextView mHeartOutline;
|
||||
@@ -427,6 +428,21 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -604,9 +620,8 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mNewerVersion = (TextView) ViewUtils.$$(mActions, R.id.newerVersion);
|
||||
mRatingText = (TextView) ViewUtils.$$(mActions, R.id.rating_text);
|
||||
mRating = (RatingBar) ViewUtils.$$(mActions, R.id.rating);
|
||||
mHeartGroup = ViewUtils.$$(mActions, R.id.heart_group);
|
||||
mHeart = (TextView) ViewUtils.$$(mHeartGroup, R.id.heart);
|
||||
mHeartOutline = (TextView) ViewUtils.$$(mHeartGroup, R.id.heart_outline);
|
||||
mHeart = (TextView) ViewUtils.$$(mActions, R.id.heart);
|
||||
mHeartOutline = (TextView) ViewUtils.$$(mActions, R.id.heart_outline);
|
||||
mTorrent = (TextView) ViewUtils.$$(mActions, R.id.torrent);
|
||||
mArchive = (TextView) ViewUtils.$$(mActions, R.id.archive);
|
||||
mShare = (TextView) ViewUtils.$$(mActions, R.id.share);
|
||||
@@ -614,15 +629,16 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mSimilar = (TextView) ViewUtils.$$(mActions, R.id.similar);
|
||||
mSearchCover = (TextView) ViewUtils.$$(mActions, R.id.search_cover);
|
||||
mNewerVersion.setOnClickListener(this);
|
||||
mHeartGroup.setOnClickListener(this);
|
||||
mHeartGroup.setOnLongClickListener(this);
|
||||
mHeart.setOnClickListener(this);
|
||||
mHeart.setOnLongClickListener(this);
|
||||
mHeartOutline.setOnClickListener(this);
|
||||
mHeartOutline.setOnLongClickListener(this);
|
||||
mTorrent.setOnClickListener(this);
|
||||
mArchive.setOnClickListener(this);
|
||||
mShare.setOnClickListener(this);
|
||||
mRate.setOnClickListener(this);
|
||||
mSimilar.setOnClickListener(this);
|
||||
mSearchCover.setOnClickListener(this);
|
||||
ensureActionDrawable();
|
||||
|
||||
mTags = (LinearLayout) ViewUtils.$$(belowHeader, R.id.tags);
|
||||
mNoTags = (TextView) ViewUtils.$$(mTags, R.id.no_tags);
|
||||
@@ -695,7 +711,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
mNewerVersion = null;
|
||||
mRatingText = null;
|
||||
mRating = null;
|
||||
mHeartGroup = null;
|
||||
mHeart = null;
|
||||
mHeartOutline = null;
|
||||
mTorrent = null;
|
||||
@@ -769,25 +784,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setActionDrawable(@Nullable TextView text, @DrawableRes int resId) {
|
||||
if (text == null) return;
|
||||
Context context = text.getContext();
|
||||
Drawable drawable = ContextCompat.getDrawable(context, resId);
|
||||
if (drawable == null) return;
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
text.setCompoundDrawables(null, drawable, null, null);
|
||||
}
|
||||
|
||||
private void ensureActionDrawable() {
|
||||
setActionDrawable(mHeart, R.drawable.v_heart_primary_x48);
|
||||
setActionDrawable(mHeartOutline, R.drawable.v_heart_outline_primary_x48);
|
||||
setActionDrawable(mTorrent, R.drawable.v_utorrent_primary_x48);
|
||||
setActionDrawable(mArchive, R.drawable.v_archive_primary_x48);
|
||||
setActionDrawable(mShare, R.drawable.v_share_primary_x48);
|
||||
setActionDrawable(mSimilar, R.drawable.v_similar_primary_x48);
|
||||
setActionDrawable(mSearchCover, R.drawable.v_file_find_primary_x48);
|
||||
}
|
||||
|
||||
private boolean createCircularReveal() {
|
||||
if (mColorBg == null) {
|
||||
return false;
|
||||
@@ -1158,7 +1154,6 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
lub.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
|
||||
lub.setImagePath(temp.getPath());
|
||||
lub.setUseSimilarityScan(true);
|
||||
lub.setShowExpunged(true);
|
||||
GalleryListScene.startScene(this, lub);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
@@ -1203,10 +1198,14 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
if (EhApplication.getDownloadManager(context).getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
|
||||
CommonOperations.startDownload(activity, galleryInfo, false);
|
||||
} else {
|
||||
new BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
.setMessage(getString(R.string.download_remove_dialog_message, galleryInfo.title))
|
||||
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> EhApplication.getDownloadManager(context).deleteDownload(galleryInfo.gid))
|
||||
CheckBoxDialogBuilder builder = new CheckBoxDialogBuilder(context,
|
||||
getString(R.string.download_remove_dialog_message, galleryInfo.title),
|
||||
getString(R.string.download_remove_dialog_check_text),
|
||||
Settings.getRemoveImageFiles());
|
||||
DeleteDialogHelper helper = new DeleteDialogHelper(
|
||||
EhApplication.getDownloadManager(context), galleryInfo, builder);
|
||||
builder.setTitle(R.string.download_remove_dialog_title)
|
||||
.setPositiveButton(android.R.string.ok, helper)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -1241,10 +1240,10 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
.show();
|
||||
}
|
||||
} else if (mInfo == v) {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(GalleryInfoScene.KEY_GALLERY_DETAIL, mGalleryDetail);
|
||||
startScene(new Announcer(GalleryInfoScene.class).setArgs(args));
|
||||
} else if (mHeartGroup == v) {
|
||||
assert mGalleryDetail != null;
|
||||
var galleryInfoBottomSheet = new GalleryInfoBottomSheet(mGalleryDetail);
|
||||
galleryInfoBottomSheet.show(requireActivity().getSupportFragmentManager(), GalleryInfoBottomSheet.TAG);
|
||||
} else if (mHeart == v || mHeartOutline == v) {
|
||||
if (mGalleryDetail != null && !mModifingFavorites) {
|
||||
boolean remove = false;
|
||||
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
|
||||
@@ -1454,9 +1453,9 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
} else if (id == R.id.add_filter) {
|
||||
showFilterTagDialog(tag);
|
||||
} else if (id == R.id.copy) {
|
||||
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), tag, false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
|
||||
ClipboardUtilKt.addTextToClipboard(requireActivity(), tag, false);
|
||||
} else if (id == R.id.copy_trans) {
|
||||
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), tv.getText().toString(), false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
|
||||
ClipboardUtilKt.addTextToClipboard(requireActivity(), tv.getText().toString(), false);
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
@@ -1491,7 +1490,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
CommonOperations.startDownload(activity, galleryInfo, true);
|
||||
}
|
||||
return true;
|
||||
} else if (mHeartGroup == v) {
|
||||
} else if (mHeart == v || mHeartOutline == v) {
|
||||
if (mGalleryDetail != null && !mModifingFavorites) {
|
||||
boolean remove = false;
|
||||
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
|
||||
@@ -2159,4 +2158,37 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
EhApplication.getEhClient(context).execute(request);
|
||||
}
|
||||
}
|
||||
private class DeleteDialogHelper implements DialogInterface.OnClickListener{
|
||||
private final com.hippo.ehviewer.download.DownloadManager mDownloadManager;
|
||||
private final GalleryInfo mGalleryInfo;
|
||||
private final CheckBoxDialogBuilder mBuilder;
|
||||
|
||||
public DeleteDialogHelper(com.hippo.ehviewer.download.DownloadManager downloadManager,
|
||||
GalleryInfo galleryInfo, CheckBoxDialogBuilder builder) {
|
||||
mDownloadManager = downloadManager;
|
||||
mGalleryInfo = galleryInfo;
|
||||
mBuilder = builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (which != DialogInterface.BUTTON_POSITIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete
|
||||
if (null != mDownloadManager) {
|
||||
mDownloadManager.deleteDownload(mGalleryInfo.gid);
|
||||
}
|
||||
|
||||
// Delete image files
|
||||
boolean checked = mBuilder.isChecked();
|
||||
Settings.putRemoveImageFiles(checked);
|
||||
if (checked) {
|
||||
EhDB.removeDownloadDirname(mGalleryInfo.gid);
|
||||
UniFile file = SpiderDen.getGalleryDownloadDir(mGalleryInfo);
|
||||
deleteFileAsync(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.ui.scene;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
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.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.UrlOpener;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.util.ClipboardUtilKt;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class GalleryInfoScene extends ToolbarScene {
|
||||
|
||||
public static final String KEY_GALLERY_DETAIL = "gallery_detail";
|
||||
public static final String KEY_KEYS = "keys";
|
||||
public static final String KEY_VALUES = "values";
|
||||
|
||||
private static final int INDEX_URL = 3;
|
||||
private static final int INDEX_PARENT = 10;
|
||||
|
||||
/*---------------
|
||||
Whole life cycle
|
||||
---------------*/
|
||||
@Nullable
|
||||
private ArrayList<String> mKeys;
|
||||
@Nullable
|
||||
private ArrayList<String> mValues;
|
||||
|
||||
@Nullable
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
onInit();
|
||||
} else {
|
||||
onRestore(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlerArgs(Bundle args) {
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
GalleryDetail gd = args.getParcelable(KEY_GALLERY_DETAIL);
|
||||
if (gd == null) {
|
||||
return;
|
||||
}
|
||||
if (mKeys == null || mValues == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mKeys.add(getString(R.string.header_key));
|
||||
mValues.add(getString(R.string.header_value));
|
||||
mKeys.add(getString(R.string.key_gid));
|
||||
mValues.add(Long.toString(gd.gid));
|
||||
mKeys.add(getString(R.string.key_token));
|
||||
mValues.add(gd.token);
|
||||
mKeys.add(getString(R.string.key_url));
|
||||
mValues.add(EhUrl.getGalleryDetailUrl(gd.gid, gd.token));
|
||||
mKeys.add(getString(R.string.key_title));
|
||||
mValues.add(gd.title);
|
||||
mKeys.add(getString(R.string.key_title_jpn));
|
||||
mValues.add(gd.titleJpn);
|
||||
mKeys.add(getString(R.string.key_thumb));
|
||||
mValues.add(gd.thumb);
|
||||
mKeys.add(getString(R.string.key_category));
|
||||
mValues.add(EhUtils.getCategory(gd.category));
|
||||
mKeys.add(getString(R.string.key_uploader));
|
||||
mValues.add(gd.uploader);
|
||||
mKeys.add(getString(R.string.key_posted));
|
||||
mValues.add(gd.posted);
|
||||
mKeys.add(getString(R.string.key_parent));
|
||||
mValues.add(gd.parent);
|
||||
mKeys.add(getString(R.string.key_visible));
|
||||
mValues.add(gd.visible);
|
||||
mKeys.add(getString(R.string.key_language));
|
||||
mValues.add(gd.language);
|
||||
mKeys.add(getString(R.string.key_pages));
|
||||
mValues.add(Integer.toString(gd.pages));
|
||||
mKeys.add(getString(R.string.key_size));
|
||||
mValues.add(gd.size);
|
||||
mKeys.add(getString(R.string.key_favorite_count));
|
||||
mValues.add(Integer.toString(gd.favoriteCount));
|
||||
mKeys.add(getString(R.string.key_favorited));
|
||||
mValues.add(Boolean.toString(gd.isFavorited));
|
||||
mKeys.add(getString(R.string.key_rating_count));
|
||||
mValues.add(Integer.toString(gd.ratingCount));
|
||||
mKeys.add(getString(R.string.key_rating));
|
||||
mValues.add(Float.toString(gd.rating));
|
||||
mKeys.add(getString(R.string.key_torrents));
|
||||
mValues.add(Integer.toString(gd.torrentCount));
|
||||
mKeys.add(getString(R.string.key_torrent_url));
|
||||
mValues.add(gd.torrentUrl);
|
||||
mKeys.add(getString(R.string.favorite_name));
|
||||
mValues.add(gd.favoriteName);
|
||||
}
|
||||
|
||||
protected void onInit() {
|
||||
mKeys = new ArrayList<>();
|
||||
mValues = new ArrayList<>();
|
||||
handlerArgs(getArguments());
|
||||
}
|
||||
|
||||
protected void onRestore(@NonNull Bundle savedInstanceState) {
|
||||
mKeys = savedInstanceState.getStringArrayList(KEY_KEYS);
|
||||
mValues = savedInstanceState.getStringArrayList(KEY_VALUES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putStringArrayList(KEY_KEYS, mKeys);
|
||||
outState.putStringArrayList(KEY_VALUES, mValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateViewWithToolbar(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_gallery_info, container, false);
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
|
||||
mRecyclerView = (EasyRecyclerView) ViewUtils.$$(view, R.id.recycler_view);
|
||||
InfoAdapter adapter = new InfoAdapter();
|
||||
mRecyclerView.setAdapter(adapter);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.VERTICAL, false));
|
||||
mRecyclerView.setClipToPadding(false);
|
||||
mRecyclerView.setHasFixedSize(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setTitle(R.string.gallery_info);
|
||||
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView.stopScroll();
|
||||
mRecyclerView = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onItemClick(int position) {
|
||||
Context context = getContext();
|
||||
if (null != context && 0 != position && null != mValues) {
|
||||
if (position == INDEX_PARENT) {
|
||||
UrlOpener.openUrl(context, mValues.get(position), true);
|
||||
} else {
|
||||
ClipboardUtilKt.addTextToClipboard(ClipboardUtilKt.getClipboardManager(getContext()), mValues.get(position), false, () -> showTip(R.string.copied_to_clipboard, LENGTH_SHORT));
|
||||
if (position == INDEX_URL) {
|
||||
// Save it to avoid detect the gallery
|
||||
Settings.putClipboardTextHashCode(mValues.get(position).hashCode());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNavigationClick() {
|
||||
onBackPressed();
|
||||
}
|
||||
|
||||
private static class InfoHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final TextView key;
|
||||
private final TextView value;
|
||||
|
||||
public InfoHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
key = itemView.findViewById(R.id.key);
|
||||
value = itemView.findViewById(R.id.value);
|
||||
}
|
||||
}
|
||||
|
||||
private class InfoAdapter extends RecyclerView.Adapter<InfoHolder> {
|
||||
|
||||
private static final int TYPE_HEADER = 0;
|
||||
private static final int TYPE_DATA = 1;
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public InfoAdapter() {
|
||||
mInflater = getLayoutInflater();
|
||||
AssertUtils.assertNotNull(mInflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == 0) {
|
||||
return TYPE_HEADER;
|
||||
} else {
|
||||
return TYPE_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public InfoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new InfoHolder(mInflater.inflate(viewType == TYPE_HEADER ?
|
||||
R.layout.item_gallery_info_header : R.layout.item_gallery_info_data, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull InfoHolder holder, int position) {
|
||||
if (mKeys != null && mValues != null) {
|
||||
holder.key.setText(mKeys.get(position));
|
||||
holder.value.setText(mValues.get(position));
|
||||
holder.itemView.setEnabled(position != 0);
|
||||
holder.itemView.setOnClickListener(v -> onItemClick(position));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mKeys == null || mValues == null ? 0 : Math.min(mKeys.size(), mValues.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.datepicker.MaterialDatePicker;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.app.EditTextDialogBuilder;
|
||||
@@ -91,6 +92,7 @@ import com.hippo.ehviewer.widget.SearchBar;
|
||||
import com.hippo.ehviewer.widget.SearchLayout;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.view.BringOutTransition;
|
||||
import com.hippo.view.ViewTransition;
|
||||
@@ -491,6 +493,8 @@ public final class GalleryListScene extends BaseScene
|
||||
mFabLayout.getSecondaryFabAt(0).setImageResource(isTopList ? R.drawable.ic_baseline_format_list_numbered_24 : R.drawable.v_magnify_x24);
|
||||
}
|
||||
|
||||
mFabLayout.setSecondaryFabVisibilityAt(1, ListUrlBuilder.MODE_WHATS_HOT != builder.getMode());
|
||||
|
||||
// Update normal search mode
|
||||
mSearchLayout.setNormalSearchMode(builder.getMode() == ListUrlBuilder.MODE_SUBSCRIPTION
|
||||
? R.id.search_subscription_search
|
||||
@@ -778,6 +782,21 @@ public final class GalleryListScene extends BaseScene
|
||||
return view;
|
||||
}
|
||||
|
||||
public boolean isBackpressCanPreviewLauncherStatus() {
|
||||
if (null != mFabLayout && mFabLayout.isExpanded()) {
|
||||
return false;
|
||||
}
|
||||
switch (mState) {
|
||||
case STATE_NORMAL:
|
||||
return getStackIndex() == 0;
|
||||
case STATE_SIMPLE_SEARCH:
|
||||
case STATE_SEARCH:
|
||||
case STATE_SEARCH_SHOW_LIST:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (null != mFabLayout && mFabLayout.isExpanded()) {
|
||||
@@ -841,36 +860,47 @@ public final class GalleryListScene extends BaseScene
|
||||
return;
|
||||
}
|
||||
|
||||
final int page = mHelper.getPageForTop();
|
||||
final int pages = mHelper.getPages();
|
||||
String hint = getString(R.string.go_to_hint, page + 1, pages);
|
||||
final EditTextDialogBuilder builder = new EditTextDialogBuilder(context, null, hint);
|
||||
builder.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
final AlertDialog dialog = builder.setTitle(R.string.go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (null == mHelper) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
if (ListUrlBuilder.MODE_TOPLIST == mUrlBuilder.getMode()) {
|
||||
final int page = mHelper.getPageForTop() + 1;
|
||||
final int pages = 200;
|
||||
String hint = getString(R.string.go_to_hint, page, pages);
|
||||
final EditTextDialogBuilder builder = new EditTextDialogBuilder(context, null, hint);
|
||||
builder.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
final AlertDialog dialog = builder.setTitle(R.string.go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
if (null == mHelper) {
|
||||
dialog.dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
String text = builder.getText().trim();
|
||||
int goTo;
|
||||
try {
|
||||
goTo = Integer.parseInt(text) - 1;
|
||||
} catch (NumberFormatException e) {
|
||||
builder.setError(getString(R.string.error_invalid_number));
|
||||
return;
|
||||
}
|
||||
if (goTo < 0 || goTo >= pages) {
|
||||
builder.setError(getString(R.string.error_out_of_range));
|
||||
return;
|
||||
}
|
||||
builder.setError(null);
|
||||
mHelper.goTo(goTo);
|
||||
dialog.dismiss();
|
||||
});
|
||||
String text = builder.getText().trim();
|
||||
int goTo;
|
||||
try {
|
||||
goTo = Integer.parseInt(text) - 1;
|
||||
} catch (NumberFormatException e) {
|
||||
builder.setError(getString(R.string.error_invalid_number));
|
||||
return;
|
||||
}
|
||||
if (goTo < 0 || goTo >= pages) {
|
||||
builder.setError(getString(R.string.error_out_of_range));
|
||||
return;
|
||||
}
|
||||
builder.setError(null);
|
||||
mHelper.goToPage(goTo);
|
||||
dialog.dismiss();
|
||||
});
|
||||
} else {
|
||||
var datePicker = MaterialDatePicker.Builder.datePicker()
|
||||
.setTitleText(R.string.go_to)
|
||||
.setSelection(MaterialDatePicker.todayInUtcMilliseconds())
|
||||
.build();
|
||||
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
|
||||
datePicker.addOnPositiveButtonClickListener(v -> {
|
||||
mHelper.goTo(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1184,6 +1214,7 @@ public final class GalleryListScene extends BaseScene
|
||||
break;
|
||||
}
|
||||
}
|
||||
((StageActivity)requireActivity()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1374,7 +1405,16 @@ public final class GalleryListScene extends BaseScene
|
||||
? R.string.gallery_list_empty_hit_subscription
|
||||
: R.string.gallery_list_empty_hit);
|
||||
mHelper.setEmptyString(emptyString);
|
||||
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
|
||||
|
||||
int pages = 0;
|
||||
if (mIsTopList)
|
||||
pages = 200;
|
||||
else if (result.nextGid == 0)
|
||||
pages = mHelper.pgCounter + 1;
|
||||
else
|
||||
pages = result.founds;
|
||||
|
||||
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,7 +1587,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
mUrlBuilder.set(mQuickSearchList.get(position));
|
||||
mUrlBuilder.setPageIndex(0);
|
||||
mUrlBuilder.setNextGid(0);
|
||||
onUpdateUrlBuilder();
|
||||
mHelper.refresh();
|
||||
setState(STATE_NORMAL);
|
||||
@@ -1564,7 +1604,7 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
mUrlBuilder.setKeyword(String.valueOf(keywords[position]));
|
||||
mUrlBuilder.setPageIndex(0);
|
||||
mUrlBuilder.setNextGid(0);
|
||||
onUpdateUrlBuilder();
|
||||
mHelper.refresh();
|
||||
setState(STATE_NORMAL);
|
||||
@@ -1694,15 +1734,37 @@ public final class GalleryListScene extends BaseScene
|
||||
}
|
||||
|
||||
private class GalleryListHelper extends GalleryInfoContentHelper {
|
||||
public int pgCounter = 0;
|
||||
|
||||
@Override
|
||||
protected void getPageData(int taskId, int type, int page) {
|
||||
pgCounter = page;
|
||||
MainActivity activity = getMainActivity();
|
||||
if (null == activity || null == mClient || null == mUrlBuilder) {
|
||||
return;
|
||||
}
|
||||
|
||||
mUrlBuilder.setPageIndex(page);
|
||||
if (mIsTopList) {
|
||||
mUrlBuilder.setNextGid(page);
|
||||
if (jumpTo != null) {
|
||||
pgCounter = Integer.parseInt(jumpTo);
|
||||
mUrlBuilder.setNextGid(pgCounter);
|
||||
}
|
||||
} else {
|
||||
int prevGid = 0, nextGid = 0;
|
||||
if (page != 0) {
|
||||
if (page >= mHelper.getPageForTop())
|
||||
nextGid = minGid;
|
||||
else
|
||||
prevGid = maxGid;
|
||||
}
|
||||
mUrlBuilder.setPrevGid(prevGid);
|
||||
mUrlBuilder.setNextGid(nextGid);
|
||||
}
|
||||
|
||||
mUrlBuilder.setJumpTo(jumpTo);
|
||||
jumpTo = null;
|
||||
|
||||
if (ListUrlBuilder.MODE_IMAGE_SEARCH == mUrlBuilder.getMode()) {
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_IMAGE_SEARCH);
|
||||
@@ -1815,4 +1877,4 @@ public final class GalleryListScene extends BaseScene
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import android.view.*
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
@@ -39,7 +38,6 @@ import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.easyrecyclerview.FastScroller
|
||||
import com.hippo.easyrecyclerview.HandlerDrawable
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration
|
||||
import com.hippo.ehviewer.*
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
@@ -64,17 +62,57 @@ import rikka.core.res.resolveColor
|
||||
|
||||
class HistoryScene : ToolbarScene() {
|
||||
private var mRecyclerView: EasyRecyclerView? = null
|
||||
private var mViewTransition: ViewTransition? = null
|
||||
private var mAdapter: HistoryAdapter? = null
|
||||
private var mDownloadManager: DownloadManager? = null
|
||||
private var mDownloadInfoListener: DownloadInfoListener? = null
|
||||
private var mFavouriteStatusRouter: FavouriteStatusRouter? = null
|
||||
private var mFavouriteStatusRouterListener: FavouriteStatusRouter.Listener? = null
|
||||
private val mAdapter: HistoryAdapter by lazy {
|
||||
HistoryAdapter(object : DiffUtil.ItemCallback<HistoryInfo>() {
|
||||
override fun areItemsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
|
||||
return oldItem.gid == newItem.gid
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
|
||||
return oldItem.gid == newItem.gid
|
||||
}
|
||||
})
|
||||
}
|
||||
private val mDownloadManager: DownloadManager by lazy {
|
||||
EhApplication.getDownloadManager(requireContext())
|
||||
}
|
||||
private val mDownloadInfoListener: DownloadInfoListener by lazy {
|
||||
object : DownloadInfoListener {
|
||||
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>) {}
|
||||
override fun onUpdateAll() {}
|
||||
override fun onReload() {
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onChange() {
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onRenameLabel(from: String, to: String) {}
|
||||
override fun onRemove(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onUpdateLabels() {}
|
||||
}
|
||||
}
|
||||
private val mFavouriteStatusRouter: FavouriteStatusRouter by lazy {
|
||||
EhApplication.getFavouriteStatusRouter(requireContext())
|
||||
}
|
||||
private val mFavouriteStatusRouterListener: FavouriteStatusRouter.Listener by lazy {
|
||||
FavouriteStatusRouter.Listener { _: Long, _: Int ->
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mDownloadManager!!.removeDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouter!!.removeListener(mFavouriteStatusRouterListener)
|
||||
mDownloadManager.removeDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener)
|
||||
}
|
||||
|
||||
override fun getNavCheckedItem(): Int {
|
||||
@@ -84,36 +122,8 @@ class HistoryScene : ToolbarScene() {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val context = requireContext()
|
||||
mDownloadManager = EhApplication.getDownloadManager(context)
|
||||
mFavouriteStatusRouter = EhApplication.getFavouriteStatusRouter(context)
|
||||
mDownloadInfoListener = object : DownloadInfoListener {
|
||||
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>) {}
|
||||
override fun onUpdateAll() {}
|
||||
override fun onReload() {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onChange() {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onRenameLabel(from: String, to: String) {}
|
||||
override fun onRemove(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onUpdateLabels() {}
|
||||
}
|
||||
mDownloadManager!!.addDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouterListener = FavouriteStatusRouter.Listener { _: Long, _: Int ->
|
||||
mAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
mFavouriteStatusRouter!!.addListener(mFavouriteStatusRouterListener)
|
||||
mDownloadManager.addDownloadInfoListener(mDownloadInfoListener)
|
||||
mFavouriteStatusRouter.addListener(mFavouriteStatusRouterListener)
|
||||
}
|
||||
|
||||
override fun onCreateViewWithToolbar(
|
||||
@@ -125,7 +135,7 @@ class HistoryScene : ToolbarScene() {
|
||||
val recyclerView = ViewUtils.`$$`(content, R.id.recycler_view) as EasyRecyclerView
|
||||
val mFastScroller = ViewUtils.`$$`(content, R.id.fast_scroller) as FastScroller
|
||||
val mTip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
mViewTransition = ViewTransition(content, mTip)
|
||||
ViewTransition(content, mTip)
|
||||
val resources = requireContext().resources
|
||||
val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.big_history)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
@@ -135,15 +145,6 @@ class HistoryScene : ToolbarScene() {
|
||||
) {
|
||||
EhDB.getHistoryLazyList()
|
||||
}.flow.cachedIn(viewLifecycleOwner.lifecycleScope)
|
||||
mAdapter = HistoryAdapter(object : DiffUtil.ItemCallback<HistoryInfo>() {
|
||||
override fun areItemsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
|
||||
return oldItem.gid == newItem.gid
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: HistoryInfo, newItem: HistoryInfo): Boolean {
|
||||
return oldItem.gid == newItem.gid
|
||||
}
|
||||
})
|
||||
recyclerView.adapter = mAdapter
|
||||
val layoutManager = AutoStaggeredGridLayoutManager(
|
||||
0, StaggeredGridLayoutManager.VERTICAL
|
||||
@@ -153,11 +154,6 @@ class HistoryScene : ToolbarScene() {
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.clipChildren = false
|
||||
val interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval)
|
||||
val paddingH = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h)
|
||||
val paddingV = resources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v)
|
||||
val decoration = MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV)
|
||||
recyclerView.addItemDecoration(decoration)
|
||||
val itemTouchHelper = ItemTouchHelper(HistoryItemTouchHelperCallback())
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView)
|
||||
mFastScroller.attachToRecyclerView(recyclerView)
|
||||
@@ -167,7 +163,7 @@ class HistoryScene : ToolbarScene() {
|
||||
mFastScroller.setHandlerDrawable(handlerDrawable)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
historyData.collectLatest { value ->
|
||||
mAdapter!!.submitData(
|
||||
mAdapter.submitData(
|
||||
value
|
||||
)
|
||||
}
|
||||
@@ -185,8 +181,6 @@ class HistoryScene : ToolbarScene() {
|
||||
super.onDestroyView()
|
||||
mRecyclerView?.stopScroll()
|
||||
mRecyclerView = null
|
||||
mViewTransition = null
|
||||
mAdapter = null
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
@@ -203,20 +197,18 @@ class HistoryScene : ToolbarScene() {
|
||||
BaseDialogBuilder(requireContext())
|
||||
.setMessage(R.string.clear_all_history)
|
||||
.setPositiveButton(R.string.clear_all) { _: DialogInterface?, which: Int ->
|
||||
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
|
||||
if (DialogInterface.BUTTON_POSITIVE != which) {
|
||||
return@setPositiveButton
|
||||
}
|
||||
EhDB.clearHistoryInfo()
|
||||
mAdapter!!.refresh()
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
mAdapter.refresh()
|
||||
mAdapter.notifyDataSetChanged()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
// Skip when in choice mode
|
||||
context ?: return false
|
||||
val id = item.itemId
|
||||
if (id == R.id.action_clear_all) {
|
||||
showClearAllDialog()
|
||||
@@ -225,20 +217,19 @@ class HistoryScene : ToolbarScene() {
|
||||
return false
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
fun onItemClick(gi: GalleryInfo): Boolean {
|
||||
val args = Bundle()
|
||||
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO)
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, mAdapter?.peek(position))
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, gi)
|
||||
val announcer = Announcer(GalleryDetailScene::class.java).setArgs(args)
|
||||
startScene(announcer)
|
||||
return true
|
||||
}
|
||||
|
||||
fun onItemLongClick(position: Int): Boolean {
|
||||
fun onItemLongClick(gi: GalleryInfo): Boolean {
|
||||
val context = requireContext()
|
||||
val activity = mainActivity ?: return false
|
||||
val gi: GalleryInfo = mAdapter?.peek(position) ?: return true
|
||||
val downloaded = mDownloadManager!!.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID
|
||||
val downloaded = mDownloadManager.getDownloadState(gi.gid) != DownloadInfo.STATE_INVALID
|
||||
val favourited = gi.favoriteSlot != -2
|
||||
val items = if (downloaded) arrayOf<CharSequence>(
|
||||
context.getString(R.string.read),
|
||||
@@ -276,6 +267,7 @@ class HistoryScene : ToolbarScene() {
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
1 -> if (downloaded) {
|
||||
BaseDialogBuilder(context)
|
||||
.setTitle(R.string.download_remove_dialog_title)
|
||||
@@ -286,7 +278,7 @@ class HistoryScene : ToolbarScene() {
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
mDownloadManager!!.deleteDownload(
|
||||
mDownloadManager.deleteDownload(
|
||||
gi.gid
|
||||
)
|
||||
}
|
||||
@@ -294,6 +286,7 @@ class HistoryScene : ToolbarScene() {
|
||||
} else {
|
||||
CommonOperations.startDownload(activity, gi, false)
|
||||
}
|
||||
|
||||
2 -> if (favourited) {
|
||||
CommonOperations.removeFromFavorites(
|
||||
activity,
|
||||
@@ -307,6 +300,7 @@ class HistoryScene : ToolbarScene() {
|
||||
AddToFavoriteListener(context, activity.stageId, tag)
|
||||
)
|
||||
}
|
||||
|
||||
3 -> {
|
||||
val labelRawList = EhApplication.getDownloadManager(context).labelList
|
||||
val labelList: MutableList<String> = ArrayList(labelRawList.size + 1)
|
||||
@@ -362,29 +356,16 @@ class HistoryScene : ToolbarScene() {
|
||||
}
|
||||
|
||||
private class HistoryHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val card: View
|
||||
val thumb: LoadImageView
|
||||
val title: TextView
|
||||
val uploader: TextView
|
||||
val rating: SimpleRatingView
|
||||
val category: TextView
|
||||
val posted: TextView
|
||||
val simpleLanguage: TextView
|
||||
val pages: TextView
|
||||
val downloaded: ImageView
|
||||
|
||||
init {
|
||||
card = itemView.findViewById(R.id.card)
|
||||
thumb = itemView.findViewById(R.id.thumb)
|
||||
title = itemView.findViewById(R.id.title)
|
||||
uploader = itemView.findViewById(R.id.uploader)
|
||||
rating = itemView.findViewById(R.id.rating)
|
||||
category = itemView.findViewById(R.id.category)
|
||||
posted = itemView.findViewById(R.id.posted)
|
||||
simpleLanguage = itemView.findViewById(R.id.simple_language)
|
||||
pages = itemView.findViewById(R.id.pages)
|
||||
downloaded = itemView.findViewById(R.id.downloaded)
|
||||
}
|
||||
val card: View = itemView.findViewById(R.id.card)
|
||||
val thumb: LoadImageView = itemView.findViewById(R.id.thumb)
|
||||
val title: TextView = itemView.findViewById(R.id.title)
|
||||
val uploader: TextView = itemView.findViewById(R.id.uploader)
|
||||
val rating: SimpleRatingView = itemView.findViewById(R.id.rating)
|
||||
val category: TextView = itemView.findViewById(R.id.category)
|
||||
val posted: TextView = itemView.findViewById(R.id.posted)
|
||||
val simpleLanguage: TextView = itemView.findViewById(R.id.simple_language)
|
||||
val pages: TextView = itemView.findViewById(R.id.pages)
|
||||
val downloaded: ImageView = itemView.findViewById(R.id.downloaded)
|
||||
}
|
||||
|
||||
private inner class MoveDialogHelper(
|
||||
@@ -432,7 +413,7 @@ class HistoryScene : ToolbarScene() {
|
||||
holder.rating.rating = gi.rating
|
||||
val category = holder.category
|
||||
val newCategoryText = EhUtils.getCategory(gi.category)
|
||||
if (!newCategoryText!!.contentEquals(category.text)) {
|
||||
if (!newCategoryText.contentEquals(category.text)) {
|
||||
category.text = newCategoryText
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category))
|
||||
}
|
||||
@@ -447,16 +428,9 @@ class HistoryScene : ToolbarScene() {
|
||||
holder.simpleLanguage.visibility = View.VISIBLE
|
||||
}
|
||||
holder.downloaded.visibility =
|
||||
if (mDownloadManager!!.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
|
||||
|
||||
// Update transition name
|
||||
val gid = gi.gid
|
||||
ViewCompat.setTransitionName(
|
||||
holder.thumb,
|
||||
TransitionNameFactory.getThumbTransitionName(gid)
|
||||
)
|
||||
holder.card.setOnClickListener { onItemClick(position) }
|
||||
holder.card.setOnLongClickListener { onItemLongClick(position) }
|
||||
if (mDownloadManager.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
|
||||
holder.card.setOnClickListener { onItemClick(gi) }
|
||||
holder.card.setOnLongClickListener { onItemLongClick(gi) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -478,12 +452,9 @@ class HistoryScene : ToolbarScene() {
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val mPosition = viewHolder.bindingAdapterPosition
|
||||
if (mAdapter == null) {
|
||||
return
|
||||
}
|
||||
val info: HistoryInfo = mAdapter!!.peek(mPosition)!!
|
||||
EhDB.deleteHistoryInfo(info)
|
||||
mAdapter!!.refresh()
|
||||
val info: HistoryInfo? = mAdapter.peek(mPosition)
|
||||
info?.let { EhDB.deleteHistoryInfo(info) }
|
||||
mAdapter.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,8 @@ import com.hippo.yorozuya.NumberUtils
|
||||
class AdvanceSearchTable @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : LinearLayout(context, attrs) {
|
||||
private var mSname: CheckBox
|
||||
private var mStags: CheckBox
|
||||
private var mSdesc: CheckBox
|
||||
private var mStorr: CheckBox
|
||||
private var mSto: CheckBox
|
||||
private var mSdt1: CheckBox
|
||||
private var mSdt2: CheckBox
|
||||
private var mSh: CheckBox
|
||||
private var mSto: CheckBox
|
||||
private var mSr: CheckBox
|
||||
private var mMinRating: TextInputLayout
|
||||
private var mSp: CheckBox
|
||||
@@ -51,28 +45,19 @@ class AdvanceSearchTable @JvmOverloads constructor(
|
||||
val inflater = LayoutInflater.from(context)
|
||||
inflater.inflate(R.layout.widget_advance_search_table, this)
|
||||
val row0 = getChildAt(0) as ViewGroup
|
||||
mSname = row0.getChildAt(0) as CheckBox
|
||||
mStags = row0.getChildAt(1) as CheckBox
|
||||
mSh = row0.getChildAt(0) as CheckBox
|
||||
mSto = row0.getChildAt(1) as CheckBox
|
||||
val row1 = getChildAt(1) as ViewGroup
|
||||
mSdesc = row1.getChildAt(0) as CheckBox
|
||||
mStorr = row1.getChildAt(1) as CheckBox
|
||||
mSr = row1.getChildAt(0) as CheckBox
|
||||
mMinRating = row1.getChildAt(1) as TextInputLayout
|
||||
val row2 = getChildAt(2) as ViewGroup
|
||||
mSto = row2.getChildAt(0) as CheckBox
|
||||
mSdt1 = row2.getChildAt(1) as CheckBox
|
||||
val row3 = getChildAt(3) as ViewGroup
|
||||
mSdt2 = row3.getChildAt(0) as CheckBox
|
||||
mSh = row3.getChildAt(1) as CheckBox
|
||||
mSp = row2.getChildAt(0) as CheckBox
|
||||
mSpf = row2.getChildAt(1) as EditText
|
||||
mSpt = row2.getChildAt(3) as EditText
|
||||
val row4 = getChildAt(4) as ViewGroup
|
||||
mSr = row4.getChildAt(0) as CheckBox
|
||||
mMinRating = row4.getChildAt(1) as TextInputLayout
|
||||
val row5 = getChildAt(5) as ViewGroup
|
||||
mSp = row5.getChildAt(0) as CheckBox
|
||||
mSpf = row5.getChildAt(1) as EditText
|
||||
mSpt = row5.getChildAt(3) as EditText
|
||||
val row7 = getChildAt(7) as ViewGroup
|
||||
mSfl = row7.getChildAt(0) as CheckBox
|
||||
mSfu = row7.getChildAt(1) as CheckBox
|
||||
mSft = row7.getChildAt(2) as CheckBox
|
||||
mSfl = row4.getChildAt(0) as CheckBox
|
||||
mSfu = row4.getChildAt(1) as CheckBox
|
||||
mSft = row4.getChildAt(2) as CheckBox
|
||||
|
||||
mSpt.setOnEditorActionListener { v: TextView, _: Int, _: KeyEvent? ->
|
||||
val nextView = v.focusSearch(
|
||||
@@ -98,31 +83,16 @@ class AdvanceSearchTable @JvmOverloads constructor(
|
||||
var advanceSearch: Int
|
||||
get() {
|
||||
var advanceSearch = 0
|
||||
if (mSname.isChecked) advanceSearch = advanceSearch or SNAME
|
||||
if (mStags.isChecked) advanceSearch = advanceSearch or STAGS
|
||||
if (mSdesc.isChecked) advanceSearch = advanceSearch or SDESC
|
||||
if (mStorr.isChecked) advanceSearch = advanceSearch or STORR
|
||||
if (mSto.isChecked) advanceSearch = advanceSearch or STO
|
||||
if (mSdt1.isChecked) advanceSearch = advanceSearch or SDT1
|
||||
if (mSdt2.isChecked) advanceSearch = advanceSearch or SDT2
|
||||
if (mSh.isChecked) advanceSearch = advanceSearch or SH
|
||||
if (mSto.isChecked) advanceSearch = advanceSearch or STO
|
||||
if (mSfl.isChecked) advanceSearch = advanceSearch or SFL
|
||||
if (mSfu.isChecked) advanceSearch = advanceSearch or SFU
|
||||
if (mSft.isChecked) advanceSearch = advanceSearch or SFT
|
||||
return advanceSearch
|
||||
}
|
||||
set(advanceSearch) {
|
||||
mSname.isChecked =
|
||||
NumberUtils.int2boolean(advanceSearch and SNAME)
|
||||
mStags.isChecked =
|
||||
NumberUtils.int2boolean(advanceSearch and STAGS)
|
||||
mSdesc.isChecked =
|
||||
NumberUtils.int2boolean(advanceSearch and SDESC)
|
||||
mStorr.isChecked = NumberUtils.int2boolean(advanceSearch and STORR)
|
||||
mSto.isChecked = NumberUtils.int2boolean(advanceSearch and STO)
|
||||
mSdt1.isChecked = NumberUtils.int2boolean(advanceSearch and SDT1)
|
||||
mSdt2.isChecked = NumberUtils.int2boolean(advanceSearch and SDT2)
|
||||
mSh.isChecked = NumberUtils.int2boolean(advanceSearch and SH)
|
||||
mSto.isChecked = NumberUtils.int2boolean(advanceSearch and STO)
|
||||
mSfl.isChecked = NumberUtils.int2boolean(advanceSearch and SFL)
|
||||
mSfu.isChecked = NumberUtils.int2boolean(advanceSearch and SFU)
|
||||
mSft.isChecked = NumberUtils.int2boolean(advanceSearch and SFT)
|
||||
@@ -174,14 +144,8 @@ class AdvanceSearchTable @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SNAME = 0x1
|
||||
const val STAGS = 0x2
|
||||
const val SDESC = 0x4
|
||||
const val STORR = 0x8
|
||||
const val STO = 0x10
|
||||
const val SDT1 = 0x20
|
||||
const val SDT2 = 0x40
|
||||
const val SH = 0x80
|
||||
const val SH = 0x1
|
||||
const val STO = 0x2
|
||||
const val SFL = 0x100
|
||||
const val SFU = 0x200
|
||||
const val SFT = 0x400
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.FavouriteStatusRouter;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.widget.ContentLayout;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelper<GalleryInfo> {
|
||||
|
||||
private static final String KEY_DATA_MAP = "data_map";
|
||||
private final FavouriteStatusRouter.Listener listener;
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private Map<Long, GalleryInfo> map = new HashMap<>();
|
||||
|
||||
public GalleryInfoContentHelper() {
|
||||
listener = (gid, slot) -> {
|
||||
GalleryInfo info = map.get(gid);
|
||||
if (info != null) {
|
||||
info.favoriteSlot = slot;
|
||||
}
|
||||
};
|
||||
EhApplication.getFavouriteStatusRouter().addListener(listener);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
EhApplication.getFavouriteStatusRouter().removeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddData(GalleryInfo data) {
|
||||
map.put(data.gid, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAddData(List<GalleryInfo> data) {
|
||||
for (GalleryInfo info : data) {
|
||||
map.put(info.gid, info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveData(GalleryInfo data) {
|
||||
map.remove(data.gid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemoveData(List<GalleryInfo> data) {
|
||||
for (GalleryInfo info : data) {
|
||||
map.remove(info.gid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClearData() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable saveInstanceState(Parcelable superState) {
|
||||
Bundle bundle = (Bundle) super.saveInstanceState(superState);
|
||||
|
||||
// TODO It's a bad design
|
||||
FavouriteStatusRouter router = EhApplication.getFavouriteStatusRouter();
|
||||
int id = router.saveDataMap(map);
|
||||
bundle.putInt(KEY_DATA_MAP, id);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable restoreInstanceState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
|
||||
int id = bundle.getInt(KEY_DATA_MAP, IntIdGenerator.INVALID_ID);
|
||||
if (id != IntIdGenerator.INVALID_ID) {
|
||||
FavouriteStatusRouter router = EhApplication.getFavouriteStatusRouter();
|
||||
Map<Long, GalleryInfo> map = router.restoreDataMap(id);
|
||||
if (map != null) {
|
||||
this.map = map;
|
||||
}
|
||||
}
|
||||
|
||||
return super.restoreInstanceState(state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2019 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.Calendar
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.widget.ContentLayout.ContentHelper
|
||||
import com.hippo.yorozuya.IntIdGenerator
|
||||
import java.util.Locale
|
||||
|
||||
abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
@JvmField
|
||||
var maxGid = -1
|
||||
@JvmField
|
||||
var minGid = -1
|
||||
@JvmField
|
||||
var jumpTo : String? = null
|
||||
private val listener: FavouriteStatusRouter.Listener
|
||||
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private var map: MutableMap<Long, GalleryInfo> = HashMap()
|
||||
|
||||
init {
|
||||
listener = FavouriteStatusRouter.Listener { gid: Long, slot: Int ->
|
||||
val info = map[gid]
|
||||
if (info != null) {
|
||||
info.favoriteSlot = slot
|
||||
}
|
||||
}
|
||||
EhApplication.getFavouriteStatusRouter().addListener(listener)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
EhApplication.getFavouriteStatusRouter().removeListener(listener)
|
||||
}
|
||||
|
||||
override fun onAddData(data: GalleryInfo?) {
|
||||
data?.let { map[data.gid] = data }
|
||||
}
|
||||
|
||||
override fun onAddData(data: List<GalleryInfo?>) {
|
||||
for (info in data) {
|
||||
info?.let {
|
||||
if (maxGid == -1) maxGid = info.gid.toInt()
|
||||
if (minGid == -1) minGid = info.gid.toInt()
|
||||
maxGid = Math.max(maxGid.toLong(), info.gid).toInt()
|
||||
minGid = Math.min(minGid.toLong(), info.gid).toInt()
|
||||
map[info.gid] = info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRemoveData(data: GalleryInfo?) {
|
||||
data?.let { map.remove(data.gid) }
|
||||
}
|
||||
|
||||
override fun onRemoveData(data: List<GalleryInfo?>) {
|
||||
for (info in data) {
|
||||
info?.let { map.remove(info.gid) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClearData() {
|
||||
map.clear()
|
||||
maxGid = -1
|
||||
minGid = -1
|
||||
}
|
||||
|
||||
override fun beforeRefresh() {
|
||||
super.beforeRefresh()
|
||||
maxGid = -1
|
||||
minGid = -1
|
||||
}
|
||||
|
||||
override fun saveInstanceState(superState: Parcelable): Parcelable {
|
||||
val bundle = super.saveInstanceState(superState) as Bundle
|
||||
|
||||
// TODO It's a bad design
|
||||
val router = EhApplication.getFavouriteStatusRouter()
|
||||
val id = router.saveDataMap(map)
|
||||
bundle.putInt(KEY_DATA_MAP, id)
|
||||
bundle.putInt(KEY_MIN_GID, minGid)
|
||||
bundle.putInt(KEY_MAX_GID, maxGid)
|
||||
return bundle
|
||||
}
|
||||
|
||||
override fun restoreInstanceState(state: Parcelable): Parcelable {
|
||||
val bundle = state as Bundle
|
||||
val id = bundle.getInt(KEY_DATA_MAP, IntIdGenerator.INVALID_ID)
|
||||
if (id != IntIdGenerator.INVALID_ID) {
|
||||
val router = EhApplication.getFavouriteStatusRouter()
|
||||
val map = router.restoreDataMap(id)
|
||||
if (map != null) {
|
||||
this.map = map
|
||||
}
|
||||
}
|
||||
minGid = bundle.getInt(KEY_MIN_GID)
|
||||
maxGid = bundle.getInt(KEY_MAX_GID)
|
||||
return super.restoreInstanceState(state)
|
||||
}
|
||||
|
||||
fun goTo(time: Long) {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = time
|
||||
val date = calendar.time
|
||||
val format = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
jumpTo = format.format(date)
|
||||
doGoToPage(Int.MAX_VALUE / 2)
|
||||
}
|
||||
|
||||
fun goToPage(page: Int) {
|
||||
jumpTo = page.toString()
|
||||
doGoToPage(page)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_DATA_MAP = "data_map"
|
||||
private const val KEY_MAX_GID = "max_gid"
|
||||
private const val KEY_MIN_GID = "min_gid"
|
||||
const val KEY_PREV_PAGE = "prev_page"
|
||||
const val KEY_NEXT_PAGE = "next_page"
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
|
||||
private View mSelectImage;
|
||||
private CheckBox mSearchUSS;
|
||||
private CheckBox mSearchOSC;
|
||||
private CheckBox mSearchSE;
|
||||
|
||||
private Helper mHelper;
|
||||
|
||||
@@ -93,7 +92,6 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
|
||||
mSelectImage = ViewUtils.$$(this, R.id.select_image);
|
||||
mSearchUSS = (CheckBox) ViewUtils.$$(this, R.id.search_uss);
|
||||
mSearchOSC = (CheckBox) ViewUtils.$$(this, R.id.search_osc);
|
||||
mSearchSE = (CheckBox) ViewUtils.$$(this, R.id.search_se);
|
||||
|
||||
mSelectImage.setOnClickListener(this);
|
||||
}
|
||||
@@ -182,7 +180,6 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
|
||||
builder.setImagePath(mImagePath);
|
||||
builder.setUseSimilarityScan(mSearchUSS.isChecked());
|
||||
builder.setOnlySearchCovers(mSearchOSC.isChecked());
|
||||
builder.setShowExpunged(mSearchSE.isChecked());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.image;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.Native;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* The {@code Image} is a image which stored pixel data in native heap
|
||||
*/
|
||||
public final class Image {
|
||||
public static final int FORMAT_NORMAL = 0;
|
||||
public static final int FORMAT_ANIMATED = 1;
|
||||
private static final ArrayList<Image> imageList = new ArrayList<>();
|
||||
private static boolean needEvictAll = false;
|
||||
private long mNativePtr;
|
||||
|
||||
private boolean needRecycle = false;
|
||||
|
||||
private Integer nativeProcessCnt = 0;
|
||||
|
||||
private Image(long nativePtr) {
|
||||
if (needEvictAll)
|
||||
evictAll();
|
||||
mNativePtr = nativePtr;
|
||||
imageList.add(this);
|
||||
}
|
||||
|
||||
private static Image newFromAddr(long addr) {
|
||||
if (addr == 0)
|
||||
return null;
|
||||
return new Image(addr);
|
||||
}
|
||||
|
||||
public static void lazyEvictAll() {
|
||||
needEvictAll = true;
|
||||
}
|
||||
|
||||
private static void evictAll() {
|
||||
(new ArrayList<>(imageList)).forEach(Image::recycle);
|
||||
needEvictAll = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(FileDescriptor fd, boolean partially) {
|
||||
return newFromAddr(nativeDecode(Native.getFd(fd)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(Integer fd, boolean partially) {
|
||||
return newFromAddr(nativeDecode(fd));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decodeAddr(Long addr, boolean partially) {
|
||||
return newFromAddr(nativeDecodeAddr(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@Nullable
|
||||
public static Image create(Bitmap bitmap) {
|
||||
return newFromAddr(nativeCreate(bitmap));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled {@code Image} instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
public static int getImageCount() {
|
||||
return imageList.size();
|
||||
}
|
||||
|
||||
private static native long nativeDecode(int fd);
|
||||
|
||||
private static native long nativeDecodeAddr(long addr);
|
||||
|
||||
private static native long nativeCreate(Bitmap bitmap);
|
||||
|
||||
private static native void nativeRender(long nativePtr,
|
||||
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height);
|
||||
|
||||
private static native void nativeTexImage(long nativePtr, boolean init, int offsetX, int offsetY, int width, int height);
|
||||
|
||||
private static native void nativeAdvance(long nativePtr);
|
||||
|
||||
private static native int nativeGetDelay(long nativePtr);
|
||||
|
||||
private static native boolean nativeIsOpaque(long nativePtr);
|
||||
|
||||
private static native void nativeRecycle(long nativePtr);
|
||||
|
||||
private static native int nativeGetFormat(long nativePtr);
|
||||
|
||||
private static native int nativeGetWidth(long nativePtr);
|
||||
|
||||
private static native int nativeGetHeight(long nativePtr);
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
public int getFormat() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetFormat(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the image
|
||||
*/
|
||||
public int getWidth() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetWidth(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the image
|
||||
*/
|
||||
public int getHeight() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetHeight(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private synchronized void checkRecycledAndLock() {
|
||||
if (mNativePtr == 0) {
|
||||
throw new IllegalStateException("The image is recycled.");
|
||||
}
|
||||
nativeProcessCnt++;
|
||||
}
|
||||
|
||||
private synchronized void releaseNativeLock() {
|
||||
nativeProcessCnt--;
|
||||
if (needRecycle)
|
||||
recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the image to {@code Bitmap}
|
||||
*/
|
||||
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height, boolean fillBlank, int defaultColor) {
|
||||
checkRecycledAndLock();
|
||||
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@code glTexImage2D} for init is true and
|
||||
* call {@code glTexSubImage2D} for init is false.
|
||||
* width * height must <= 512 * 512 or do nothing
|
||||
*/
|
||||
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
|
||||
checkRecycledAndLock();
|
||||
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to next frame. Do nothing for non-animation image
|
||||
*/
|
||||
public void advance() {
|
||||
checkRecycledAndLock();
|
||||
nativeAdvance(mNativePtr);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current frame delay. 0 for non-animation image
|
||||
*/
|
||||
public int getDelay() {
|
||||
checkRecycledAndLock();
|
||||
int delay = nativeGetDelay(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return delay <= 10 ? 100 : delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return is the image opaque
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
checkRecycledAndLock();
|
||||
boolean ret = nativeIsOpaque(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the native object associated with this image.
|
||||
* It must be called when the image will not be used.
|
||||
* The image can't be used after this method is called.
|
||||
*/
|
||||
public synchronized void recycle() {
|
||||
if (mNativePtr != 0) {
|
||||
if (nativeProcessCnt != 0) {
|
||||
needRecycle = true;
|
||||
return;
|
||||
}
|
||||
nativeRecycle(mNativePtr);
|
||||
mNativePtr = 0;
|
||||
imageList.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this image has been recycled.
|
||||
*/
|
||||
public boolean isRecycled() {
|
||||
return mNativePtr == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.hippo.Native.getFd
|
||||
import java.io.FileDescriptor
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the image
|
||||
*/
|
||||
val width: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetWidth(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this image has been recycled.
|
||||
*/
|
||||
val isRecycled: Boolean
|
||||
get() = mNativePtr == 0L
|
||||
|
||||
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 lazyEvictAll() {
|
||||
needEvictAll = true
|
||||
}
|
||||
|
||||
private fun evictAll() {
|
||||
ArrayList(imageList).forEach(
|
||||
Consumer { obj: Image -> obj.recycle() })
|
||||
needEvictAll = 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,
|
||||
srcX: Int, srcY: Int, dst: Bitmap, dstX: Int, dstY: Int,
|
||||
width: Int, height: Int
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeTexImage(
|
||||
nativePtr: Long,
|
||||
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,329 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.image;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import 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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A image with {@link Image} for data and {@link Bitmap} for render.
|
||||
*/
|
||||
public final class ImageBitmap implements Animatable, Runnable {
|
||||
|
||||
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
|
||||
@NonNull
|
||||
private final Bitmap mBitmap;
|
||||
private final int mFormat;
|
||||
private final boolean mIsOpaque;
|
||||
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
|
||||
@Nullable
|
||||
private Image mImage;
|
||||
private int mReferences;
|
||||
private int mAnimationReferences;
|
||||
private boolean mRunning;
|
||||
|
||||
private ImageBitmap(@NonNull Image image) throws OutOfMemoryError {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
image.render(0, 0, mBitmap, 0, 0, width, height, false, 0);
|
||||
mFormat = image.getFormat();
|
||||
mIsOpaque = image.isOpaque();
|
||||
|
||||
if (mFormat == 1) {
|
||||
// For animated image, save image object
|
||||
mImage = image;
|
||||
} else {
|
||||
// Free the image
|
||||
image.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private ImageBitmap(@NonNull Bitmap bitmap) {
|
||||
mFormat = Image.FORMAT_NORMAL;
|
||||
mBitmap = bitmap;
|
||||
mIsOpaque = !bitmap.hasAlpha();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode {@code InputStream}, then create image.
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap decode(@NonNull FileDescriptor fd) {
|
||||
Image image = Image.decode(fd, false);
|
||||
if (image != null) {
|
||||
return create(image);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@code ImageBitmap} from {@code Image}.
|
||||
* It is not recommended. Use {@link #decode(InputStream)} 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.
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap create(@NonNull Image image) {
|
||||
if (!image.isRecycled()) {
|
||||
try {
|
||||
return new ImageBitmap(image);
|
||||
} catch (OutOfMemoryError e) {
|
||||
image.recycle();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@code ImageBitmap} from {@code Bitmap}.
|
||||
*
|
||||
* @param bitmap the bitmap should not be recycled
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap create(@NonNull Bitmap bitmap) {
|
||||
if (!bitmap.isRecycled()) {
|
||||
return new ImageBitmap(bitmap);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the image bitmap
|
||||
*
|
||||
* @return false for the image is recycled and obtain failed
|
||||
*/
|
||||
public synchronized boolean obtain() {
|
||||
if (mBitmap.isRecycled()) {
|
||||
return false;
|
||||
} else {
|
||||
++mReferences;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the image bitmap
|
||||
*/
|
||||
public synchronized void release() {
|
||||
--mReferences;
|
||||
if (mReferences <= 0 && !mBitmap.isRecycled()) {
|
||||
mBitmap.recycle();
|
||||
if (mImage != null) {
|
||||
mImage.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback for invalidating
|
||||
*/
|
||||
public void addCallback(@NonNull Callback callback) {
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
Callback c;
|
||||
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(new WeakReference<>(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback
|
||||
*/
|
||||
public void removeCallback(@NonNull Callback callback) {
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
Callback c;
|
||||
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 the format of the image
|
||||
*/
|
||||
public int getFormat() {
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image width
|
||||
*/
|
||||
public int getWidth() {
|
||||
return mBitmap.getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image height
|
||||
*/
|
||||
public int getHeight() {
|
||||
return mBitmap.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image is opaque
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
return mIsOpaque;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image is animated
|
||||
*/
|
||||
public boolean isAnimated() {
|
||||
return mImage != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, float left, float top, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, left, top, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, @Nullable Rect src, @NonNull Rect dst, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, @Nullable Rect src, @NonNull RectF dst, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code start()} and {@code stop()} is a pair
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
mAnimationReferences++;
|
||||
if (mBitmap.isRecycled() || mImage == null || mRunning) {
|
||||
return;
|
||||
}
|
||||
mRunning = true;
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage.getDelay()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code start()} and {@code stop()} is a pair
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
mAnimationReferences--;
|
||||
if (mAnimationReferences <= 0) {
|
||||
mRunning = false;
|
||||
HANDLER.removeCallbacks(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mRunning;
|
||||
}
|
||||
|
||||
private boolean notifyUpdate() {
|
||||
boolean hasCallback = false;
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
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.getWidth(), mImage.getHeight(), 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
|
||||
public void run() {
|
||||
// Check recycled
|
||||
if (mBitmap.isRecycled() || mImage == null) {
|
||||
mRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mImage.advance();
|
||||
|
||||
if (notifyUpdate()) {
|
||||
if (mRunning) {
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage.getDelay()));
|
||||
}
|
||||
} else {
|
||||
mRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void invalidateImage(ImageBitmap who);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
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) {
|
||||
false
|
||||
} else {
|
||||
++mReferences
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the image bitmap
|
||||
*/
|
||||
@Synchronized
|
||||
fun release() {
|
||||
--mReferences
|
||||
if (mReferences <= 0 && !mBitmap.isRecycled) {
|
||||
mBitmap.recycle()
|
||||
if (mImage != null) {
|
||||
mImage!!.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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?)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +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 {@link ImageBitmap}
|
||||
*/
|
||||
public class ImageDrawable extends Drawable implements Animatable, ImageBitmap.Callback {
|
||||
|
||||
private final ImageBitmap mImageBitmap;
|
||||
private final Paint mPaint;
|
||||
|
||||
public ImageDrawable(ImageBitmap imageBitmap) throws Exception {
|
||||
if (!imageBitmap.obtain()) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
mImageBitmap = imageBitmap;
|
||||
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
|
||||
// Add callback
|
||||
imageBitmap.addCallback(this);
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
mImageBitmap.removeCallback(this);
|
||||
mImageBitmap.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
mImageBitmap.draw(canvas, null, getBounds(), mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return mImageBitmap.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mImageBitmap.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mImageBitmap.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
mImageBitmap.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
mImageBitmap.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mImageBitmap.isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateImage(ImageBitmap who) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ public class SceneFragment extends Fragment {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity instanceof StageActivity) {
|
||||
((StageActivity) activity).startScene(announcer, horizontal);
|
||||
((StageActivity) activity).updateBackPressCallBackStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -31,6 +32,7 @@ import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.ui.EhActivity;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
|
||||
@@ -58,6 +60,8 @@ public abstract class StageActivity extends EhActivity {
|
||||
private final SceneViewComparator mSceneViewComparator = new SceneViewComparator();
|
||||
private int mStageId = IntIdGenerator.INVALID_ID;
|
||||
|
||||
private OnBackPressedCallback callback;
|
||||
|
||||
public static void registerLaunchMode(Class<?> clazz, @SceneFragment.LaunchMode int launchMode) {
|
||||
if (launchMode != SceneFragment.LAUNCH_MODE_STANDARD &&
|
||||
launchMode != SceneFragment.LAUNCH_MODE_SINGLE_TOP &&
|
||||
@@ -140,6 +144,8 @@ public abstract class StageActivity extends EhActivity {
|
||||
protected final void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
callback = new BackPressCallBack(false);
|
||||
getOnBackPressedDispatcher().addCallback(callback);
|
||||
if (savedInstanceState != null) {
|
||||
mStageId = savedInstanceState.getInt(KEY_STAGE_ID, IntIdGenerator.INVALID_ID);
|
||||
ArrayList<String> list = savedInstanceState.getStringArrayList(KEY_SCENE_TAG_LIST);
|
||||
@@ -247,6 +253,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
|
||||
public void startScene(Announcer announcer) {
|
||||
startScene(announcer, false);
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
public void startScene(Announcer announcer, boolean horizontal) {
|
||||
@@ -461,6 +468,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
// TODO Call onNewArguments when view created ?
|
||||
scene.onNewArguments(args);
|
||||
}
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
int getSceneIndex(SceneFragment scene) {
|
||||
@@ -477,6 +485,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
|
||||
public void finishScene(SceneFragment scene) {
|
||||
finishScene(scene.getTag());
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
private void finishScene(String tag) {
|
||||
@@ -558,25 +567,51 @@ public abstract class StageActivity extends EhActivity {
|
||||
} else {
|
||||
fragmentManager.beginTransaction().detach(fragment).attach(fragment).commitAllowingStateLoss();
|
||||
}
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
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;
|
||||
private boolean shouldEnableBackPressCallBack() {
|
||||
if (mSceneTagList.size() > 1)
|
||||
return true;
|
||||
if (mSceneTagList.size() == 0)
|
||||
return false;
|
||||
String tag = mSceneTagList.get(0);
|
||||
GalleryListScene fragment = (GalleryListScene) getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null)
|
||||
return false;
|
||||
return !fragment.isBackpressCanPreviewLauncherStatus();
|
||||
}
|
||||
|
||||
public void updateBackPressCallBackStatus() {
|
||||
var isEnabled = callback.isEnabled();
|
||||
var shouldEnable = shouldEnableBackPressCallBack();
|
||||
if (isEnabled != shouldEnable)
|
||||
callback.setEnabled(shouldEnable);
|
||||
}
|
||||
|
||||
class BackPressCallBack extends OnBackPressedCallback {
|
||||
public BackPressCallBack(boolean enabled) {
|
||||
super(enabled);
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onBackPressed();
|
||||
@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
|
||||
@@ -584,19 +619,17 @@ public abstract class StageActivity extends EhActivity {
|
||||
super.onProvideAssistContent(outContent);
|
||||
int size = mSceneTagList.size();
|
||||
String tag = mSceneTagList.get(size - 1);
|
||||
SceneFragment scene;
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
Log.e(TAG, "onProvideAssistContent: Can't find scene by tag: " + tag);
|
||||
return;
|
||||
}
|
||||
if (!(fragment instanceof SceneFragment)) {
|
||||
Log.e(TAG, "onProvideAssistContent: The fragment is not SceneFragment");
|
||||
return;
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onProvideAssistContent(outContent);
|
||||
if (fragment instanceof SceneFragment scene) {
|
||||
scene.onProvideAssistContent(outContent);
|
||||
} else {
|
||||
Log.e(TAG, "onProvideAssistContent: The fragment is not SceneFragment");
|
||||
}
|
||||
}
|
||||
|
||||
public SceneFragment findSceneByTag(String tag) {
|
||||
|
||||
@@ -25,20 +25,31 @@ import android.os.Build
|
||||
import android.os.PersistableBundle
|
||||
import android.text.TextUtils
|
||||
import android.view.textclassifier.TextClassifier
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.SettingsActivity
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
|
||||
fun Context.getClipboardManager(): ClipboardManager {
|
||||
return getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
}
|
||||
|
||||
fun ClipboardManager.addTextToClipboard(text: String?, isSensitive: Boolean, showTip: () -> Unit) {
|
||||
setPrimaryClip(ClipData.newPlainText(null, text).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && isSensitive)
|
||||
description.extras = PersistableBundle().apply {
|
||||
putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
|
||||
}
|
||||
})
|
||||
fun Context.addTextToClipboard(text: String?, isSensitive: Boolean) {
|
||||
getClipboardManager().apply {
|
||||
setPrimaryClip(ClipData.newPlainText(null, text).apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && isSensitive)
|
||||
description.extras = PersistableBundle().apply {
|
||||
putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
// Avoid double notify user since system have done that on Tiramisu above
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
|
||||
showTip()
|
||||
if (this is MainActivity) {
|
||||
showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
|
||||
} else if (this is SettingsActivity) {
|
||||
showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
fun ClipboardManager.getTextFromClipboard(context: Context): String? {
|
||||
|
||||
@@ -463,23 +463,11 @@ public class ContentLayout extends FrameLayout {
|
||||
onClearData();
|
||||
notifyDataSetChanged();
|
||||
|
||||
if (true || mEndPage >= mPages) { // Not found
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Ui change, show progress bar
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showProgressBar();
|
||||
|
||||
// Get next page
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS;
|
||||
mCurrentTaskPage = mEndPage;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
}
|
||||
// Not found
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
mData.clear();
|
||||
onClearData();
|
||||
@@ -513,33 +501,24 @@ public class ContentLayout extends FrameLayout {
|
||||
// assert mStartPage >= 0
|
||||
|
||||
if (data.isEmpty()) {
|
||||
if (true || mStartPage <= 0) { // OK, that's all
|
||||
if (mData.isEmpty()) {
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
if (mCurrentTaskType == TYPE_PRE_PAGE && mRecyclerView.isAttachedToWindow()) {
|
||||
// RecyclerView scroll, to top
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), 0, 0);
|
||||
onScrollToPosition(0);
|
||||
}
|
||||
}
|
||||
// OK, that's all
|
||||
if (mData.isEmpty()) {
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Keep UI
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
// Get previous
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
// Keep mCurrentTaskType
|
||||
mCurrentTaskPage = mStartPage - 1;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
if (mCurrentTaskType == TYPE_PRE_PAGE && mRecyclerView.isAttachedToWindow()) {
|
||||
// RecyclerView scroll, to top
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), 0, 0);
|
||||
onScrollToPosition(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mData.addAll(0, data);
|
||||
@@ -576,33 +555,24 @@ public class ContentLayout extends FrameLayout {
|
||||
mPages = Math.max(mEndPage, pages);
|
||||
|
||||
if (data.isEmpty()) {
|
||||
if (true || mEndPage >= mPages) { // OK, that's all
|
||||
if (mData.isEmpty()) {
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
if (mCurrentTaskType == TYPE_NEXT_PAGE && mRecyclerView.isAttachedToWindow()) {
|
||||
// RecyclerView scroll
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), oldDataSize, 0);
|
||||
onScrollToPosition(oldDataSize);
|
||||
}
|
||||
}
|
||||
// OK, that's all
|
||||
if (mData.isEmpty()) {
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Keep UI
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
// Get next page
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
// Keep mCurrentTaskType
|
||||
mCurrentTaskPage = mEndPage;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
if (mCurrentTaskType == TYPE_NEXT_PAGE && mRecyclerView.isAttachedToWindow()) {
|
||||
// RecyclerView scroll
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), oldDataSize, 0);
|
||||
onScrollToPosition(oldDataSize);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mData.addAll(data);
|
||||
@@ -639,23 +609,11 @@ public class ContentLayout extends FrameLayout {
|
||||
onClearData();
|
||||
notifyDataSetChanged();
|
||||
|
||||
if (true || mEndPage >= mPages) { // Not found
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
// Ui change, show progress bar
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showProgressBar();
|
||||
|
||||
// Get next page
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS;
|
||||
mCurrentTaskPage = mEndPage;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
}
|
||||
// Not found
|
||||
// Ui change, show empty string
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
mData.clear();
|
||||
onClearData();
|
||||
@@ -753,6 +711,10 @@ public class ContentLayout extends FrameLayout {
|
||||
mViewTransition.showView(0);
|
||||
}
|
||||
|
||||
protected void beforeRefresh() {
|
||||
|
||||
}
|
||||
|
||||
private boolean isContentShowing() {
|
||||
return mViewTransition.getShownViewIndex() == 0;
|
||||
}
|
||||
@@ -803,7 +765,8 @@ public class ContentLayout extends FrameLayout {
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
}
|
||||
|
||||
private void doRefresh() {
|
||||
protected void doRefresh() {
|
||||
beforeRefresh();
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_REFRESH;
|
||||
mCurrentTaskPage = 0;
|
||||
@@ -914,6 +877,15 @@ public class ContentLayout extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
protected void doGoToPage(int page) {
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_SOMEWHERE;
|
||||
mCurrentTaskPage = page;
|
||||
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
|
||||
}
|
||||
|
||||
protected Parcelable saveInstanceState(Parcelable superState) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(KEY_SUPER, superState);
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.SimpleAnimatorListener;
|
||||
|
||||
@@ -298,6 +299,7 @@ public class FabLayout extends ViewGroup implements View.OnClickListener {
|
||||
mOnExpandListener.onExpand(expanded);
|
||||
}
|
||||
}
|
||||
((StageActivity)getContext()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2022 hathlife
|
||||
~
|
||||
~ 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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/keyline_margin">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/newerVersion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:gravity="center"
|
||||
android:padding="8dp"
|
||||
android:text="@string/newer_version_avaliable"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/keyline_margin"
|
||||
android:paddingRight="@dimen/keyline_margin">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rate"
|
||||
android:layout_width="484dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatRatingBar
|
||||
android:id="@+id/rating"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
|
||||
android:isIndicator="true"
|
||||
android:theme="@style/RatingBarTheme" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rating_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/actions_scroll_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:clipToPadding="false"
|
||||
android:paddingLeft="36dp"
|
||||
android:paddingRight="@dimen/keyline_margin"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/spacer_keyline"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:showDividers="middle">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/heart_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:nextFocusUp="@+id/download">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/heart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/favorited"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/heart_outline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="4dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/not_favorited"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="4dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:nextFocusUp="@+id/download"
|
||||
android:text="@string/share"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/torrent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="4dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:nextFocusUp="@+id/download"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/archive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="4dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:nextFocusUp="@+id/download"
|
||||
android:text="@string/archive"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/similar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="4dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:nextFocusUp="@+id/download"
|
||||
android:text="@string/similar_gallery"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/search_cover"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="4dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:nextFocusRight="@+id/search_cover"
|
||||
android:nextFocusUp="@+id/download"
|
||||
android:text="@string/search_cover"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -28,7 +28,7 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:gravity="center_horizontal" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
<com.hippo.app.HapticSlider
|
||||
android:id="@+id/slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -44,96 +45,62 @@
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:divider="@drawable/spacer_keyline"
|
||||
android:orientation="horizontal"
|
||||
android:showDividers="middle">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/heart_group"
|
||||
<Button
|
||||
android:id="@+id/heart"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
android:text="@string/favorited"
|
||||
app:icon="@drawable/v_heart_primary_x48" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/heart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/favorited"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/heart_outline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/not_favorited"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/share"
|
||||
<Button
|
||||
android:id="@+id/heart_outline"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="8dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/share"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
android:text="@string/not_favorited"
|
||||
app:icon="@drawable/v_heart_outline_primary_x48" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/torrent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="8dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/archive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="8dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/archive"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
<Button
|
||||
android:id="@+id/similar"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="8dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/similar_gallery"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
app:icon="@drawable/v_similar_primary_x48" />
|
||||
|
||||
<TextView
|
||||
<Button
|
||||
android:id="@+id/search_cover"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:drawablePadding="8dp"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/search_cover"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
app:icon="@drawable/v_file_find_primary_x48" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/share"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/share"
|
||||
app:icon="@drawable/v_share_primary_x48" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/torrent"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/v_utorrent_primary_x48" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/archive"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/archive"
|
||||
app:icon="@drawable/v_archive_primary_x48" />
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
@@ -14,9 +14,29 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<com.hippo.easyrecyclerview.EasyRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/recycler_view"
|
||||
android:id="@+id/standard_bottom_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/TextAppearance.Material3.TitleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -26,10 +26,9 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:checked="true"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_sname" />
|
||||
android:text="@string/search_sh" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:layout_width="0dp"
|
||||
@@ -37,91 +36,9 @@
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:checked="true"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_stags" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/advance_search_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/advance_search_table_item_margin">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_sdesc" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_storr" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/advance_search_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/advance_search_table_item_margin">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_sto" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_sdt1" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/advance_search_table_item_margin"
|
||||
android:layout_marginBottom="@dimen/advance_search_table_item_margin">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginRight="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_sdt2" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_sh" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
||||
@@ -72,17 +72,6 @@
|
||||
|
||||
<TableRow>
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/search_se"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/advance_search_table_item_margin"
|
||||
android:layout_weight="1"
|
||||
android:checked="false"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/search_se" />
|
||||
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -98,14 +98,8 @@
|
||||
<string name="search_specify_uploader">Einen Uploader spezifizieren</string>
|
||||
<string name="search_specify_tag">Ein Etikett spezifizieren</string>
|
||||
<string name="search_enable_advance">Erweitere Optionen aktivieren</string>
|
||||
<string name="search_sname">Nach Galerie suchen</string>
|
||||
<string name="search_stags">Nach Galerie-Etikett suchen</string>
|
||||
<string name="search_sdesc">Nach Galeriebeschreibungen suchen</string>
|
||||
<string name="search_storr">Nach Torrentdateien suchen</string>
|
||||
<string name="search_sto">Nur die Galerien mit Torrentdateien anzeigen</string>
|
||||
<string name="search_sdt1">Nach unwichtigen Etiketten suchen</string>
|
||||
<string name="search_sdt2">Nach unerwünschten Etiketten suchen</string>
|
||||
<string name="search_sh">Gelöschte Galerien anzeigen</string>
|
||||
<string name="search_sto">Nur die Galerien mit Torrentdateien anzeigen</string>
|
||||
<string name="search_sr">Mindestpunktzahl: </string>
|
||||
<string name="search_sp">Seiten: </string>
|
||||
<string name="search_sp_to">zu</string>
|
||||
@@ -116,7 +110,6 @@
|
||||
<string name="select_image">Bild wählen</string>
|
||||
<string name="search_uss">Ähnlichkeitscan aktivieren</string>
|
||||
<string name="search_osc">Nur nach Umschläge suchen</string>
|
||||
<string name="search_se">Die gelöschten Galerien anzeigen</string>
|
||||
<string name="select_image_first">Wähle ein Bild</string>
|
||||
<string name="add_to_favourites">Zu den Favoriten hinzufügen</string>
|
||||
<string name="stop_downloading">Download stoppen</string>
|
||||
@@ -464,7 +457,6 @@
|
||||
<string name="settings_privacy">Datenschutzoptionen</string>
|
||||
<string name="settings_privacy_secure">Screenshot verhindern</string>
|
||||
<string name="settings_privacy_secure_summary">Screenshots werden verhindert. Inhalt der App wird in Aktuelle Apps-Liste nicht angezeigt.</string>
|
||||
<string name="press_twice_exit">Darauf erneut antippen, um die App zu beenden</string>
|
||||
<string name="app_waring">Inhalt dieser App kommt aus dem Internet, von dem einige dir körperlichen oder emotionalen Schaden zufügen kann. Du hast es wirklich erfahrt und möchtest immer noch fortsetzen.</string>
|
||||
<string name="error_username_cannot_empty">Gibt den Nutzername ein</string>
|
||||
<string name="error_password_cannot_empty">Gibt das Passwort ein</string>
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
<string name="skip_signing_in">Saltar inicio de sesión</string>
|
||||
<string name="sign_in_failed">Inicio de sesión fallido</string>
|
||||
<string name="sign_in_failed_plain">Tal ves sea debido a un error de actualización de el sitio web o algo mal sucedió. Puedes intentar iniciar sesión vía WebView, ingresar una cookie o saltar el inicio de sesión presionando el botón de abajo.\nEs posible que te salga un \"Panda Triste\" después de saltar el inicio de sesión. Intente \"Ingresar vía cookies\" primero si ha iniciado sesión en la versión 0.6.x de HeViewer.</string>
|
||||
<string name="press_twice_exit">Presione una vez más para salir</string>
|
||||
<string name="content_layout_restore_tip">Pulsar para reintentar</string>
|
||||
<string name="write_rationale">EhViewer solicitará el permiso WRITE_EXTERNAL_STORAGE para descargar galerías. ¿Desea conceder el permiso?</string>
|
||||
<string name="you_rejected_me">Me has rechazado :(</string>
|
||||
@@ -120,20 +119,13 @@
|
||||
|
||||
<string name="search_tip">Búsqueda normal: Solo busca.\n\nBúsqueda de suscripción: Buscar etiquetas suscriptas.n\nUploader especifico: Listar de galerías subidas por el uploader. Otras opciones serán ignoradas.\n\nEtiquetas específicas: Listar las galerías que contienen la etiqueta. Solo se acepta una etiqueta. Otras opciones serán ignoradas.</string>
|
||||
|
||||
<string name="search_sname">Buscar galerías</string>
|
||||
<string name="search_stags">Buscar etiquetas de galería</string>
|
||||
<string name="search_sdesc">Buscar descripción de galería</string>
|
||||
<string name="search_storr">Buscar nombres de archivo torrent</string>
|
||||
<string name="search_sto">Mostrar solo galerías con torrents</string>
|
||||
<string name="search_sdt1">Buscar etiquetas Low-Power</string>
|
||||
<string name="search_sdt2">Buscar etiquetas pobres</string>
|
||||
<string name="search_sh">Mostrar galerías borradas</string>
|
||||
<string name="search_sto">Mostrar solo galerías con torrents</string>
|
||||
<string name="search_sr">Valoración mínima:</string>
|
||||
<string name="search_subscription_search">Búsqueda de etiquetas suscritas</string>
|
||||
<string name="select_image">Seleccionar imagen</string>
|
||||
<string name="search_uss">Usar búsqueda similar</string>
|
||||
<string name="search_osc">Buscar solo portadas</string>
|
||||
<string name="search_se">Mostrar borrados</string>
|
||||
<string name="select_image_first">Seleccione la imagen primero</string>
|
||||
<string name="search_sp">Páginas:</string>
|
||||
<string name="search_sp_to">hasta</string>
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
<string name="skip_signing_in">Continuer sans se connecter</string>
|
||||
<string name="sign_in_failed">Impossible de se connecter</string>
|
||||
<string name="sign_in_failed_plain">Il se peut que ce soit une erreur causée par la mise à jour du site Web ou d\'autre chose. Vous pouvez essayer de vous connecter via WebView, à l\'aide de cookies, ou continuer sans vous connecter. Mais si vous ne vous connectez pas, vous pouvez devenir un panda triste. Essayez de vous connecter à l\'aide de cookies si vous vous êtes connecté(e) dans la version 0.6.x.</string>
|
||||
<string name="press_twice_exit">Appuyez encore une fois pour quitter</string>
|
||||
<string name="content_layout_restore_tip">Appuyez pour réessayer</string>
|
||||
<string name="write_rationale">EhViewer réquêrir la permission WRITE_EXTERNAL_STORAGE pour télécharger les galeries. Voulez-vous l\'accorder ?</string>
|
||||
<string name="you_rejected_me">C\'est pas sympa T_T</string>
|
||||
@@ -135,14 +134,8 @@
|
||||
|
||||
<string name="search_tip">Recherche normale : faire recherche.\n\nRecherche dans les tags suivis : seulement rechercher dans les tags suivis.\n\nSpécifer un uploader : faire la liste des galeries mises en ligne par l\'uploader spécifié. Autres options sont ignorées.\n\nSpécifier un tag : faire la liste des galeries qui contiennent le tag spécifié. Vous ne pouvez spécifier qu\'un tag. Autres options sont ignorées.</string>
|
||||
|
||||
<string name="search_sname">Chercher une galerie</string>
|
||||
<string name="search_stags">Chercher tags galerie</string>
|
||||
<string name="search_sdesc">Chercher la description galerie</string>
|
||||
<string name="search_storr">Chercher un nom de fiche torrent</string>
|
||||
<string name="search_sto">Seulement montrer galeries avec des fiches torrent</string>
|
||||
<string name="search_sdt1">Chercher des tags faibles</string>
|
||||
<string name="search_sdt2">Chercher des tags peu acceptés</string>
|
||||
<string name="search_sh">Montrer les galeries supprimées</string>
|
||||
<string name="search_sto">Seulement montrer galeries avec des fiches torrent</string>
|
||||
<string name="search_sr">Minimum note :</string>
|
||||
<string name="search_sp">Pages : </string>
|
||||
<string name="search_sp_to">à</string>
|
||||
@@ -154,7 +147,6 @@
|
||||
<string name="select_image">Sélectionner une image</string>
|
||||
<string name="search_uss">Activer le scannage de similarité</string>
|
||||
<string name="search_osc">Chercher seulement les couverture</string>
|
||||
<string name="search_se">Montrer les galeries supprimées</string>
|
||||
<string name="select_image_first">Sélectionnez une image</string>
|
||||
|
||||
<string name="add_to_favourites">Ajouter à favori</string>
|
||||
|
||||
@@ -89,7 +89,6 @@
|
||||
<string name="skip_signing_in">ログインをスキップ</string>
|
||||
<string name="sign_in_failed">ログインに失敗しました。</string>
|
||||
<string name="sign_in_failed_plain">このエラーの原因は、ウェブサイトがメンテ中であることか、または不明なエラーが発生したことである可能性があります。ログインボタンの下でWebでログインするか、Cookieを入力してログインするか、またはログインをスキップしてみてください。ログインをスキップすると「Sad Panda」エラーが発生することがあります。0.6.xバージョンでログインした場合、Cookieでログインすることをおすすめします。</string>
|
||||
<string name="press_twice_exit">もう一度押して終了します</string>
|
||||
<string name="content_layout_restore_tip">クリックしてもう一度試す</string>
|
||||
<string name="write_rationale">EhViewerはギャラリーをダウンロードするためのWRITE_EXTERNAL_STORAGE権限を請求します。許可してもらえないでしょうか?</string>
|
||||
<string name="you_rejected_me">痛い</string>
|
||||
@@ -133,14 +132,8 @@
|
||||
|
||||
<string name="search_tip">普通検索:検索します。\n\n購読検索:購読しているタグで検索します。\n\nアップローダを検索:このアップローダーがアップロードしたギャラリーをリストします。他のオプションは無視されます。\n\nタグを検索:指定したタグを含むギャラリーをリストします。他のオプションは無視されます。</string>
|
||||
|
||||
<string name="search_sname">ギャラリーを検索</string>
|
||||
<string name="search_stags">ギャラリータグを検索</string>
|
||||
<string name="search_sdesc">ギャラリー説明を検索</string>
|
||||
<string name="search_storr">Torrentファイル名を検索</string>
|
||||
<string name="search_sto">Torrentのあるギャラリーのみを表示</string>
|
||||
<string name="search_sdt1">低希望タグを検索</string>
|
||||
<string name="search_sdt2">低評価タグを検索</string>
|
||||
<string name="search_sh">削除済みのギャラリーを表示</string>
|
||||
<string name="search_sto">Torrentのあるギャラリーのみを表示</string>
|
||||
<string name="search_sr">評価の下限:</string>
|
||||
<string name="search_sp">ページ:</string>
|
||||
<string name="search_sp_to">から</string>
|
||||
@@ -153,7 +146,6 @@
|
||||
<string name="select_image">画像を選択</string>
|
||||
<string name="search_uss">類似検索を有効化</string>
|
||||
<string name="search_osc">カバーのみを検索</string>
|
||||
<string name="search_se">削除済みのギャラリーを表示</string>
|
||||
<string name="select_image_first">画像を選択してください</string>
|
||||
|
||||
<string name="add_to_favourites">お気に入りに追加</string>
|
||||
|
||||
@@ -95,7 +95,6 @@
|
||||
<string name="skip_signing_in">로그인 없이 사용</string>
|
||||
<string name="sign_in_failed">로그인 실패</string>
|
||||
<string name="sign_in_failed_plain">뭔가가 잘못되었습니다. 웹사이트가 업데이트 중일 수 있습니다. WebView 또는 쿠키를 사용하여 로그인하거나 아래 버튼을 눌러 로그인을 건너뛸 수 있습니다. 그러나 로그인을 건너뛰면 "슬픈 팬더"를 보게 될 것입니다.</string>
|
||||
<string name="press_twice_exit">한 번 더 누르면 종료됩니다.</string>
|
||||
<string name="content_layout_restore_tip">다시 시도하려면 누르기</string>
|
||||
<string name="write_rationale">앱이 갤러리를 다운로드하려면 WRITE_EXTERNAL_STORAGE 권한이 필요합니다. 권한을 허용하시겠습니까?</string>
|
||||
<string name="you_rejected_me">안돼요</string>
|
||||
@@ -137,20 +136,13 @@
|
||||
|
||||
<string name="search_tip">일반 검색: 그냥 검색합니다.\n\n업로더 지정: 해당 업로더가 업로드한 갤러리를 검색합니다. 다른 옵션은 무시됩니다.\n\n태그 지정: 해당 태그를 포함한 갤러리를 검색합니다. 하나의 태그만 지정할 수 있으며, 다른 옵션은 무시됩니다.</string>
|
||||
|
||||
<string name="search_sname">갤러리 검색</string>
|
||||
<string name="search_stags">갤러래 태그 검색</string>
|
||||
<string name="search_sdesc">갤러리 설명 검색</string>
|
||||
<string name="search_storr">토렌트 파일 이름 검색</string>
|
||||
<string name="search_sto">토렌트 파일이 있는 갤러리만 검색</string>
|
||||
<string name="search_sdt1">Low-Power 태그 검색</string>
|
||||
<string name="search_sdt2">Downvoted 태그 검색</string>
|
||||
<string name="search_sh">Expunged 갤러리 표시</string>
|
||||
<string name="search_sto">토렌트 파일이 있는 갤러리만 검색</string>
|
||||
<string name="search_sr">최소 평점:</string>
|
||||
|
||||
<string name="select_image">이미지 선택</string>
|
||||
<string name="search_uss">유사한 이미지 검색</string>
|
||||
<string name="search_osc">커버만 검색</string>
|
||||
<string name="search_se">Expunged 이미지 표시</string>
|
||||
<string name="select_image_first">먼저 이미지를 선택해 주세요.</string>
|
||||
|
||||
<string name="add_to_favourites">즐겨찾기에 추가</string>
|
||||
|
||||
@@ -95,7 +95,6 @@
|
||||
<string name="skip_signing_in">ดำเนินการต่อโดยไม่ต้องลงชื่อเข้าระบบ</string>
|
||||
<string name="sign_in_failed">การลงชื่อเข้าใช้ล้มเหลว</string>
|
||||
<string name="sign_in_failed_plain">บางทีนี้เป็นข้อผิดพลาดเนื่องจากเว็บไซต์มีการอัพเดตหรือมีบางอย่างเกิดขึ้น คุณจะต้องลองวิธีลงชื่อเข้าใช้ผ่านมุมมองเว็บไซต์ หรือวิธีแบบคุกกี๊ หรือกดปุ่มข้ามไปก่อน แต่คุณอาจจะเจอ "แพนด้าผู้เดียวดาย" ได้หลังจากข้ามการลงชื่อเข้าใช้ อันดับแรกลอง "ลงชื่อเข้าใช้ผ่านคุกกี้" ถ้าคุณเคยเข้าสู่ระบบแล้วในเวอร์ชั่น 0.6.x</string>
|
||||
<string name="press_twice_exit">กดสองครั้งเพื่อออก</string>
|
||||
<string name="content_layout_restore_tip">แตะเพื่อลองอีกครั้ง</string>
|
||||
<string name="write_rationale">EhViewer จะขอสิทธิ์การเขียนข้อมูลพื้นที่จัดเก็บภายนอก (WRITE_EXTERNAL_STORAGE) เพื่อการดาวน์โหลดแกลเลอรี่ คุณจะอนุญาตหรือไม่?</string>
|
||||
<string name="you_rejected_me">เอื้อออออ เจ็บจัง</string>
|
||||
@@ -137,20 +136,13 @@
|
||||
|
||||
<string name="search_tip">ค้นหาแบบธรรมดา: ค้นหาแบบง๊ายง่าย\n\nระบุผู้อัพโหลด: ค้นหาแกลเลอรี่ที่อัพโหลดโดยระบุชื่อผู้ใช้ของคนอัพโหลด โดยตัวเลือกอื่นๆจะถูกข้ามไป\n\nระบุป้ายกำกับ: ค้นหาแกลเลอรี่ที่ถูกใส่ป้ายกำกับหรือแท็กไว้ในแต่ละแกลเลอรี่ ระบุได้เพียงหนึ่งป้ายกำกับ ตัวเลือกอื่นๆจะถูกข้ามไป</string>
|
||||
|
||||
<string name="search_sname">ค้นหาแกลเลอรี่</string>
|
||||
<string name="search_stags">ค้นหาแท็กของแกลเลอรี่</string>
|
||||
<string name="search_sdesc">ค้นหาคำอธิบายของแกลเลอรี่</string>
|
||||
<string name="search_storr">ค้นหาชื่อไฟล์ Torrent</string>
|
||||
<string name="search_sto">แสดงผลเฉพาะแกลเลอรี่ที่มีไฟล์ Torrents</string>
|
||||
<string name="search_sdt1">ค้นหาแท็กที่ไม่โด่งดัง</string>
|
||||
<string name="search_sdt2">ค้นหาแท็กที่ถูกโหวตไม่เห็นด้วย</string>
|
||||
<string name="search_sh">แสดงแกลเลอรี่ที่ถูกลบออก</string>
|
||||
<string name="search_sto">แสดงผลเฉพาะแกลเลอรี่ที่มีไฟล์ Torrents</string>
|
||||
<string name="search_sr">เรตติ้งขั้นต่ำ:</string>
|
||||
|
||||
<string name="select_image">เลือกรูปภาพ</string>
|
||||
<string name="search_uss">ใช้สแกนหาความเหมือน</string>
|
||||
<string name="search_osc">ค้นหาเฉพาะหน้าปก</string>
|
||||
<string name="search_se">แสดงเรื่องที่โดนลบ</string>
|
||||
<string name="select_image_first">โปรดเลือกรูปภาพก่อน</string>
|
||||
|
||||
<string name="add_to_favourites">เพิ่มเข้าในรายการโปรด</string>
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
<string name="sign_in_failed">登录失败</string>
|
||||
<string name="sign_in_failed_tip">若持续出错,请尝试“通过网页登录”或“通过 Cookie 登录”。</string>
|
||||
<string name="sign_in_failed_plain">这可能是网站更新导致的,也有可能是发生了奇怪的事情。您可以选择在登录按钮下方通过网页登录或者输入 cookie,或者跳过登录。跳过登录可能导致无法正常使用应用。如果你已经在 0.6.x 版登录过了,请优先尝试选择“通过 Cookie 登录”。</string>
|
||||
<string name="press_twice_exit">再按一次退出</string>
|
||||
<string name="content_layout_restore_tip">点击重试</string>
|
||||
<string name="write_rationale">EhViewer 将申请设备存储空间访问权限以便下载画廊。您愿意授权吗?</string>
|
||||
<string name="you_rejected_me">好痛</string>
|
||||
@@ -146,14 +145,8 @@
|
||||
|
||||
<string name="search_tip">一般搜索:就是搜索。\n\n订阅搜素:在订阅中搜索。\n\n指定上传者:列出该上传者上传的画廊。其他选项会被忽略。\n\n指定标签:列出含有该标签的画廊。其他选项会被忽略。</string>
|
||||
|
||||
<string name="search_sname">搜索画廊</string>
|
||||
<string name="search_stags">搜索画廊标签</string>
|
||||
<string name="search_sdesc">搜索画廊描述</string>
|
||||
<string name="search_storr">搜索种子文件名</string>
|
||||
<string name="search_sh">仅显示被删除的画廊</string>
|
||||
<string name="search_sto">仅显示有种子的画廊</string>
|
||||
<string name="search_sdt1">搜索低愿力标签</string>
|
||||
<string name="search_sdt2">搜索差评标签</string>
|
||||
<string name="search_sh">显示被删除的画廊</string>
|
||||
<string name="search_sr">最低评分:</string>
|
||||
<string name="search_sp">页数:</string>
|
||||
<string name="search_sp_to">到</string>
|
||||
@@ -165,7 +158,6 @@
|
||||
<string name="select_image">选择图片</string>
|
||||
<string name="search_uss">使用相似搜索</string>
|
||||
<string name="search_osc">只搜索封面</string>
|
||||
<string name="search_se">显示被删除的画廊</string>
|
||||
<string name="select_image_first">请先选择图片</string>
|
||||
|
||||
<string name="add_to_favourites">收藏</string>
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
<string name="sign_in_failed">登錄失敗</string>
|
||||
<string name="sign_in_failed_tip">若持續出錯,請嘗試“通過網頁登錄”或“通過 Cookie 登錄”。</string>
|
||||
<string name="sign_in_failed_plain">這可能是網站更新導致的,也有可能是發生了奇怪的事情。您可以選擇在登錄按鈕下方通過網頁登錄或者輸入 cookie,或者跳過登錄。跳過登錄可能導致無法正常使用應用。如果你已經在 0.6.x 版登錄過了,請優先嚐試選擇“通過 Cookie 登錄”。</string>
|
||||
<string name="press_twice_exit">再按一次退出</string>
|
||||
<string name="content_layout_restore_tip">點擊重試</string>
|
||||
<string name="write_rationale">EhViewer 將申請設備存儲空間訪問權限以便下載畫廊。您願意授權嗎?</string>
|
||||
<string name="you_rejected_me">好痛</string>
|
||||
@@ -146,14 +145,8 @@
|
||||
|
||||
<string name="search_tip">一般搜索:就是搜索。\n\n訂閲搜素:在訂閲中搜索。\n\n指定上傳者:列出該上傳者上傳的畫廊。其他選項會被忽略。\n\n指定標籤:列出含有該標籤的畫廊。其他選項會被忽略。</string>
|
||||
|
||||
<string name="search_sname">搜索畫廊</string>
|
||||
<string name="search_stags">搜索畫廊標籤</string>
|
||||
<string name="search_sdesc">搜索畫廊描述</string>
|
||||
<string name="search_storr">搜索種子文件名</string>
|
||||
<string name="search_sh">僅顯示被刪除的畫廊</string>
|
||||
<string name="search_sto">僅顯示有種子的畫廊</string>
|
||||
<string name="search_sdt1">搜索低願力標籤</string>
|
||||
<string name="search_sdt2">搜索差評標籤</string>
|
||||
<string name="search_sh">顯示被刪除的畫廊</string>
|
||||
<string name="search_sr">最低評分:</string>
|
||||
<string name="search_sp">頁數:</string>
|
||||
<string name="search_sp_to">到</string>
|
||||
@@ -165,7 +158,6 @@
|
||||
<string name="select_image">選擇圖片</string>
|
||||
<string name="search_uss">使用相似搜索</string>
|
||||
<string name="search_osc">只搜索封面</string>
|
||||
<string name="search_se">顯示被刪除的畫廊</string>
|
||||
<string name="select_image_first">請先選擇圖片</string>
|
||||
|
||||
<string name="add_to_favourites">收藏</string>
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
<string name="sign_in_failed">登入失敗</string>
|
||||
<string name="sign_in_failed_tip">若持續出錯,請嘗試“通過網頁登入”或“通過 Cookie 登入”。</string>
|
||||
<string name="sign_in_failed_plain">這可能是網站更新造成的,也有可能是哪裡出錯了。您可以選擇在登入按鈕下方透過網頁登入或者輸入 cookie,也可以略過登入。略過登入可能導致無法正常使用應用程式。如果你已經在 0.6.x 版登入過了,請先試試“透過 Cookie 登入”。</string>
|
||||
<string name="press_twice_exit">再按一次離開</string>
|
||||
<string name="content_layout_restore_tip">點擊重試</string>
|
||||
<string name="write_rationale">EhViewer 將請求裝置外部儲存空間的存取許可以下載圖庫。您願意授權嗎?</string>
|
||||
<string name="you_rejected_me">好難過</string>
|
||||
@@ -146,14 +145,8 @@
|
||||
|
||||
<string name="search_tip">一般搜尋:就是搜尋。\n\n訂閱搜尋:在訂閱中搜尋。\n\n指定上傳者:列出該上傳者上傳的圖庫。其他選項會被忽略。\n\n指定標籤:列出含有該標籤的圖庫。其他選項會被忽略。</string>
|
||||
|
||||
<string name="search_sname">搜尋圖庫</string>
|
||||
<string name="search_stags">搜尋圖庫的標籤</string>
|
||||
<string name="search_sdesc">搜尋圖庫的描述</string>
|
||||
<string name="search_storr">搜尋種子檔名</string>
|
||||
<string name="search_sh">只顯示已被移除的圖庫</string>
|
||||
<string name="search_sto">只顯示有種子的圖庫</string>
|
||||
<string name="search_sdt1">搜尋標籤:低願力</string>
|
||||
<string name="search_sdt2">搜尋標籤:被噓爆的</string>
|
||||
<string name="search_sh">顯示已被移除的的圖庫</string>
|
||||
<string name="search_sr">最低評分:</string>
|
||||
<string name="search_sp">頁數:</string>
|
||||
<string name="search_sp_to">到</string>
|
||||
@@ -165,7 +158,6 @@
|
||||
<string name="select_image">選擇圖片</string>
|
||||
<string name="search_uss">使用相似性搜尋</string>
|
||||
<string name="search_osc">只搜尋封面</string>
|
||||
<string name="search_se">顯示被刪除的圖庫</string>
|
||||
<string name="select_image_first">請先選擇圖片</string>
|
||||
|
||||
<string name="add_to_favourites">收藏</string>
|
||||
|
||||
@@ -144,7 +144,6 @@
|
||||
<string name="sign_in_failed">Sign in failed</string>
|
||||
<string name="sign_in_failed_tip">If this issue continues, try \"Sign in via WebView\" or \"Sign in via cookies\"</string>
|
||||
<string name="sign_in_failed_plain">Perhaps it is a mistake due to website update or something wrong happened. You may try sign in via WebView or enter cookie or skip signing in under sign in button. But you may be a "Sad Panda" after skipping signing in. Try "Sign in via cookies" first if you had signed in on 0.6.x version.</string>
|
||||
<string name="press_twice_exit">Press twice to exit</string>
|
||||
<string name="content_layout_restore_tip">Tap to retry</string>
|
||||
<string name="write_rationale">EhViewer will request WRITE_EXTERNAL_STORAGE permission for downloading gallery. Do you want to grant the permission?</string>
|
||||
<string name="you_rejected_me">It hurts</string>
|
||||
@@ -188,14 +187,8 @@
|
||||
|
||||
<string name="search_tip">Normal search: Simply search.\n\nSubscription search: Search in subscriptions.\n\nSpecify uploader: Search for galleries uploaded by the specified uploader. All other options will be ignored.\n\nSpecify tag: Search for galleries that contain the specified tag. Only one tag accepted. Other options will be ignored.</string>
|
||||
|
||||
<string name="search_sname">Search Gallery</string>
|
||||
<string name="search_stags">Search Gallery Tags</string>
|
||||
<string name="search_sdesc">Search Gallery Description</string>
|
||||
<string name="search_storr">Search Torrent Filenames</string>
|
||||
<string name="search_sh">Only Show Expunged Galleries</string>
|
||||
<string name="search_sto">Only Show Galleries With Torrents</string>
|
||||
<string name="search_sdt1">Search Low-Power Tags</string>
|
||||
<string name="search_sdt2">Search Downvoted Tags</string>
|
||||
<string name="search_sh">Show Expunged Galleries</string>
|
||||
<string name="search_sr">Minimum Rating:</string>
|
||||
<string name="search_sp">Pages:</string>
|
||||
<string name="search_sp_to">to</string>
|
||||
@@ -209,7 +202,6 @@
|
||||
<string name="select_image">Select image</string>
|
||||
<string name="search_uss">Use similarity scan</string>
|
||||
<string name="search_osc">Only search covers</string>
|
||||
<string name="search_se">Show expunged</string>
|
||||
<string name="select_image_first">Please select image first</string>
|
||||
|
||||
<string name="add_to_favourites">Add to favourites</string>
|
||||
@@ -619,7 +611,7 @@
|
||||
<string name="settings_about_declaration" translatable="false">EhViewer</string>
|
||||
<string name="settings_about_declaration_summary">EhViewer is not affiliated with E-Hentai.org in any way</string>
|
||||
<string name="settings_about_author">Author</string>
|
||||
<string name="settings_about_author_summary" translatable="false"><del>Hippo &lt;ehviewersu$gmail.com&gt;</del><br>NekoInverter<br>飛鳥澪</string>
|
||||
<string name="settings_about_author_summary" translatable="false"><del>Hippo &lt;ehviewersu$gmail.com&gt;</del><br><del>NekoInverter</del><br>飛鳥澪</string>
|
||||
<string name="settings_about_telegram">Telegram</string>
|
||||
<string name="settings_about_latest_release">Latest release</string>
|
||||
<string name="settings_about_website">Website</string>
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
<style name="AppTheme" parent="Base.AppTheme" />
|
||||
|
||||
<!-- Progress View -->
|
||||
<style name="ProgressView">
|
||||
<item name="android:indeterminate">true</item>
|
||||
<item name="android:minWidth">48dp</item>
|
||||
@@ -55,4 +54,13 @@
|
||||
</style>
|
||||
|
||||
<style name="Guide.Title" parent="TextAppearance.Material3.TitleMedium" />
|
||||
|
||||
<style name="Snackbar" parent="Widget.Material3.Snackbar">
|
||||
<item name="materialThemeOverlay">@style/SnackbarTheme</item>
|
||||
<item name="actionTextColorAlpha">1</item>
|
||||
</style>
|
||||
|
||||
<style name="SnackbarButton" parent="Widget.Material3.Button.TextButton.Snackbar">
|
||||
<item name="android:textColor">?attr/colorOnSurfaceInverse</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -16,15 +16,11 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<!-- ========== -->
|
||||
<!-- Base Theme -->
|
||||
<!-- ========== -->
|
||||
|
||||
<style name="Base.AppTheme" parent="Theme.Material3.DynamicColors.DayNight">
|
||||
<item name="dividerColor">@color/divider</item>
|
||||
<item name="drawableColorSecondary">?attr/colorOnSurface</item>
|
||||
<item name="drawableColorThemePrimary">?attr/colorPrimary</item>
|
||||
<item name="tagGroupBackgroundColor">?attr/colorSecondaryContainer</item>
|
||||
<item name="tagGroupBackgroundColor">?attr/colorPrimaryContainer</item>
|
||||
<item name="tagBackgroundColor">?attr/colorTertiaryContainer</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
@@ -32,6 +28,10 @@
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="filterChipStyle">@style/Widget.Material3.Chip.Filter</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Rikka.Material3</item>
|
||||
<item name="snackbarStyle">@style/Snackbar</item>
|
||||
<item name="snackbarButtonStyle">@style/SnackbarButton</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="bottomSheetDialogTheme">@style/BottomSheetDialogTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay" />
|
||||
@@ -45,12 +45,17 @@
|
||||
<item name="drawableColorThemePrimary">@color/primary_drawable_black</item>
|
||||
</style>
|
||||
|
||||
<!-- ============ -->
|
||||
<!-- Widget Theme -->
|
||||
<!-- ============ -->
|
||||
|
||||
<style name="RatingBarTheme" parent="ThemeOverlay.Material3">
|
||||
<item name="colorControlNormal">@color/yellow_800</item>
|
||||
<item name="colorControlActivated">@color/yellow_800</item>
|
||||
</style>
|
||||
|
||||
<style name="SnackbarTheme" parent="ThemeOverlay.Material3.Snackbar">
|
||||
<item name="colorPrimary">?attr/colorPrimaryInverse</item>
|
||||
<item name="colorOnSurface">?attr/colorSurfaceInverse</item>
|
||||
</style>
|
||||
|
||||
<style name="BottomSheetDialogTheme" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -43,12 +43,6 @@
|
||||
android:title="@string/settings_advanced_clear_download_path_cache"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<Preference
|
||||
android:key="clear_memory_cache"
|
||||
android:summary="@string/settings_advanced_clear_memory_cache_summary"
|
||||
android:title="@string/settings_advanced_clear_memory_cache"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<rikka.preference.SimpleMenuPreference
|
||||
android:defaultValue="320"
|
||||
android:key="read_cache_size"
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '8.0.0-alpha02' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
|
||||
id 'com.google.devtools.ksp' version '1.7.10-1.0.6' apply false
|
||||
id 'com.android.application' version '8.0.0-alpha08' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.21' apply false
|
||||
id 'com.google.devtools.ksp' version '1.7.21-1.0.8' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
||||
+3
-1
@@ -11,7 +11,7 @@
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -XX:+UseParallelGC
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
@@ -22,6 +22,8 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableAppCompileTimeRClass=true
|
||||
android.nonTransitiveRClass=true
|
||||
android.experimental.enableNewResourceShrinker=true
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
||||
|
||||
# signing environment variable
|
||||
signStoreFile=keystore/androidkey.jks
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,5 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-milestone-3-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,10 +80,10 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
@@ -143,12 +143,16 @@ fi
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
||||
Vendored
+1
@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
||||
Reference in new issue
Block a user