Compare commits

..
Author SHA1 Message Date
tarsin d9981f15f2 Bump version
Releases / Build (push) Has been cancelled
2022-11-15 22:19:21 +08:00
tarsin 27b75746fd Simplify clipboardutils 2022-11-15 22:17:12 +08:00
tarsin ee80a3a841 Copy to Clipboard refactor 2022-11-15 22:05:26 +08:00
tarsin f8d639054f Just use normal rv 2022-11-15 21:48:22 +08:00
tarsin 7f76e58e4e Move GalleryInfoBottomSheet out of scene package to allow obfuscate and repackage 2022-11-15 21:46:00 +08:00
tarsin 1b326a86a2 Convert GalleryInfoScene to BottomSheet, Resolve #354 2022-11-15 21:38:27 +08:00
tarsin 607bf4c67f Use inverse color on snackbar, #326 2022-11-15 15:30:44 +08:00
小白-白 3e4836c91e Support favorites prev page load 2022-11-15 12:54:41 +08:00
小白-白 dae8f21388 Support prev page load 2022-11-15 12:54:41 +08:00
小白-白 84b328b60b Init prev page load 2022-11-15 12:54:41 +08:00
小白-白 b98d55b444 Hide jump button on local fav cat 2022-11-15 12:54:41 +08:00
小白-白 ee9413e54a Fix favorite page jump 2022-11-15 12:54:41 +08:00
小白-白 76dedce5da Remove unavailable search options 2022-11-15 12:54:41 +08:00
tarsin 37f99ae706 Fuck off the "Press twice to exit"
It can't be more stupid on our modern gesture navigation
2022-11-14 19:34:37 +08:00
tarsin aee58d967d Kotlin 1.7.21 2022-11-14 14:42:41 +08:00
tarsin 5ecabeb045 Rearrange gallery actions, resolve #413 2022-11-14 14:29:02 +08:00
tarsin 408c142aad Haptic feedback for slider, #408 2022-11-14 14:09:54 +08:00
tarsin a56d541380 fixup! Adjust madvise usage 2022-11-10 15:28:06 +08:00
tarsin ae79417845 Adjust madvise usage 2022-11-10 15:21:17 +08:00
小白-白 010daa583e Remove unavailable advanced search options 2022-11-10 12:36:08 +08:00
renovate[bot] 3034957c99 Update room_version to v2.5.0-beta02 (1.8.x.x) (#402)
Update room_version to v2.5.0-beta02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-10 08:23:57 +08:00
renovate[bot] 99134346ed Update dependency androidx.compose.material3:material3 to v1.0.1 (1.8.x.x) (#399)
Update dependency androidx.compose.material3:material3 to v1.0.1

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-10 08:23:00 +08:00
renovate[bot] 9a70708300 Update compose_version to v1.3.1 2022-11-09 23:13:03 +00:00
tarsin 50f0df5012 Explicitly set format and filter we use
This helps to reduce native library size
2022-11-10 00:56:49 +08:00
小白-白 599bb80106 Fix toplist page load 2022-11-09 23:15:38 +08:00
小白-白 aa0db1f400 Hide jump button on the popular page 2022-11-09 23:15:38 +08:00
skymkmk 82b6fd22a9 Enhancement: Add delete image file option to the dialog on the gallery page (#391) 2022-11-08 15:26:29 +08:00
renovate[bot] b5c730d37a Update plugin com.android.application to v8.0.0-alpha08 (1.8.x.x) (#388)
Update plugin com.android.application to v8.0.0-alpha08

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-08 04:36:48 +00:00
txyyh a2cb6444f4 Revert "Remove preview count limit #332 (#333)"
This reverts commit 783fa10157.
2022-11-07 10:28:34 +00:00
tarsin 8a144eabca Reset ContentLayout changes
This component is quite a shit, further we must rewrite it to better adapt new page-off gid-based load design
2022-11-07 18:26:27 +08:00
tarsin 75c2a3b0ec Tweak HistoryScene with modern kotlin 2022-11-06 23:19:15 +08:00
tarsin beec02a69b Fix #370 2022-11-06 21:59:08 +08:00
tarsin df78b45f69 Initial support for jump to certain date 2022-11-06 14:58:05 +08:00
tarsin e6a3c8f165 Rename .java to .kt 2022-11-06 14:58:05 +08:00
tarsin 4d948e93a3 Datepicker stub 2022-11-06 13:23:49 +08:00
tarsin ece082727b Release 1.8.3.0-alpha04
Releases / Build (push) Has been cancelled
2022-11-06 13:02:55 +08:00
tarsin 5b5040f4af No gallery found no more a exception 2022-11-06 13:02:05 +08:00
tarsin d9d88e690b Fix favourite list load first 2022-11-06 12:57:51 +08:00
tarsin bda123fc39 Build with JDK 19
Gradle 8.0.0-M3 should be able to work with JDK19
2022-11-06 00:22:59 +08:00
tarsin 2200bb9b45 Upgrade java compatibility to Java 17 2022-11-06 00:21:25 +08:00
txyyh 884d460b52 Rel 1.8.3.0-alpha03
Releases / Build (push) Has been cancelled
2022-11-05 09:50:13 +00:00
tarsin beb97270a0 Initial support for predictive back gesture
https://developer.android.com/guide/navigation/predictive-back-gesture
2022-11-05 12:48:03 +08:00
小白-白 52681bd386 Fix favorite page load (#368)
修复收藏页面按发布时间排序时无法翻页问题
2022-11-04 11:40:30 +08:00
renovate[bot] d772a737fc Update plugin com.google.devtools.ksp to v1.7.20-1.0.8 (1.8.x.x) (#367)
Update plugin com.google.devtools.ksp to v1.7.20-1.0.8

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-04 11:15:20 +08:00
tarsin 638fcfea12 Do not cancel a sent call
Should fix slow load speed
2022-11-04 00:22:25 +08:00
tarsin cae32cca5a Rel 1.8.3.0-alpha02
Releases / Build (push) Has been cancelled
2022-11-03 23:19:06 +08:00
tarsin c429e7054a Don't parse found in common path 2022-11-03 23:11:33 +08:00
tarsin f526f11c73 Fix tag search page load 2022-11-03 22:59:27 +08:00
tarsin 892016368f Fix favourite scene data restore 2022-11-03 22:54:28 +08:00
tarsin d9464ac278 Fix uploader page request 2022-11-03 16:20:47 +08:00
tarsin a68a9995a1 Rel 1.8.3.0-alpha01
Releases / Build (push) Has been cancelled
2022-11-03 16:07:16 +08:00
tarsin 1a0c2cb180 Relax founds limit
We will calculate accurately later
2022-11-03 16:01:50 +08:00
tarsin d7572f5a79 Fix restore state 2022-11-03 15:48:35 +08:00
tarsin a208651e13 Code clean and allow refresh pages 2022-11-03 15:31:31 +08:00
tarsin 41c231b4f3 Bump version to 1.8.3.0-alpha
Releases / Build (push) Has been cancelled
2022-11-03 14:13:32 +08:00
tarsin 567b6f7659 Minimal changes to rework Favourite grabs
TODO: Reimpl goto, refresh pages
And take care of per page counts by user
2022-11-03 13:18:38 +08:00
tarsin 6d1e0089e5 Minimal changes to rework new exhentai page load
TODO: Reimpl Favourite grab, goto, refresh pages
2022-11-03 11:48:04 +08:00
renovate[bot] 502f532fe7 Update plugin com.android.application to v8.0.0-alpha07 (1.8.x.x) (#357)
Update plugin com.android.application to v8.0.0-alpha07

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-02 19:24:59 +00:00
tarsin ddc47f09ab Resolve #348 2022-11-01 11:18:44 +08:00
tarsin 5dbcdf2fce Use thinlto 2022-11-01 00:00:07 +08:00
tarsin 706588c39c Kotlinize 2022-10-31 23:53:44 +08:00
tarsin 3e3e64942e Rename .java to .kt 2022-10-31 23:53:44 +08:00
renovate[bot] 3700ac8a84 Update dependency gradle to v8.0-milestone-3 (1.8.x.x) (#350)
Update dependency gradle to v8.0-milestone-3

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-28 23:05:06 +08:00
renovate[bot] cc0dc0abf5 Update lifecycle_version to v2.6.0-alpha03 (1.8.x.x) (#340)
Update lifecycle_version to v2.6.0-alpha03

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 12:14:55 +08:00
renovate[bot] 5861147d17 Update dependency androidx.paging:paging-runtime to v3.2.0-alpha03 (1.8.x.x) (#338)
Update dependency androidx.paging:paging-runtime to v3.2.0-alpha03

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 12:14:42 +08:00
renovate[bot] 732035a0e2 Update dependency com.google.android.material:material to v1.8.0-alpha02 (1.8.x.x) (#339)
Update dependency com.google.android.material:material to v1.8.0-alpha02

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 12:14:28 +08:00
renovate[bot] 643f71009d Update nav_version to v2.5.3 (1.8.x.x) (#341)
Update nav_version to v2.5.3

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 12:14:12 +08:00
renovate[bot] 4643e37adb Update plugin com.android.application to v8.0.0-alpha06 (1.8.x.x) (#342)
Update plugin com.android.application to v8.0.0-alpha06

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 12:13:55 +08:00
renovate[bot] 1bc8854685 Update dependency androidx.compose.material3:material3 to v1.0.0 (1.8.x.x) (#337)
Update dependency androidx.compose.material3:material3 to v1.0.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-25 02:07:28 +00:00
renovate[bot] 66ebf6a391 Update compose_version to v1.3.0 (1.8.x.x) (#336)
Update compose_version to v1.3.0

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-24 23:01:21 +00:00
renovate[bot] 1239277cec Update activity_version to v1.6.1 (1.8.x.x) (#335)
Update activity_version to v1.6.1

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-24 19:34:27 +00:00
tarsin 9cb8fb5527 Enable new resources shrinker
And full mode r8 is enabled by default on AGP8 above
2022-10-23 19:04:56 +08:00
tarsin 819d67dca8 Upgrade to gradle 8.0.0M2 2022-10-23 13:05:29 +08:00
tarsin f895982fa0 Revert to jdk 18 2022-10-23 12:45:59 +08:00
tarsin 505c097659 Build with jdk19 and enable parallel-gc
https://blog.bitrise.io/post/switch-to-jdk-17-parallel-gc-and-speed-up-your-android-builds-by-9-20
2022-10-23 12:35:05 +08:00
小白-白 783fa10157 Remove preview count limit #332 (#333)
fix: preview count limit
2022-10-23 01:25:06 +08:00
tarsin 82ffbf2a1d Reset unlock status if within delay time 2022-10-20 13:33:50 +08:00
renovate[bot] 3c3ae47511 Update plugin com.android.application to v8.0.0-alpha05 (1.8.x.x) (#331)
Update plugin com.android.application to v8.0.0-alpha05

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-18 19:44:59 +00:00
tarsin 858d7acf68 Fix code style 2022-10-17 00:43:35 +08:00
renovate[bot] f8b7005690 Update plugin com.android.application to v8.0.0-alpha04 (1.8.x.x) (#330)
Update plugin com.android.application to v8.0.0-alpha04

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-15 03:29:10 +00:00
renovate[bot] 33a8cb98e6 Update plugin com.google.devtools.ksp to v1.7.20-1.0.7 (1.8.x.x) (#329)
Update plugin com.google.devtools.ksp to v1.7.20-1.0.7

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-14 02:17:50 +00:00
tarsin 338a22f2ed No need to trigger gc manually 2022-10-09 14:44:42 +08:00
tarsin f7cecaf709 on stack tile buffer 2022-10-08 21:05:42 +08:00
tarsin c38b3c15e7 Tweak tags color 2022-10-08 20:48:40 +08:00
tarsin 4665678e9f fixup! Use MaterialIconButton 2022-10-08 20:35:43 +08:00
tarsin 07416eb74d Use MaterialIconButton 2022-10-07 19:20:01 +08:00
renovate[bot] 6f37dc53c7 Update plugin com.android.application to v8.0.0-alpha03 (1.8.x.x) (#327)
Update plugin com.android.application to v8.0.0-alpha03

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-07 02:07:07 +00:00
tarsin 55b963dcf9 Kotlin 1.7.20 2022-10-06 12:29:35 +08:00
txyyh 1a433019b3 Add strikethrough (#325) 2022-10-06 12:21:02 +08:00
renovate[bot] 7b292cb1ab Update dependency androidx.compose.material3:material3 to v1.0.0-rc01 (1.8.x.x) (#322)
Update dependency androidx.compose.material3:material3 to v1.0.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-06 11:07:03 +08:00
renovate[bot] b2f5590c04 Update fragment_version to v1.6.0-alpha03 (1.8.x.x) (#323)
Update fragment_version to v1.6.0-alpha03

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-06 09:36:07 +08:00
renovate[bot] 2cf2c87a8b Update room_version to v2.5.0-beta01 (1.8.x.x) (#324)
Update room_version to v2.5.0-beta01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-06 09:35:54 +08:00
renovate[bot] 01420f7e72 Update compose_version to v1.3.0-rc01 (1.8.x.x) (#321)
Update compose_version to v1.3.0-rc01

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-05 23:01:01 +00:00
84 changed files with 2358 additions and 2838 deletions

No files matched your search

+2 -2
View File
@@ -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: |
+2 -2
View File
@@ -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: |
+2 -2
View File
@@ -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
View File
@@ -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'
+1
View File
@@ -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"
+2 -2
View File
@@ -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
View File
@@ -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);
+35
View File
@@ -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 */
+1 -13
View File
@@ -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;
-29
View File
@@ -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 */
-45
View File
@@ -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;
}
}
+267
View File
@@ -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>
+1 -1
View File
@@ -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>
+23 -3
View File
@@ -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"
+1 -9
View File
@@ -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>
+1 -9
View File
@@ -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>
+1 -9
View File
@@ -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>
+1 -9
View File
@@ -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>
+1 -9
View File
@@ -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>
+1 -9
View File
@@ -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>
+1 -9
View File
@@ -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>
+1 -9
View File
@@ -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>
+1 -9
View File
@@ -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>
+2 -10
View File
@@ -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">&lt;del&gt;Hippo &amp;lt;ehviewersu$gmail.com&amp;gt;&lt;/del&gt;&lt;br&gt;NekoInverter&lt;br&gt;飛鳥澪</string>
<string name="settings_about_author_summary" translatable="false">&lt;del&gt;Hippo &amp;lt;ehviewersu$gmail.com&amp;gt;&lt;/del&gt;&lt;br&gt;&lt;del&gt;NekoInverter&lt;/del&gt;&lt;br&gt;飛鳥澪</string>
<string name="settings_about_telegram">Telegram</string>
<string name="settings_about_latest_release">Latest release</string>
<string name="settings_about_website">Website</string>
+9 -1
View File
@@ -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>
+14 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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
Binary file not shown.
+2 -1
View File
@@ -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
Vendored
+8 -4
View File
@@ -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
View File
@@ -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%