Compare commits
53
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
884d460b52 | ||
|
|
beb97270a0 | ||
|
|
52681bd386 | ||
|
|
d772a737fc | ||
|
|
638fcfea12 | ||
|
|
cae32cca5a | ||
|
|
c429e7054a | ||
|
|
f526f11c73 | ||
|
|
892016368f | ||
|
|
d9464ac278 | ||
|
|
a68a9995a1 | ||
|
|
1a0c2cb180 | ||
|
|
d7572f5a79 | ||
|
|
a208651e13 | ||
|
|
41c231b4f3 | ||
|
|
567b6f7659 | ||
|
|
6d1e0089e5 | ||
|
|
502f532fe7 | ||
|
|
ddc47f09ab | ||
|
|
5dbcdf2fce | ||
|
|
706588c39c | ||
|
|
3e3e64942e | ||
|
|
3700ac8a84 | ||
|
|
cc0dc0abf5 | ||
|
|
5861147d17 | ||
|
|
732035a0e2 | ||
|
|
643f71009d | ||
|
|
4643e37adb | ||
|
|
1bc8854685 | ||
|
|
66ebf6a391 | ||
|
|
1239277cec | ||
|
|
9cb8fb5527 | ||
|
|
819d67dca8 | ||
|
|
f895982fa0 | ||
|
|
505c097659 | ||
|
|
783fa10157 | ||
|
|
82ffbf2a1d | ||
|
|
3c3ae47511 | ||
|
|
858d7acf68 | ||
|
|
f8b7005690 | ||
|
|
33a8cb98e6 | ||
|
|
338a22f2ed | ||
|
|
f7cecaf709 | ||
|
|
c38b3c15e7 | ||
|
|
4665678e9f | ||
|
|
07416eb74d | ||
|
|
6f37dc53c7 | ||
|
|
55b963dcf9 | ||
|
|
1a433019b3 | ||
|
|
7b292cb1ab | ||
|
|
b2f5590c04 | ||
|
|
2cf2c87a8b | ||
|
|
01420f7e72 |
No files matched your search
@@ -17,11 +17,11 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 18
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 18
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
|
||||
@@ -12,11 +12,11 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 18
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 18
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
|
||||
@@ -15,11 +15,11 @@ jobs:
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Setup Java 11
|
||||
- name: Setup Java 18
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 18
|
||||
|
||||
- name: Install NDK
|
||||
run: |
|
||||
|
||||
+12
-12
@@ -31,8 +31,8 @@ android {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 31
|
||||
targetSdkVersion 33
|
||||
versionCode 180013
|
||||
versionName "1.8.2.4"
|
||||
versionCode 180017
|
||||
versionName "1.8.3.0-alpha03"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -86,14 +86,14 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.0"
|
||||
kotlinCompilerExtensionVersion = "1.3.2"
|
||||
}
|
||||
|
||||
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.0"
|
||||
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.0"
|
||||
|
||||
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-beta01"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-paging:$room_version"
|
||||
@@ -172,7 +172,7 @@ dependencies {
|
||||
implementation 'androidx.webkit:webkit:1.5.0'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.8.0-alpha01'
|
||||
implementation 'com.google.android.material:material:1.8.0-alpha02'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
android:allowBackup="true"
|
||||
android:appCategory="image"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_scheme"
|
||||
android:hasFragileUserData="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
project(ehviewer)
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto \
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections -flto=thin \
|
||||
-mllvm -polly \
|
||||
-mllvm -polly-run-dce \
|
||||
-mllvm -polly-run-inliner \
|
||||
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
|
||||
add_subdirectory(libarchive)
|
||||
|
||||
# Build and link our app's native lib
|
||||
add_library(${PROJECT_NAME} SHARED main.c archive.c image.c fdutils.c)
|
||||
add_library(${PROJECT_NAME} SHARED archive.c image.c fdutils.c)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
|
||||
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#include "image.h"
|
||||
|
||||
#define TAG "ImageDecoder_wrapper"
|
||||
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG ,__VA_ARGS__)
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG ,__VA_ARGS__)
|
||||
@@ -38,17 +36,7 @@
|
||||
|
||||
#define IMAGE_TILE_MAX_SIZE (512 * 512)
|
||||
|
||||
static void *tile_buffer = NULL;
|
||||
|
||||
bool image_onLoad() {
|
||||
tile_buffer = malloc(IMAGE_TILE_MAX_SIZE * 4);
|
||||
return tile_buffer != NULL;
|
||||
}
|
||||
|
||||
void image_onUnload() {
|
||||
free(tile_buffer);
|
||||
tile_buffer = NULL;
|
||||
}
|
||||
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
|
||||
|
||||
typedef struct {
|
||||
AImageDecoder *decoder;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef IMAGE_IMAGE_H
|
||||
#define IMAGE_IMAGE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool image_onLoad();
|
||||
|
||||
void image_onUnload();
|
||||
|
||||
#endif /* IMAGE_IMAGE_H */
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <archive.h>
|
||||
#include <lzma.h>
|
||||
#include <zlib.h>
|
||||
#include <nettle/config.h>
|
||||
|
||||
#include "image.h"
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
JNIEnv *env;
|
||||
|
||||
if ((*vm)->GetEnv(vm, (void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
if (!image_onLoad(vm)) {
|
||||
return JNI_ERR;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
JNI_OnUnload(JavaVM *vm, void *reserved) {
|
||||
image_onUnload();
|
||||
}
|
||||
@@ -15,15 +15,16 @@
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.hippo
|
||||
|
||||
package com.hippo;
|
||||
import java.io.FileDescriptor
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
|
||||
public class Native {
|
||||
public static void initialize() {
|
||||
System.loadLibrary("ehviewer");
|
||||
object Native {
|
||||
@JvmStatic
|
||||
fun initialize() {
|
||||
System.loadLibrary("ehviewer")
|
||||
}
|
||||
|
||||
public static native int getFd(FileDescriptor fd);
|
||||
}
|
||||
@JvmStatic
|
||||
external fun getFd(fd: FileDescriptor?): Int
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.hippo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
public class UriArchiveAccessor {
|
||||
ParcelFileDescriptor pfd;
|
||||
Context ctx;
|
||||
Uri uri;
|
||||
|
||||
public UriArchiveAccessor(Context ctx, Uri uri) {
|
||||
this.ctx = ctx;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public int open() throws Exception {
|
||||
pfd = ctx.getContentResolver().openFileDescriptor(uri, "r");
|
||||
return openArchive(pfd.getFd(), pfd.getStatSize());
|
||||
}
|
||||
|
||||
private native int openArchive(int fd, long size);
|
||||
|
||||
public native long extractToAddr(int index);
|
||||
|
||||
public native void extractToFd(int index, int fd);
|
||||
|
||||
public native String getFilename(int index);
|
||||
|
||||
public native boolean needPassword();
|
||||
|
||||
public native boolean providePassword(String str);
|
||||
|
||||
private native void closeArchive();
|
||||
|
||||
public void close() throws Exception {
|
||||
closeArchive();
|
||||
pfd.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2022 Tarsin Norbin
|
||||
*
|
||||
* This file is part of EhViewer
|
||||
*
|
||||
* EhViewer is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* EhViewer is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.hippo
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
||||
class UriArchiveAccessor(ctx: Context, uri: Uri) {
|
||||
val pfd by lazy { ctx.contentResolver.openFileDescriptor(uri, "r")!! }
|
||||
fun open(): Int {
|
||||
return openArchive(pfd.fd, pfd.statSize)
|
||||
}
|
||||
|
||||
private external fun openArchive(fd: Int, size: Long): Int
|
||||
external fun extractToAddr(index: Int): Long
|
||||
external fun extractToFd(index: Int, fd: Int)
|
||||
external fun getFilename(index: Int): String
|
||||
external fun needPassword(): Boolean
|
||||
external fun providePassword(str: String): Boolean
|
||||
private external fun closeArchive()
|
||||
fun close() {
|
||||
closeArchive()
|
||||
pfd.close()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
}
|
||||
};
|
||||
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
|
||||
private int mIndex;
|
||||
private String mNext;
|
||||
private String mKeyword;
|
||||
private int mFavCat = FAV_CAT_ALL;
|
||||
|
||||
@@ -52,7 +52,7 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
}
|
||||
|
||||
protected FavListUrlBuilder(Parcel in) {
|
||||
this.mIndex = in.readInt();
|
||||
this.mNext = in.readString();
|
||||
this.mKeyword = in.readString();
|
||||
this.mFavCat = in.readInt();
|
||||
}
|
||||
@@ -61,8 +61,8 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
return favCat >= 0 && favCat <= 9;
|
||||
}
|
||||
|
||||
public void setIndex(int index) {
|
||||
mIndex = index;
|
||||
public void setNext(String next) {
|
||||
mNext = next;
|
||||
}
|
||||
|
||||
public String getKeyword() {
|
||||
@@ -105,8 +105,8 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
Log.e(TAG, "Can't URLEncoder.encode " + mKeyword);
|
||||
}
|
||||
}
|
||||
if (mIndex > 0) {
|
||||
ub.addQuery("page", Integer.toString(mIndex));
|
||||
if (mNext != null && !mNext.isEmpty()) {
|
||||
ub.addQuery("next", mNext);
|
||||
}
|
||||
return ub.build();
|
||||
}
|
||||
@@ -118,7 +118,7 @@ public class FavListUrlBuilder implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mIndex);
|
||||
dest.writeString(this.mNext);
|
||||
dest.writeString(this.mKeyword);
|
||||
dest.writeInt(this.mFavCat);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
@Mode
|
||||
private int mMode = MODE_NORMAL;
|
||||
|
||||
private int mPageIndex = 0;
|
||||
private int mNextGid = 0;
|
||||
private int mPrevGid = 0;
|
||||
|
||||
private int mCategory = EhUtils.NONE;
|
||||
private String mKeyword = null;
|
||||
@@ -86,7 +87,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
@SuppressWarnings("WrongConstant")
|
||||
protected ListUrlBuilder(Parcel in) {
|
||||
this.mMode = in.readInt();
|
||||
this.mPageIndex = in.readInt();
|
||||
this.mNextGid = in.readInt();
|
||||
this.mPrevGid = in.readInt();
|
||||
this.mCategory = in.readInt();
|
||||
this.mKeyword = in.readString();
|
||||
this.mAdvanceSearch = in.readInt();
|
||||
@@ -105,7 +107,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
*/
|
||||
public void reset() {
|
||||
mMode = MODE_NORMAL;
|
||||
mPageIndex = 0;
|
||||
mNextGid = 0;
|
||||
mPrevGid = 0;
|
||||
mCategory = EhUtils.NONE;
|
||||
mKeyword = null;
|
||||
mAdvanceSearch = -1;
|
||||
@@ -137,12 +140,12 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
mMode = mode;
|
||||
}
|
||||
|
||||
public int getPageIndex() {
|
||||
return mPageIndex;
|
||||
public void setNextGid(int nextGid) {
|
||||
mNextGid = nextGid;
|
||||
}
|
||||
|
||||
public void setPageIndex(int pageIndex) {
|
||||
mPageIndex = pageIndex;
|
||||
public void setPrevGid(int prevGid) {
|
||||
mPrevGid = prevGid;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
@@ -233,7 +236,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
*/
|
||||
public void set(ListUrlBuilder lub) {
|
||||
mMode = lub.mMode;
|
||||
mPageIndex = lub.mPageIndex;
|
||||
mNextGid = lub.mNextGid;
|
||||
mPrevGid = lub.mPrevGid;
|
||||
mCategory = lub.mCategory;
|
||||
mKeyword = lub.mKeyword;
|
||||
mAdvanceSearch = lub.mAdvanceSearch;
|
||||
@@ -524,9 +528,11 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
if (mSHash != null) {
|
||||
ub.addQuery("f_shash", mSHash);
|
||||
}
|
||||
// Page index
|
||||
if (mPageIndex != 0) {
|
||||
ub.addQuery("page", mPageIndex);
|
||||
if (mNextGid != 0) {
|
||||
ub.addQuery("next", mNextGid);
|
||||
}
|
||||
if (mPrevGid != 0) {
|
||||
ub.addQuery("prev", mPrevGid);
|
||||
}
|
||||
// Advance search
|
||||
if (mAdvanceSearch != -1) {
|
||||
@@ -570,8 +576,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Empty
|
||||
}
|
||||
if (mPageIndex != 0) {
|
||||
sb.append('/').append(mPageIndex);
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -583,8 +589,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Empty
|
||||
}
|
||||
if (mPageIndex != 0) {
|
||||
sb.append('/').append(mPageIndex);
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -600,8 +606,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Empty
|
||||
}
|
||||
if (mPageIndex != 0) {
|
||||
sb.append("&p=").append(mPageIndex);
|
||||
if (mNextGid != 0) {
|
||||
sb.append("&p=").append(mNextGid);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -615,7 +621,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(this.mMode);
|
||||
dest.writeInt(this.mPageIndex);
|
||||
dest.writeInt(this.mNextGid);
|
||||
dest.writeInt(this.mPrevGid);
|
||||
dest.writeInt(this.mCategory);
|
||||
dest.writeString(this.mKeyword);
|
||||
dest.writeInt(this.mAdvanceSearch);
|
||||
|
||||
@@ -31,8 +31,10 @@ import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FavoritesParser {
|
||||
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>")) {
|
||||
@@ -59,14 +61,25 @@ public class FavoritesParser {
|
||||
e.printStackTrace();
|
||||
throw new ParseException("Parse favorites error", body);
|
||||
}
|
||||
Result re = new Result();
|
||||
|
||||
try {
|
||||
var d = Jsoup.parse(body);
|
||||
var snv = d.getElementsByClass("searchnav").first();
|
||||
assert snv != null;
|
||||
var href = snv.child(3).child(0).attr("href");
|
||||
var matcher = PATTERN_NEXT_PAGE.matcher(href);
|
||||
if (matcher.find()) {
|
||||
re.nextPage = matcher.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 +88,7 @@ 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 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_GETINT = Pattern.compile("[^0-9]");
|
||||
|
||||
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
|
||||
new String[]{"0", "0", "0"},
|
||||
@@ -297,39 +298,21 @@ public class GalleryListParser {
|
||||
return gi;
|
||||
}
|
||||
|
||||
private static int getInt(String str) {
|
||||
var m = PATTERN_GETINT.matcher(str);
|
||||
return Integer.parseInt(m.replaceAll("").trim());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
//noinspection unchecked
|
||||
result.galleryInfoList = Collections.EMPTY_LIST;
|
||||
return result;
|
||||
} else if (d.getElementsByClass("ptt").isEmpty()) {
|
||||
result.pages = 1;
|
||||
} else {
|
||||
result.pages = Integer.MAX_VALUE;
|
||||
}
|
||||
result.founds = Integer.MAX_VALUE;
|
||||
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
|
||||
if (body.contains("No hits found</p>")) {
|
||||
result.founds = 0;
|
||||
result.galleryInfoList = Collections.EMPTY_LIST;
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -364,8 +347,7 @@ public class GalleryListParser {
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public int pages;
|
||||
public int nextPage;
|
||||
public int founds;
|
||||
public boolean noWatchedTags;
|
||||
public List<GalleryInfo> galleryInfoList;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,10 @@ import java.util.Objects;
|
||||
|
||||
public final class CommonOperations {
|
||||
|
||||
public static int getPagesForFounds(int founds, int per) {
|
||||
return (int)Math.ceil((double) founds/per);
|
||||
}
|
||||
|
||||
private static void doAddToFavorites(Activity activity, GalleryInfo galleryInfo,
|
||||
int slot, EhClient.Callback<Void> listener) {
|
||||
if (slot == -1) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.SparseBooleanArray;
|
||||
@@ -850,7 +851,14 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
updateSearchBar();
|
||||
mHelper.onGetPageData(taskId, result.pages, result.nextPage, result.galleryInfoList);
|
||||
int pages = 0;
|
||||
assert mUrlBuilder != null;
|
||||
if (FavListUrlBuilder.isValidFavCat(mUrlBuilder.getFavCat()))
|
||||
pages = CommonOperations.getPagesForFounds(mFavCountArray[mUrlBuilder.getFavCat()], 50);
|
||||
else if (mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_ALL)
|
||||
pages = CommonOperations.getPagesForFounds(mFavCountSum, 50);
|
||||
mHelper.nextPg = result.nextPage;
|
||||
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
|
||||
|
||||
if (mDrawerAdapter != null) {
|
||||
mDrawerAdapter.notifyDataSetChanged();
|
||||
@@ -1169,6 +1177,8 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
|
||||
private class FavoritesHelper extends GalleryInfoContentHelper {
|
||||
public int pgCounter = 0;
|
||||
public String nextPg = null;
|
||||
|
||||
@Override
|
||||
protected void getPageData(final int taskId, int type, int page) {
|
||||
@@ -1176,6 +1186,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
if (null == activity || null == mUrlBuilder || null == mClient) {
|
||||
return;
|
||||
}
|
||||
pgCounter = page;
|
||||
|
||||
if (mEnableModify) {
|
||||
mEnableModify = false;
|
||||
@@ -1215,7 +1226,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 +1239,7 @@ public class FavoritesScene extends BaseScene implements
|
||||
final String keyword = mUrlBuilder.getKeyword();
|
||||
SimpleHandler.getInstance().post(() -> onGetFavoritesLocal(keyword, taskId));
|
||||
} else {
|
||||
mUrlBuilder.setIndex(page);
|
||||
mUrlBuilder.setNext(nextPg);
|
||||
String url = mUrlBuilder.build();
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_GET_FAVORITES);
|
||||
@@ -1291,5 +1302,25 @@ public class FavoritesScene extends BaseScene implements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClearData() {
|
||||
super.onClearData();
|
||||
nextPg = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable saveInstanceState(Parcelable superState) {
|
||||
Bundle bundle = (Bundle) super.saveInstanceState(superState);
|
||||
bundle.putString(KEY_NEXT_PAGE, nextPg);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Parcelable restoreInstanceState(Parcelable state) {
|
||||
Bundle bundle = (Bundle) state;
|
||||
nextPg = bundle.getString(KEY_NEXT_PAGE);
|
||||
return super.restoreInstanceState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,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;
|
||||
@@ -203,8 +202,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;
|
||||
@@ -604,9 +601,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 +610,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 +692,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 +765,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;
|
||||
@@ -1067,7 +1044,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
if (gd.previewPages <= 0 || previewSet == null || previewSet.size() == 0) {
|
||||
mPreviewText.setText(R.string.no_previews);
|
||||
return;
|
||||
} else if (gd.previewPages == 1 && previewSet.size() <= 60) {
|
||||
} else if (gd.previewPages == 1) {
|
||||
mPreviewText.setText(R.string.no_more_previews);
|
||||
} else {
|
||||
mPreviewText.setText(R.string.more_previews);
|
||||
@@ -1076,8 +1053,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
int columnWidth = resources.getDimensionPixelOffset(Settings.getThumbSizeResId());
|
||||
mGridLayout.setColumnSize(columnWidth);
|
||||
mGridLayout.setStrategy(SimpleGridAutoSpanLayout.STRATEGY_SUITABLE_SIZE);
|
||||
int size = Math.min(60, previewSet.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int i = 0, size = previewSet.size(); i < size; i++) {
|
||||
View view = inflater.inflate(R.layout.item_gallery_preview, mGridLayout, false);
|
||||
mGridLayout.addView(view);
|
||||
|
||||
@@ -1244,7 +1220,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(GalleryInfoScene.KEY_GALLERY_DETAIL, mGalleryDetail);
|
||||
startScene(new Announcer(GalleryInfoScene.class).setArgs(args));
|
||||
} else if (mHeartGroup == v) {
|
||||
} else if (mHeart == v || mHeartOutline == v) {
|
||||
if (mGalleryDetail != null && !mModifingFavorites) {
|
||||
boolean remove = false;
|
||||
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
|
||||
@@ -1491,7 +1467,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) {
|
||||
|
||||
@@ -91,6 +91,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;
|
||||
@@ -778,6 +779,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()) {
|
||||
@@ -1184,6 +1200,7 @@ public final class GalleryListScene extends BaseScene
|
||||
break;
|
||||
}
|
||||
}
|
||||
((StageActivity)requireActivity()).updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1374,7 +1391,7 @@ 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);
|
||||
mHelper.onGetPageData(taskId, CommonOperations.getPagesForFounds(result.founds, 25), mHelper.pgCounter + 1, result.galleryInfoList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,7 +1564,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 +1581,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 +1711,20 @@ 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 (page != 0)
|
||||
mUrlBuilder.setNextGid(minGid);
|
||||
else
|
||||
mUrlBuilder.setNextGid(0);
|
||||
if (ListUrlBuilder.MODE_IMAGE_SEARCH == mUrlBuilder.getMode()) {
|
||||
EhRequest request = new EhRequest();
|
||||
request.setMethod(EhClient.METHOD_IMAGE_SEARCH);
|
||||
|
||||
@@ -31,8 +31,14 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelper<GalleryInfo> {
|
||||
public int maxGid = -1;
|
||||
public int minGid = -1;
|
||||
|
||||
private static final String KEY_DATA_MAP = "data_map";
|
||||
private static final String KEY_MAX_GID = "max_gid";
|
||||
private static final String KEY_MIN_GID = "min_gid";
|
||||
|
||||
public static final String KEY_NEXT_PAGE = "next_page";
|
||||
private final FavouriteStatusRouter.Listener listener;
|
||||
@SuppressLint("UseSparseArrays")
|
||||
private Map<Long, GalleryInfo> map = new HashMap<>();
|
||||
@@ -59,6 +65,12 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
|
||||
@Override
|
||||
protected void onAddData(List<GalleryInfo> data) {
|
||||
for (GalleryInfo info : data) {
|
||||
if (maxGid == -1)
|
||||
maxGid = (int) info.gid;
|
||||
if (minGid == -1)
|
||||
minGid = (int) info.gid;
|
||||
maxGid = (int) Math.max(maxGid, info.gid);
|
||||
minGid = (int) Math.min(minGid, info.gid);
|
||||
map.put(info.gid, info);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +90,8 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
|
||||
@Override
|
||||
protected void onClearData() {
|
||||
map.clear();
|
||||
maxGid = -1;
|
||||
minGid = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +102,8 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
|
||||
FavouriteStatusRouter router = EhApplication.getFavouriteStatusRouter();
|
||||
int id = router.saveDataMap(map);
|
||||
bundle.putInt(KEY_DATA_MAP, id);
|
||||
bundle.putInt(KEY_MIN_GID, minGid);
|
||||
bundle.putInt(KEY_MAX_GID, maxGid);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
@@ -104,6 +120,8 @@ public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelp
|
||||
this.map = map;
|
||||
}
|
||||
}
|
||||
minGid = bundle.getInt(KEY_MIN_GID);
|
||||
maxGid = bundle.getInt(KEY_MAX_GID);
|
||||
|
||||
return super.restoreInstanceState(state);
|
||||
}
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.image;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.Native;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* The {@code Image} is a image which stored pixel data in native heap
|
||||
*/
|
||||
public final class Image {
|
||||
public static final int FORMAT_NORMAL = 0;
|
||||
public static final int FORMAT_ANIMATED = 1;
|
||||
private static final ArrayList<Image> imageList = new ArrayList<>();
|
||||
private static boolean needEvictAll = false;
|
||||
private long mNativePtr;
|
||||
|
||||
private boolean needRecycle = false;
|
||||
|
||||
private Integer nativeProcessCnt = 0;
|
||||
|
||||
private Image(long nativePtr) {
|
||||
if (needEvictAll)
|
||||
evictAll();
|
||||
mNativePtr = nativePtr;
|
||||
imageList.add(this);
|
||||
}
|
||||
|
||||
private static Image newFromAddr(long addr) {
|
||||
if (addr == 0)
|
||||
return null;
|
||||
return new Image(addr);
|
||||
}
|
||||
|
||||
public static void lazyEvictAll() {
|
||||
needEvictAll = true;
|
||||
}
|
||||
|
||||
private static void evictAll() {
|
||||
(new ArrayList<>(imageList)).forEach(Image::recycle);
|
||||
needEvictAll = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(FileDescriptor fd, boolean partially) {
|
||||
return newFromAddr(nativeDecode(Native.getFd(fd)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decode(Integer fd, boolean partially) {
|
||||
return newFromAddr(nativeDecode(fd));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from {@code InputStream}
|
||||
*/
|
||||
@Nullable
|
||||
public static Image decodeAddr(Long addr, boolean partially) {
|
||||
return newFromAddr(nativeDecodeAddr(addr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@Nullable
|
||||
public static Image create(Bitmap bitmap) {
|
||||
return newFromAddr(nativeCreate(bitmap));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled {@code Image} instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
public static int getImageCount() {
|
||||
return imageList.size();
|
||||
}
|
||||
|
||||
private static native long nativeDecode(int fd);
|
||||
|
||||
private static native long nativeDecodeAddr(long addr);
|
||||
|
||||
private static native long nativeCreate(Bitmap bitmap);
|
||||
|
||||
private static native void nativeRender(long nativePtr,
|
||||
int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height);
|
||||
|
||||
private static native void nativeTexImage(long nativePtr, boolean init, int offsetX, int offsetY, int width, int height);
|
||||
|
||||
private static native void nativeAdvance(long nativePtr);
|
||||
|
||||
private static native int nativeGetDelay(long nativePtr);
|
||||
|
||||
private static native boolean nativeIsOpaque(long nativePtr);
|
||||
|
||||
private static native void nativeRecycle(long nativePtr);
|
||||
|
||||
private static native int nativeGetFormat(long nativePtr);
|
||||
|
||||
private static native int nativeGetWidth(long nativePtr);
|
||||
|
||||
private static native int nativeGetHeight(long nativePtr);
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
public int getFormat() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetFormat(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the image
|
||||
*/
|
||||
public int getWidth() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetWidth(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the image
|
||||
*/
|
||||
public int getHeight() {
|
||||
checkRecycledAndLock();
|
||||
int ret = nativeGetHeight(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private synchronized void checkRecycledAndLock() {
|
||||
if (mNativePtr == 0) {
|
||||
throw new IllegalStateException("The image is recycled.");
|
||||
}
|
||||
nativeProcessCnt++;
|
||||
}
|
||||
|
||||
private synchronized void releaseNativeLock() {
|
||||
nativeProcessCnt--;
|
||||
if (needRecycle)
|
||||
recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the image to {@code Bitmap}
|
||||
*/
|
||||
public void render(int srcX, int srcY, Bitmap dst, int dstX, int dstY,
|
||||
int width, int height, boolean fillBlank, int defaultColor) {
|
||||
checkRecycledAndLock();
|
||||
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call {@code glTexImage2D} for init is true and
|
||||
* call {@code glTexSubImage2D} for init is false.
|
||||
* width * height must <= 512 * 512 or do nothing
|
||||
*/
|
||||
public void texImage(boolean init, int offsetX, int offsetY, int width, int height) {
|
||||
checkRecycledAndLock();
|
||||
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to next frame. Do nothing for non-animation image
|
||||
*/
|
||||
public void advance() {
|
||||
checkRecycledAndLock();
|
||||
nativeAdvance(mNativePtr);
|
||||
releaseNativeLock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current frame delay. 0 for non-animation image
|
||||
*/
|
||||
public int getDelay() {
|
||||
checkRecycledAndLock();
|
||||
int delay = nativeGetDelay(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return delay <= 10 ? 100 : delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return is the image opaque
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
checkRecycledAndLock();
|
||||
boolean ret = nativeIsOpaque(mNativePtr);
|
||||
releaseNativeLock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the native object associated with this image.
|
||||
* It must be called when the image will not be used.
|
||||
* The image can't be used after this method is called.
|
||||
*/
|
||||
public synchronized void recycle() {
|
||||
if (mNativePtr != 0) {
|
||||
if (nativeProcessCnt != 0) {
|
||||
needRecycle = true;
|
||||
return;
|
||||
}
|
||||
nativeRecycle(mNativePtr);
|
||||
mNativePtr = 0;
|
||||
imageList.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this image has been recycled.
|
||||
*/
|
||||
public boolean isRecycled() {
|
||||
return mNativePtr == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.hippo.Native.getFd
|
||||
import java.io.FileDescriptor
|
||||
import java.util.function.Consumer
|
||||
|
||||
/**
|
||||
* The `Image` is a image which stored pixel data in native heap
|
||||
*/
|
||||
class Image private constructor(nativePtr: Long) {
|
||||
private var mNativePtr: Long
|
||||
private var needRecycle = false
|
||||
private var nativeProcessCnt = 0
|
||||
|
||||
init {
|
||||
if (needEvictAll) evictAll()
|
||||
mNativePtr = nativePtr
|
||||
imageList.add(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
val format: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetFormat(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the width of the image
|
||||
*/
|
||||
val width: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetWidth(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the height of the image
|
||||
*/
|
||||
val height: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeGetHeight(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun checkRecycledAndLock() {
|
||||
check(mNativePtr != 0L) { "The image is recycled." }
|
||||
nativeProcessCnt++
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun releaseNativeLock() {
|
||||
nativeProcessCnt--
|
||||
if (needRecycle) recycle()
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the image to `Bitmap`
|
||||
*/
|
||||
fun render(
|
||||
srcX: Int, srcY: Int, dst: Bitmap, dstX: Int, dstY: Int,
|
||||
width: Int, height: Int, fillBlank: Boolean, defaultColor: Int
|
||||
) {
|
||||
checkRecycledAndLock()
|
||||
nativeRender(mNativePtr, srcX, srcY, dst, dstX, dstY, width, height)
|
||||
releaseNativeLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Call `glTexImage2D` for init is true and
|
||||
* call `glTexSubImage2D` for init is false.
|
||||
* width * height must <= 512 * 512 or do nothing
|
||||
*/
|
||||
fun texImage(init: Boolean, offsetX: Int, offsetY: Int, width: Int, height: Int) {
|
||||
checkRecycledAndLock()
|
||||
nativeTexImage(mNativePtr, init, offsetX, offsetY, width, height)
|
||||
releaseNativeLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to next frame. Do nothing for non-animation image
|
||||
*/
|
||||
fun advance() {
|
||||
checkRecycledAndLock()
|
||||
nativeAdvance(mNativePtr)
|
||||
releaseNativeLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current frame delay. 0 for non-animation image
|
||||
*/
|
||||
val delay: Int
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val delay = nativeGetDelay(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return if (delay <= 10) 100 else delay
|
||||
}
|
||||
|
||||
/**
|
||||
* Return is the image opaque
|
||||
*/
|
||||
val isOpaque: Boolean
|
||||
get() {
|
||||
checkRecycledAndLock()
|
||||
val ret = nativeIsOpaque(mNativePtr)
|
||||
releaseNativeLock()
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the native object associated with this image.
|
||||
* It must be called when the image will not be used.
|
||||
* The image can't be used after this method is called.
|
||||
*/
|
||||
@Synchronized
|
||||
fun recycle() {
|
||||
if (mNativePtr != 0L) {
|
||||
if (nativeProcessCnt != 0) {
|
||||
needRecycle = true
|
||||
return
|
||||
}
|
||||
nativeRecycle(mNativePtr)
|
||||
mNativePtr = 0
|
||||
imageList.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this image has been recycled.
|
||||
*/
|
||||
val isRecycled: Boolean
|
||||
get() = mNativePtr == 0L
|
||||
|
||||
companion object {
|
||||
const val FORMAT_NORMAL = 0
|
||||
const val FORMAT_ANIMATED = 1
|
||||
private val imageList = ArrayList<Image>()
|
||||
private var needEvictAll = false
|
||||
private fun newFromAddr(addr: Long): Image? {
|
||||
return if (addr == 0L) null else Image(addr)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun lazyEvictAll() {
|
||||
needEvictAll = true
|
||||
}
|
||||
|
||||
private fun evictAll() {
|
||||
ArrayList(imageList).forEach(
|
||||
Consumer { obj: Image -> obj.recycle() })
|
||||
needEvictAll = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: FileDescriptor?, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecode(getFd(fd)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: Int, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecode(fd))
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode image from `InputStream`
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decodeAddr(addr: Long, partially: Boolean): Image? {
|
||||
return newFromAddr(nativeDecodeAddr(addr))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plain image from Bitmap
|
||||
*/
|
||||
@JvmStatic
|
||||
fun create(bitmap: Bitmap): Image? {
|
||||
return newFromAddr(nativeCreate(bitmap))
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all un-recycled `Image` instance count.
|
||||
* It is useful for debug.
|
||||
*/
|
||||
@JvmStatic
|
||||
val imageCount: Int
|
||||
get() = imageList.size
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeDecode(fd: Int): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeDecodeAddr(addr: Long): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeCreate(bitmap: Bitmap): Long
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeRender(
|
||||
nativePtr: Long,
|
||||
srcX: Int, srcY: Int, dst: Bitmap, dstX: Int, dstY: Int,
|
||||
width: Int, height: Int
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeTexImage(
|
||||
nativePtr: Long,
|
||||
init: Boolean,
|
||||
offsetX: Int,
|
||||
offsetY: Int,
|
||||
width: Int,
|
||||
height: Int
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeAdvance(nativePtr: Long)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetDelay(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeIsOpaque(nativePtr: Long): Boolean
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeRecycle(nativePtr: Long)
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetFormat(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetWidth(nativePtr: Long): Int
|
||||
|
||||
@JvmStatic
|
||||
private external fun nativeGetHeight(nativePtr: Long): Int
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.image;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A image with {@link Image} for data and {@link Bitmap} for render.
|
||||
*/
|
||||
public final class ImageBitmap implements Animatable, Runnable {
|
||||
|
||||
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
|
||||
@NonNull
|
||||
private final Bitmap mBitmap;
|
||||
private final int mFormat;
|
||||
private final boolean mIsOpaque;
|
||||
private final Set<WeakReference<Callback>> mCallbackSet = new LinkedHashSet<>();
|
||||
@Nullable
|
||||
private Image mImage;
|
||||
private int mReferences;
|
||||
private int mAnimationReferences;
|
||||
private boolean mRunning;
|
||||
|
||||
private ImageBitmap(@NonNull Image image) throws OutOfMemoryError {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
image.render(0, 0, mBitmap, 0, 0, width, height, false, 0);
|
||||
mFormat = image.getFormat();
|
||||
mIsOpaque = image.isOpaque();
|
||||
|
||||
if (mFormat == 1) {
|
||||
// For animated image, save image object
|
||||
mImage = image;
|
||||
} else {
|
||||
// Free the image
|
||||
image.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private ImageBitmap(@NonNull Bitmap bitmap) {
|
||||
mFormat = Image.FORMAT_NORMAL;
|
||||
mBitmap = bitmap;
|
||||
mIsOpaque = !bitmap.hasAlpha();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode {@code InputStream}, then create image.
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap decode(@NonNull FileDescriptor fd) {
|
||||
Image image = Image.decode(fd, false);
|
||||
if (image != null) {
|
||||
return create(image);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@code ImageBitmap} from {@code Image}.
|
||||
* It is not recommended. Use {@link #decode(InputStream)} if you can.
|
||||
*
|
||||
* @param image the image should not be used before and
|
||||
* it must not be recycled. And the image should
|
||||
* not be used directly anymore.
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap create(@NonNull Image image) {
|
||||
if (!image.isRecycled()) {
|
||||
try {
|
||||
return new ImageBitmap(image);
|
||||
} catch (OutOfMemoryError e) {
|
||||
image.recycle();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@code ImageBitmap} from {@code Bitmap}.
|
||||
*
|
||||
* @param bitmap the bitmap should not be recycled
|
||||
*/
|
||||
@Nullable
|
||||
public static ImageBitmap create(@NonNull Bitmap bitmap) {
|
||||
if (!bitmap.isRecycled()) {
|
||||
return new ImageBitmap(bitmap);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the image bitmap
|
||||
*
|
||||
* @return false for the image is recycled and obtain failed
|
||||
*/
|
||||
public synchronized boolean obtain() {
|
||||
if (mBitmap.isRecycled()) {
|
||||
return false;
|
||||
} else {
|
||||
++mReferences;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the image bitmap
|
||||
*/
|
||||
public synchronized void release() {
|
||||
--mReferences;
|
||||
if (mReferences <= 0 && !mBitmap.isRecycled()) {
|
||||
mBitmap.recycle();
|
||||
if (mImage != null) {
|
||||
mImage.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback for invalidating
|
||||
*/
|
||||
public void addCallback(@NonNull Callback callback) {
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
Callback c;
|
||||
while (iterator.hasNext()) {
|
||||
c = iterator.next().get();
|
||||
if (c == null) {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove();
|
||||
} else if (c == callback) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mCallbackSet.add(new WeakReference<>(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback
|
||||
*/
|
||||
public void removeCallback(@NonNull Callback callback) {
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
Callback c;
|
||||
while (iterator.hasNext()) {
|
||||
c = iterator.next().get();
|
||||
if (c == null) {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove();
|
||||
} else if (c == callback) {
|
||||
iterator.remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
public int getFormat() {
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image width
|
||||
*/
|
||||
public int getWidth() {
|
||||
return mBitmap.getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image height
|
||||
*/
|
||||
public int getHeight() {
|
||||
return mBitmap.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image is opaque
|
||||
*/
|
||||
public boolean isOpaque() {
|
||||
return mIsOpaque;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image is animated
|
||||
*/
|
||||
public boolean isAnimated() {
|
||||
return mImage != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, float left, float top, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, left, top, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, @Nullable Rect src, @NonNull Rect dst, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
public void draw(Canvas canvas, @Nullable Rect src, @NonNull RectF dst, @Nullable Paint paint) {
|
||||
if (!mBitmap.isRecycled()) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code start()} and {@code stop()} is a pair
|
||||
*/
|
||||
@Override
|
||||
public void start() {
|
||||
mAnimationReferences++;
|
||||
if (mBitmap.isRecycled() || mImage == null || mRunning) {
|
||||
return;
|
||||
}
|
||||
mRunning = true;
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage.getDelay()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code start()} and {@code stop()} is a pair
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
mAnimationReferences--;
|
||||
if (mAnimationReferences <= 0) {
|
||||
mRunning = false;
|
||||
HANDLER.removeCallbacks(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mRunning;
|
||||
}
|
||||
|
||||
private boolean notifyUpdate() {
|
||||
boolean hasCallback = false;
|
||||
final Iterator<WeakReference<Callback>> iterator = mCallbackSet.iterator();
|
||||
Callback callback;
|
||||
while (iterator.hasNext()) {
|
||||
callback = iterator.next().get();
|
||||
if (callback != null) {
|
||||
// Render bitmap int the first time
|
||||
if (!hasCallback) {
|
||||
hasCallback = true;
|
||||
mImage.render(0, 0, mBitmap, 0, 0, mImage.getWidth(), mImage.getHeight(), false, 0);
|
||||
}
|
||||
callback.invalidateImage(this);
|
||||
} else {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
return hasCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Check recycled
|
||||
if (mBitmap.isRecycled() || mImage == null) {
|
||||
mRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mImage.advance();
|
||||
|
||||
if (notifyUpdate()) {
|
||||
if (mRunning) {
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage.getDelay()));
|
||||
}
|
||||
} else {
|
||||
mRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void invalidateImage(ImageBitmap who);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import java.io.FileDescriptor
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* A image with [Image] for data and [Bitmap] for render.
|
||||
*/
|
||||
class ImageBitmap : Animatable, Runnable {
|
||||
private val mBitmap: Bitmap
|
||||
|
||||
/**
|
||||
* Return the format of the image
|
||||
*/
|
||||
val format: Int
|
||||
|
||||
/**
|
||||
* Return image is opaque
|
||||
*/
|
||||
val isOpaque: Boolean
|
||||
private val mCallbackSet: MutableSet<WeakReference<Callback>> = LinkedHashSet()
|
||||
private var mImage: Image? = null
|
||||
private var mReferences = 0
|
||||
private var mAnimationReferences = 0
|
||||
private var mRunning = false
|
||||
|
||||
private constructor(image: Image) {
|
||||
val width = image.width
|
||||
val height = image.height
|
||||
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
image.render(0, 0, mBitmap, 0, 0, width, height, false, 0)
|
||||
format = image.format
|
||||
isOpaque = image.isOpaque
|
||||
if (format == 1) {
|
||||
// For animated image, save image object
|
||||
mImage = image
|
||||
} else {
|
||||
// Free the image
|
||||
image.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(bitmap: Bitmap) {
|
||||
format = Image.FORMAT_NORMAL
|
||||
mBitmap = bitmap
|
||||
isOpaque = !bitmap.hasAlpha()
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the image bitmap
|
||||
*
|
||||
* @return false for the image is recycled and obtain failed
|
||||
*/
|
||||
@Synchronized
|
||||
fun obtain(): Boolean {
|
||||
return if (mBitmap.isRecycled) {
|
||||
false
|
||||
} else {
|
||||
++mReferences
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the image bitmap
|
||||
*/
|
||||
@Synchronized
|
||||
fun release() {
|
||||
--mReferences
|
||||
if (mReferences <= 0 && !mBitmap.isRecycled) {
|
||||
mBitmap.recycle()
|
||||
if (mImage != null) {
|
||||
mImage!!.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback for invalidating
|
||||
*/
|
||||
fun addCallback(callback: Callback) {
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var c: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
c = iterator.next().get()
|
||||
if (c == null) {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
} else if (c === callback) {
|
||||
return
|
||||
}
|
||||
}
|
||||
mCallbackSet.add(WeakReference(callback))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a callback
|
||||
*/
|
||||
fun removeCallback(callback: Callback) {
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var c: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
c = iterator.next().get()
|
||||
if (c == null) {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
} else if (c === callback) {
|
||||
iterator.remove()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return image width
|
||||
*/
|
||||
val width: Int
|
||||
get() = mBitmap.width
|
||||
|
||||
/**
|
||||
* Return image height
|
||||
*/
|
||||
val height: Int
|
||||
get() = mBitmap.height
|
||||
|
||||
/**
|
||||
* Return image is animated
|
||||
*/
|
||||
val isAnimated: Boolean
|
||||
get() = mImage != null
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, left: Float, top: Float, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, left, top, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, src: Rect?, dst: Rect, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw image to canvas
|
||||
*/
|
||||
fun draw(canvas: Canvas, src: Rect?, dst: RectF, paint: Paint?) {
|
||||
if (!mBitmap.isRecycled) {
|
||||
canvas.drawBitmap(mBitmap, src, dst, paint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `start()` and `stop()` is a pair
|
||||
*/
|
||||
override fun start() {
|
||||
mAnimationReferences++
|
||||
if (mBitmap.isRecycled || mImage == null || mRunning) {
|
||||
return
|
||||
}
|
||||
mRunning = true
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage!!.delay).toLong())
|
||||
}
|
||||
|
||||
/**
|
||||
* `start()` and `stop()` is a pair
|
||||
*/
|
||||
override fun stop() {
|
||||
mAnimationReferences--
|
||||
if (mAnimationReferences <= 0) {
|
||||
mRunning = false
|
||||
HANDLER.removeCallbacks(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRunning(): Boolean {
|
||||
return mRunning
|
||||
}
|
||||
|
||||
private fun notifyUpdate(): Boolean {
|
||||
var hasCallback = false
|
||||
val iterator = mCallbackSet.iterator()
|
||||
var callback: Callback?
|
||||
while (iterator.hasNext()) {
|
||||
callback = iterator.next().get()
|
||||
if (callback != null) {
|
||||
// Render bitmap int the first time
|
||||
if (!hasCallback) {
|
||||
hasCallback = true
|
||||
mImage!!.render(0, 0, mBitmap, 0, 0, mImage!!.width, mImage!!.height, false, 0)
|
||||
}
|
||||
callback.invalidateImage(this)
|
||||
} else {
|
||||
// Remove from the set if the reference has been cleared or
|
||||
// it can't be used.
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
return hasCallback
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
// Check recycled
|
||||
if (mBitmap.isRecycled || mImage == null) {
|
||||
mRunning = false
|
||||
return
|
||||
}
|
||||
mImage!!.advance()
|
||||
if (notifyUpdate()) {
|
||||
if (mRunning) {
|
||||
HANDLER.postDelayed(this, Math.max(0, mImage!!.delay).toLong())
|
||||
}
|
||||
} else {
|
||||
mRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun invalidateImage(who: ImageBitmap?)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val HANDLER = Handler(Looper.getMainLooper())
|
||||
|
||||
/**
|
||||
* Decode `InputStream`, then create image.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun decode(fd: FileDescriptor): ImageBitmap? {
|
||||
val image = Image.decode(fd, false)
|
||||
return if (image != null) {
|
||||
create(image)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `ImageBitmap` from `Image`.
|
||||
* It is not recommended. Use [.decode] if you can.
|
||||
*
|
||||
* @param image the image should not be used before and
|
||||
* it must not be recycled. And the image should
|
||||
* not be used directly anymore.
|
||||
*/
|
||||
fun create(image: Image): ImageBitmap? {
|
||||
return if (!image.isRecycled) {
|
||||
try {
|
||||
ImageBitmap(image)
|
||||
} catch (e: OutOfMemoryError) {
|
||||
image.recycle()
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `ImageBitmap` from `Bitmap`.
|
||||
*
|
||||
* @param bitmap the bitmap should not be recycled
|
||||
*/
|
||||
fun create(bitmap: Bitmap): ImageBitmap? {
|
||||
return if (!bitmap.isRecycled) {
|
||||
ImageBitmap(bitmap)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.image;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
/**
|
||||
* A drawable to draw {@link ImageBitmap}
|
||||
*/
|
||||
public class ImageDrawable extends Drawable implements Animatable, ImageBitmap.Callback {
|
||||
|
||||
private final ImageBitmap mImageBitmap;
|
||||
private final Paint mPaint;
|
||||
|
||||
public ImageDrawable(ImageBitmap imageBitmap) throws Exception {
|
||||
if (!imageBitmap.obtain()) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
mImageBitmap = imageBitmap;
|
||||
mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
|
||||
// Add callback
|
||||
imageBitmap.addCallback(this);
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
mImageBitmap.removeCallback(this);
|
||||
mImageBitmap.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
mImageBitmap.draw(canvas, null, getBounds(), mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return mImageBitmap.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mImageBitmap.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mImageBitmap.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
mImageBitmap.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
mImageBitmap.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mImageBitmap.isRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateImage(ImageBitmap who) {
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.image
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
/**
|
||||
* A drawable to draw [ImageBitmap]
|
||||
*/
|
||||
class ImageDrawable(imageBitmap: ImageBitmap) : Drawable(), Animatable, ImageBitmap.Callback {
|
||||
private val mImageBitmap: ImageBitmap
|
||||
private val mPaint: Paint
|
||||
|
||||
init {
|
||||
if (!imageBitmap.obtain()) {
|
||||
throw Exception()
|
||||
}
|
||||
mImageBitmap = imageBitmap
|
||||
mPaint = Paint(Paint.FILTER_BITMAP_FLAG)
|
||||
|
||||
// Add callback
|
||||
imageBitmap.addCallback(this)
|
||||
}
|
||||
|
||||
fun recycle() {
|
||||
mImageBitmap.removeCallback(this)
|
||||
mImageBitmap.release()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
mImageBitmap.draw(canvas, null, bounds, mPaint)
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
mPaint.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
mPaint.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int {
|
||||
return if (mImageBitmap.isOpaque) PixelFormat.OPAQUE else PixelFormat.UNKNOWN
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return mImageBitmap.width
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
return mImageBitmap.height
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
mImageBitmap.start()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
mImageBitmap.stop()
|
||||
}
|
||||
|
||||
override fun isRunning(): Boolean {
|
||||
return mImageBitmap.isRunning
|
||||
}
|
||||
|
||||
override fun invalidateImage(who: ImageBitmap?) {
|
||||
invalidateSelf()
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ public class SceneFragment extends Fragment {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity instanceof StageActivity) {
|
||||
((StageActivity) activity).startScene(announcer, horizontal);
|
||||
((StageActivity) activity).updateBackPressCallBackStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -31,6 +32,7 @@ import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.ui.EhActivity;
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
|
||||
@@ -58,6 +60,8 @@ public abstract class StageActivity extends EhActivity {
|
||||
private final SceneViewComparator mSceneViewComparator = new SceneViewComparator();
|
||||
private int mStageId = IntIdGenerator.INVALID_ID;
|
||||
|
||||
private OnBackPressedCallback callback;
|
||||
|
||||
public static void registerLaunchMode(Class<?> clazz, @SceneFragment.LaunchMode int launchMode) {
|
||||
if (launchMode != SceneFragment.LAUNCH_MODE_STANDARD &&
|
||||
launchMode != SceneFragment.LAUNCH_MODE_SINGLE_TOP &&
|
||||
@@ -140,6 +144,8 @@ public abstract class StageActivity extends EhActivity {
|
||||
protected final void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
callback = new BackPressCallBack(false);
|
||||
getOnBackPressedDispatcher().addCallback(callback);
|
||||
if (savedInstanceState != null) {
|
||||
mStageId = savedInstanceState.getInt(KEY_STAGE_ID, IntIdGenerator.INVALID_ID);
|
||||
ArrayList<String> list = savedInstanceState.getStringArrayList(KEY_SCENE_TAG_LIST);
|
||||
@@ -247,6 +253,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
|
||||
public void startScene(Announcer announcer) {
|
||||
startScene(announcer, false);
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
public void startScene(Announcer announcer, boolean horizontal) {
|
||||
@@ -461,6 +468,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
// TODO Call onNewArguments when view created ?
|
||||
scene.onNewArguments(args);
|
||||
}
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
int getSceneIndex(SceneFragment scene) {
|
||||
@@ -477,6 +485,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
|
||||
public void finishScene(SceneFragment scene) {
|
||||
finishScene(scene.getTag());
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
private void finishScene(String tag) {
|
||||
@@ -558,25 +567,51 @@ public abstract class StageActivity extends EhActivity {
|
||||
} else {
|
||||
fragmentManager.beginTransaction().detach(fragment).attach(fragment).commitAllowingStateLoss();
|
||||
}
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
int size = mSceneTagList.size();
|
||||
String tag = mSceneTagList.get(size - 1);
|
||||
SceneFragment scene;
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
Log.e(TAG, "onBackPressed: Can't find scene by tag: " + tag);
|
||||
return;
|
||||
}
|
||||
if (!(fragment instanceof SceneFragment)) {
|
||||
Log.e(TAG, "onBackPressed: The fragment is not SceneFragment");
|
||||
return;
|
||||
private boolean shouldEnableBackPressCallBack() {
|
||||
if (mSceneTagList.size() > 1)
|
||||
return true;
|
||||
if (mSceneTagList.size() == 0)
|
||||
return false;
|
||||
String tag = mSceneTagList.get(0);
|
||||
GalleryListScene fragment = (GalleryListScene) getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null)
|
||||
return false;
|
||||
return !fragment.isBackpressCanPreviewLauncherStatus();
|
||||
}
|
||||
|
||||
public void updateBackPressCallBackStatus() {
|
||||
var isEnabled = callback.isEnabled();
|
||||
var shouldEnable = shouldEnableBackPressCallBack();
|
||||
if (isEnabled != shouldEnable)
|
||||
callback.setEnabled(shouldEnable);
|
||||
}
|
||||
|
||||
class BackPressCallBack extends OnBackPressedCallback {
|
||||
public BackPressCallBack(boolean enabled) {
|
||||
super(enabled);
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onBackPressed();
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
int size = mSceneTagList.size();
|
||||
String tag = mSceneTagList.get(size - 1);
|
||||
SceneFragment scene;
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
|
||||
if (fragment == null) {
|
||||
Log.e(TAG, "onBackPressed: Can't find scene by tag: " + tag);
|
||||
return;
|
||||
}
|
||||
if (!(fragment instanceof SceneFragment)) {
|
||||
Log.e(TAG, "onBackPressed: The fragment is not SceneFragment");
|
||||
return;
|
||||
}
|
||||
|
||||
scene = (SceneFragment) fragment;
|
||||
scene.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -451,48 +451,22 @@ public class ContentLayout extends FrameLayout {
|
||||
|
||||
switch (mCurrentTaskType) {
|
||||
case TYPE_REFRESH:
|
||||
mStartPage = 0;
|
||||
mEndPage = 1;
|
||||
mPages = pages;
|
||||
mNextPage = nextPage;
|
||||
mPageDivider.clear();
|
||||
mPageDivider.add(data.size());
|
||||
|
||||
if (data.isEmpty()) {
|
||||
mData.clear();
|
||||
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);
|
||||
}
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showEmptyString();
|
||||
} else {
|
||||
mData.clear();
|
||||
onClearData();
|
||||
mData.addAll(data);
|
||||
onAddData(data);
|
||||
notifyDataSetChanged();
|
||||
|
||||
// Ui change, show content
|
||||
mRefreshLayout.setRefreshing(false);
|
||||
mBottomProgress.hide();
|
||||
showContent();
|
||||
|
||||
// RecyclerView scroll
|
||||
if (mRecyclerView.isAttachedToWindow()) {
|
||||
mRecyclerView.stopScroll();
|
||||
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), 0, 0);
|
||||
@@ -513,33 +487,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 +541,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 +595,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();
|
||||
@@ -804,6 +748,12 @@ public class ContentLayout extends FrameLayout {
|
||||
}
|
||||
|
||||
private void doRefresh() {
|
||||
mStartPage = 0;
|
||||
mEndPage = 1;
|
||||
mPages = 0;
|
||||
mPageDivider.clear();
|
||||
mData.clear();
|
||||
onClearData();
|
||||
mCurrentTaskId = mIdGenerator.nextId();
|
||||
mCurrentTaskType = TYPE_REFRESH;
|
||||
mCurrentTaskPage = 0;
|
||||
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
<Button
|
||||
android:id="@+id/heart_outline"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/not_favorited"
|
||||
app:icon="@drawable/v_heart_outline_primary_x48" />
|
||||
|
||||
<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
|
||||
<Button
|
||||
android:id="@+id/share"
|
||||
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" />
|
||||
app:icon="@drawable/v_share_primary_x48" />
|
||||
|
||||
<TextView
|
||||
<Button
|
||||
android:id="@+id/torrent"
|
||||
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:textColor="?android:attr/textColorPrimary" />
|
||||
app:icon="@drawable/v_utorrent_primary_x48" />
|
||||
|
||||
<TextView
|
||||
<Button
|
||||
android:id="@+id/archive"
|
||||
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/archive"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
app:icon="@drawable/v_archive_primary_x48" />
|
||||
|
||||
<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" />
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
@@ -619,7 +619,7 @@
|
||||
<string name="settings_about_declaration" translatable="false">EhViewer</string>
|
||||
<string name="settings_about_declaration_summary">EhViewer is not affiliated with E-Hentai.org in any way</string>
|
||||
<string name="settings_about_author">Author</string>
|
||||
<string name="settings_about_author_summary" translatable="false"><del>Hippo &lt;ehviewersu$gmail.com&gt;</del><br>NekoInverter<br>飛鳥澪</string>
|
||||
<string name="settings_about_author_summary" translatable="false"><del>Hippo &lt;ehviewersu$gmail.com&gt;</del><br><del>NekoInverter</del><br>飛鳥澪</string>
|
||||
<string name="settings_about_telegram">Telegram</string>
|
||||
<string name="settings_about_latest_release">Latest release</string>
|
||||
<string name="settings_about_website">Website</string>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<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 +32,7 @@
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="filterChipStyle">@style/Widget.Material3.Chip.Filter</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Rikka.Material3</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay" />
|
||||
|
||||
@@ -43,12 +43,6 @@
|
||||
android:title="@string/settings_advanced_clear_download_path_cache"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<Preference
|
||||
android:key="clear_memory_cache"
|
||||
android:summary="@string/settings_advanced_clear_memory_cache_summary"
|
||||
android:title="@string/settings_advanced_clear_memory_cache"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<rikka.preference.SimpleMenuPreference
|
||||
android:defaultValue="320"
|
||||
android:key="read_cache_size"
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '8.0.0-alpha02' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
|
||||
id 'com.google.devtools.ksp' version '1.7.10-1.0.6' apply false
|
||||
id 'com.android.application' version '8.0.0-alpha07' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
|
||||
id 'com.google.devtools.ksp' version '1.7.20-1.0.8' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
|
||||
+3
-1
@@ -11,7 +11,7 @@
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -XX:+UseParallelGC
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
@@ -22,6 +22,8 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableAppCompileTimeRClass=true
|
||||
android.nonTransitiveRClass=true
|
||||
android.experimental.enableNewResourceShrinker=true
|
||||
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
||||
|
||||
# signing environment variable
|
||||
signStoreFile=keystore/androidkey.jks
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-1
@@ -1,5 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-milestone-3-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,10 +80,10 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
@@ -143,12 +143,16 @@ fi
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
||||
Vendored
+1
@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
|
||||
Reference in new issue
Block a user