Compare commits

...
Author SHA1 Message Date
txyyh 8dbe24ab55 Release 1.8.5.0-preview
Releases / Build (push) Has been cancelled
2022-12-13 14:34:43 +00:00
tarsin 25f7b005cb Mark reader support wideColorGamut 2022-12-13 21:36:26 +08:00
tarsin 23f55ea208 Update gradle wrapper 2022-12-13 21:31:43 +08:00
tarsin 5f19fa0f1f Restore config 2022-12-13 21:18:11 +08:00
tarsin 58fbdcfcdd Sync tachiyomi reader translations 2022-12-13 21:02:54 +08:00
tarsin e33cca9fd9 Reformat code 2022-12-13 20:54:45 +08:00
tarsin b25e3d5454 Remove unused res 2022-12-13 20:51:01 +08:00
tarsin 583f272daa Remove unsupported setting 2022-12-13 20:46:22 +08:00
tarsin 2a83ad63be Inline DirectionalViewPager 2022-12-13 20:29:44 +08:00
tarsin 6fdc908ad1 Support save cursor 2022-12-13 18:48:48 +08:00
tarsin 5213caac49 Drop glgallery (#519)
* Neutralize GlGallery

* Enable dataBinding and viewBinding

* Add tachiyomi reader layout

* Cleanup Dead-code

* Rename .java to .kt

* Fix activity rename

* Nullability changes

* Add basic tachiyomi reader imageviews

* Fix size increase

* Let's cleanup later

* Add some tachiyomi webtoon widgets

* Import tachiyomi webtoon pager without chapter transition

* Remove rx dependency

* Rewrite GalleryProvider interface with flow

* groundwork for image load

* Refine webtoon pageholder

* Force us hw bitmap, refine recycle

* Strip tachiyomi decoder usage

* Keep our own SubsamplingScaleImageView, strip tachiyomi decoder to avoid apk bloated

* Import other PageViewers

* Add webtoon viewer config

* groundFix ReaderActivity

* Add tachiyomi Preferences

* Readpreference

* Fix fullscreen

* Move related class to tachiyomi package

* Fix pageload

* Add readerconfig

* Add ViewerConfig hook

* Add Reader bottomsheet settings

* Fix reader setting apply

* Refine

* Fix save share image

* Refine theme
2022-12-13 15:51:27 +08:00
FooIbar ca0518c747 Verify cookies through getProfile (#523)
Verify cookies through getProfile, Resolve? #283
2022-12-13 09:00:56 +08:00
FooIbar 298cd29537 Disable proxy when domain fronting is enabled (#522) 2022-12-13 08:26:53 +08:00
FooIbar 1a963a3239 Notify next items changed after items inserted (#520)
* Notify next items changed after items inserted, Resolve #201

* Code cleanup
2022-12-12 19:09:05 +08:00
FooIbar 683c8dc5c5 Fix onApplySearch getting called twice 2022-12-12 08:42:21 +08:00
FooIbar 2087970de6 Use MODE for favoriteSlot in HistoryInfo 2022-12-12 01:03:09 +08:00
FooIbar 5bd270fe6a Parse favoriteSlot for GalleryDetail 2022-12-12 01:03:09 +08:00
FooIbar 0992fcfcb0 Fix local favorites search 2022-12-11 20:11:30 +08:00
tarsin fc12395f7e Limit coil threads 2022-12-11 17:21:02 +08:00
tarsin 57b291a4e8 Checkout xz-utils to 5.2.9 2022-12-11 16:54:17 +08:00
tarsin 3adf391478 Checkout libarchive to 3.8.2 2022-12-11 16:49:57 +08:00
tarsin a859941845 Reformat Code 2022-12-11 12:26:22 +08:00
tarsin 8d1fc85638 Fix filter chip 2022-12-11 12:22:31 +08:00
tarsin 51ece1c472 Adjust block ips 2022-12-11 11:59:33 +08:00
tarsin 45f67ec37f Fix webtoon down sampling 2022-12-11 11:57:39 +08:00
FooIbar 85b559c989 Generate simpleLanguage for local favorites 2022-12-11 11:50:26 +08:00
FooIbar 1382050b5f Request UConfig on login 2022-12-11 11:50:26 +08:00
FooIbar fb970752fd Remove unused parameter 2022-12-11 11:50:26 +08:00
FooIbar 3d256a402f Auto close response to avoid resource leak 2022-12-11 11:50:26 +08:00
tarsin a677d95d34 Use mca 1.8.0beta01 2022-12-11 08:11:16 +08:00
FooIbar 452c0f2846 Use compose BOM instead 2022-12-10 07:36:24 +08:00
267 changed files with 15784 additions and 20071 deletions

No files matched your search

+17 -12
View File
@@ -31,8 +31,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 28
targetSdkVersion 33
versionCode 180026
versionName "1.8.4.0-alpha02"
versionCode 180027
versionName "1.8.5.0-preview"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -94,6 +94,8 @@ android {
buildFeatures {
buildConfig true
compose true
dataBinding true
viewBinding true
}
composeOptions {
@@ -123,15 +125,14 @@ dependencies {
implementation "androidx.collection:collection:$collection_version"
implementation "androidx.collection:collection-ktx:$collection_version"
def compose_version = "1.3.1"
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.foundation:foundation-layout:$compose_version"
implementation "androidx.compose.runtime:runtime:$compose_version"
implementation "androidx.compose.runtime:runtime-saveable:$compose_version"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material3:material3:1.0.1"
implementation platform("androidx.compose:compose-bom:2022.12.00")
implementation "androidx.compose.animation:animation"
implementation "androidx.compose.foundation:foundation"
implementation "androidx.compose.foundation:foundation-layout"
implementation "androidx.compose.material3:material3"
implementation "androidx.compose.runtime:runtime"
implementation "androidx.compose.runtime:runtime-saveable"
implementation "androidx.compose.ui:ui"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
@@ -182,7 +183,7 @@ dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.8.0-SNAPSHOT'
implementation 'com.google.android.material:material:1.8.0-beta01'
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
implementation 'com.squareup.okhttp3:okhttp'
@@ -195,6 +196,10 @@ dependencies {
implementation 'io.coil-kt:coil:2.2.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'org.jsoup:jsoup:1.15.3'
// Tachiyomi Reader
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'dev.chrisbanes.insetter:insetter:0.6.1'
}
configurations.all {
+2
View File
@@ -14,6 +14,8 @@
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
-keep class androidx.viewpager.widget.ViewPager$LayoutParams { int position; }
-renamesourcefileattribute
-repackageclasses
-allowaccessmodification
+2 -1
View File
@@ -102,9 +102,10 @@
</activity>
<activity
android:name="com.hippo.ehviewer.ui.GalleryActivity"
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:exported="true"
android:colorMode="wideColorGamut"
android:launchMode="singleTask">
<intent-filter>
+1 -1
View File
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
add_subdirectory(libarchive)
# Build and link our app's native lib
add_library(${PROJECT_NAME} SHARED archive.c image.c fdutils.c)
add_library(${PROJECT_NAME} SHARED archive.c fdutils.c)
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
target_link_libraries(${PROJECT_NAME} archive_static log jnigraphics GLESv2 -Wl,--gc-sections)
-143
View File
@@ -1,143 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* EhViewer. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <android/bitmap.h>
#include <android/data_space.h>
#include <android/log.h>
#include <GLES2/gl2.h>
#include <jni.h>
#define TAG "ImageDecoder_wrapper"
#include "ehviewer.h"
#define IMAGE_TILE_MAX_SIZE (512 * 512)
static char tile_buffer[IMAGE_TILE_MAX_SIZE * 4];
bool copy_pixels(const void *src, int src_w, int src_h, int src_x, int src_y,
void *dst, int dst_w, int dst_h, int dst_x, int dst_y,
int width, int height) {
int left;
int line;
size_t line_stride;
int src_stride;
int src_pos;
int dst_pos;
size_t dst_blank_length;
// Sanitize
if (src_x < 0) {
width -= src_x;
dst_x -= src_x;
src_x = 0;
}
if (dst_x < 0) {
width -= dst_x;
src_x -= dst_x;
dst_x = 0;
}
if (width <= 0) {
return false;
}
if (src_y < 0) {
height -= src_y;
dst_y -= src_y;
src_y = 0;
}
if (dst_y < 0) {
height -= dst_y;
src_y -= dst_y;
dst_y = 0;
}
if (height <= 0) {
return false;
}
left = src_x + width - src_w;
if (left > 0) {
width -= left;
}
left = dst_x + width - dst_w;
if (left > 0) {
width -= left;
}
if (width <= 0) {
return false;
}
left = src_y + height - src_h;
if (left > 0) {
height -= left;
}
left = dst_y + height - dst_h;
if (left > 0) {
height -= left;
}
if (height <= 0) {
return false;
}
// Init
line_stride = (size_t) (width * 4);
src_stride = src_w * 4;
src_pos = src_y * src_stride + src_x * 4;
dst_pos = 0;
dst_blank_length = (size_t) (dst_y * dst_w + dst_x) * 4;
// First line
dst_pos += (int) dst_blank_length;
memcpy(dst + dst_pos, src + src_pos, line_stride);
dst_pos += (int) line_stride;
src_pos += src_stride;
// Other lines
dst_blank_length = (size_t) ((dst_w - width) * 4);
for (line = 1; line < height; line++) {
dst_pos += (int) dst_blank_length;
memcpy(dst + dst_pos, src + src_pos, line_stride);
dst_pos += (int) line_stride;
src_pos += src_stride;
}
return true;
}
JNIEXPORT void JNICALL
Java_com_hippo_image_Image_nativeTexImage(JNIEnv *env, jclass clazz, jobject bitmap, jboolean init,
jint offset_x, jint offset_y, jint width, jint height) {
if (width * height > IMAGE_TILE_MAX_SIZE)
return;
AndroidBitmapInfo info;
void *pixels = NULL;
AndroidBitmap_lockPixels(env, bitmap, &pixels);
AndroidBitmap_getInfo(env, bitmap, &info);
copy_pixels(pixels, info.width, info.height, offset_x, offset_y, tile_buffer, width, height, 0, 0, width, height);
AndroidBitmap_unlockPixels(env, bitmap);
if (init) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
tile_buffer);
} else {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
tile_buffer);
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,160 @@
package com.davemorrissey.labs.subscaleview
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Rect
import android.net.Uri
import com.davemorrissey.labs.subscaleview.provider.AssetInputProvider
import com.davemorrissey.labs.subscaleview.provider.InputProvider
import com.davemorrissey.labs.subscaleview.provider.OpenStreamProvider
import com.davemorrissey.labs.subscaleview.provider.ResourceInputProvider
import com.davemorrissey.labs.subscaleview.provider.UriInputProvider
import java.io.InputStream
/**
* Helper class used to set the source and additional attributes from a variety of sources. Supports
* use of a bitmap, asset, resource, external file or any other URI.
*
*
* When you are using a preview image, you must set the dimensions of the full size image on the
* ImageSource object for the full size image using the [.dimensions] method.
*/
class ImageSource {
val bitmap: Bitmap?
val provider: InputProvider?
private var sWidth = 0
private var sHeight = 0
var sRegion = Rect(0, 0, 0, 0)
private set
var isCached = false
private set
private constructor(bitmap: Bitmap, cached: Boolean) {
this.bitmap = bitmap
provider = null
sWidth = bitmap.width
sHeight = bitmap.height
isCached = cached
}
private constructor(provider: InputProvider) {
bitmap = null
this.provider = provider
}
/**
* Use a region of the source image. Region must be set independently for the full size image and the preview if
* you are using one.
*
* @param sRegion the region of the source image to be displayed.
* @return this instance for chaining.
*/
fun region(sRegion: Rect): ImageSource {
this.sRegion = sRegion
setInvariants()
return this
}
/**
* Declare the dimensions of the image. This is only required for a full size image, when you are specifying a URI
* and also a preview image. When displaying a bitmap object, or not using a preview, you do not need to declare
* the image dimensions. Note if the declared dimensions are found to be incorrect, the view will reset.
*
* @param sWidth width of the source image.
* @param sHeight height of the source image.
* @return this instance for chaining.
*/
fun dimensions(sWidth: Int, sHeight: Int): ImageSource {
if (bitmap == null) {
this.sWidth = sWidth
this.sHeight = sHeight
}
setInvariants()
return this
}
private fun setInvariants() {
if (!sRegion.isEmpty) {
sWidth = sRegion.width()
sHeight = sRegion.height()
}
}
companion object {
/**
* Create an instance from a resource. The correct resource for the device screen resolution will be used.
*
* @param resId resource ID.
* @return an [ImageSource] instance.
*/
@JvmStatic
fun resource(context: Context, resId: Int): ImageSource {
return ImageSource(ResourceInputProvider(context, resId))
}
/**
* Create an instance from an asset name.
*
* @param assetName asset name.
* @return an [ImageSource] instance.
*/
@JvmStatic
fun asset(context: Context, assetName: String): ImageSource {
return ImageSource(AssetInputProvider(context, assetName))
}
/**
* Create an instance from a URI.
*
* @param uri image URI.
* @return an [ImageSource] instance.
*/
fun uri(context: Context, uri: Uri): ImageSource {
return ImageSource(UriInputProvider(context, uri))
}
/**
* Create an instance from an input provider.
*
* @param provider input stream provider.
* @return an [ImageSource] instance.
*/
fun provider(provider: InputProvider): ImageSource {
return ImageSource(provider)
}
/**
* Create an instance from an input stream.
*
* @param stream open input stream.
* @return an [ImageSource] instance.
*/
fun inputStream(stream: InputStream): ImageSource {
return ImageSource(OpenStreamProvider(stream))
}
/**
* Provide a loaded bitmap for display.
*
* @param bitmap bitmap to be displayed.
* @return an [ImageSource] instance.
*/
fun bitmap(bitmap: Bitmap): ImageSource {
return ImageSource(bitmap, false)
}
/**
* Provide a loaded and cached bitmap for display. This bitmap will not be recycled when it is no
* longer needed. Use this method if you loaded the bitmap with an image loader such as Picasso
* or Volley.
*
* @param bitmap bitmap to be displayed.
* @return an [ImageSource] instance.
*/
fun cachedBitmap(bitmap: Bitmap): ImageSource {
return ImageSource(bitmap, true)
}
}
}
@@ -0,0 +1,15 @@
package com.davemorrissey.labs.subscaleview
import android.graphics.PointF
import java.io.Serializable
/**
* Wraps the scale, center and orientation of a displayed image for easy restoration on screen rotate.
*/
class ImageViewState(val scale: Float, center: PointF) : Serializable {
private val centerX: Float = center.x
private val centerY: Float = center.y
val center: PointF
get() = PointF(centerX, centerY)
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,53 @@
package com.davemorrissey.labs.subscaleview.decoder
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
import kotlin.Throws
import com.davemorrissey.labs.subscaleview.provider.InputProvider
import java.lang.Exception
/**
* Interface for image decoding classes.
*/
interface Decoder {
/**
* Initialise the decoder. When possible, perform initial setup work once in this method. The
* dimensions of the image must be returned.
*
* @param context Application context. A reference may be held, but must be cleared on recycle.
* @param provider Provider of the image.
* @return Dimensions of the image.
* @throws Exception if initialisation fails.
*/
@Throws(Exception::class)
fun init(context: Context, provider: InputProvider): Point
/**
* Decode a region of the image with the given sample size. This method is called off the UI
* thread so it can safely load the image on the current thread. It is called from
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
* implementations must be thread safe. Adding `synchronized` to the method signature
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
* called concurrently.
*
* @param sRect Source image rectangle to decode.
* @param sampleSize Sample size.
* @return The decoded region. It is safe to return null if decoding fails.
*/
fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap
/**
* Status check. Should return false before initialisation and after recycle.
*
* @return true if the decoder is ready to be used.
*/
val isReady: Boolean
/**
* This method will be called when the decoder is no longer required. It should clean up any
* resources still in use.
*/
fun recycle()
}
@@ -0,0 +1,64 @@
package com.davemorrissey.labs.subscaleview.decoder
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.Rect
import kotlin.Throws
import com.davemorrissey.labs.subscaleview.provider.InputProvider
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import java.lang.Exception
class ImageDecoder @JvmOverloads constructor(
private val cropBorders: Boolean,
bitmapConfig: Bitmap.Config? = null
) : Decoder {
init {
val globalBitmapConfig = SubsamplingScaleImageView.preferredBitmapConfig
}
/**
* Initialise the decoder. When possible, perform initial setup work once in this method. The
* dimensions of the image must be returned.
*
* @param context Application context. A reference may be held, but must be cleared on recycle.
* @param provider Provider of the image.
* @return Dimensions of the image.
* @throws Exception if initialisation fails.
*/
@Throws(Exception::class)
override fun init(context: Context, provider: InputProvider): Point {
TODO()
}
/**
* Decode a region of the image with the given sample size. This method is called off the UI
* thread so it can safely load the image on the current thread. It is called from
* [android.os.AsyncTask]s running in an executor that may have multiple threads, so
* implementations must be thread safe. Adding `synchronized` to the method signature
* is the simplest way to achieve this, but bear in mind the [.recycle] method can be
* called concurrently.
*
* @param sRect Source image rectangle to decode.
* @param sampleSize Sample size.
* @return The decoded region. It is safe to return null if decoding fails.
*/
override fun decodeRegion(sRect: Rect, sampleSize: Int): Bitmap {
TODO()
}
/**
* Status check. Should return false before initialisation and after recycle.
*
* @return true if the decoder is ready to be used.
*/
override val isReady: Boolean = TODO()
/**
* This method will be called when the decoder is no longer required. It should clean up any
* resources still in use.
*/
override fun recycle() {
TODO()
}
}
@@ -0,0 +1,11 @@
package com.davemorrissey.labs.subscaleview.provider
import android.content.Context
import java.io.IOException
class AssetInputProvider(context: Context, val assetName: String) : InputProvider {
private val assets = context.assets
@Throws(IOException::class)
override fun openStream() = assets.open(assetName)
}
@@ -0,0 +1,10 @@
package com.davemorrissey.labs.subscaleview.provider
import java.io.IOException
import java.io.InputStream
import kotlin.Throws
interface InputProvider {
@Throws(IOException::class)
fun openStream(): InputStream?
}
@@ -0,0 +1,16 @@
package com.davemorrissey.labs.subscaleview.provider
import java.io.IOException
import java.io.InputStream
import kotlin.Throws
/**
* Provider from an already open input stream.
*
*
* Please keep in mind this provider is not reusable.
*/
class OpenStreamProvider(private val stream: InputStream) : InputProvider {
@Throws(IOException::class)
override fun openStream() = stream
}
@@ -0,0 +1,11 @@
package com.davemorrissey.labs.subscaleview.provider
import android.content.Context
import java.io.IOException
class ResourceInputProvider(context: Context, val resource: Int) : InputProvider {
private val resources = context.resources
@Throws(IOException::class)
override fun openStream() = resources.openRawResource(resource)
}
@@ -0,0 +1,13 @@
package com.davemorrissey.labs.subscaleview.provider
import android.content.Context
import android.net.Uri
import java.io.IOException
import kotlin.Throws
class UriInputProvider(context: Context, val uri: Uri) : InputProvider {
private val resolver = context.contentResolver
@Throws(IOException::class)
override fun openStream() = resolver.openInputStream(uri)
}
@@ -250,21 +250,7 @@ public final class DiskLruCache implements Closeable {
private static String inputStreamToString(InputStream in) throws IOException {
return Util.readFully(new InputStreamReader(in, Util.UTF_8));
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
}
private void readJournal() throws IOException {
StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII);
@@ -343,7 +329,21 @@ public final class DiskLruCache implements Closeable {
} else {
throw new IOException("unexpected journal line: " + line);
}
}
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
/**
* Computes the initial size and collects garbage as a part of opening the
@@ -18,6 +18,7 @@ package com.hippo.ehviewer
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.content.ComponentCallbacks2
import android.os.Build
import android.text.Html
@@ -26,12 +27,13 @@ import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.LruCache
import androidx.core.content.getSystemService
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import coil.Coil
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.hippo.Native
import com.hippo.app.BaseDialogBuilder
import com.hippo.beerbelly.SimpleDiskCache
@@ -52,20 +54,26 @@ import com.hippo.ehviewer.ui.EhActivity
import com.hippo.scene.SceneApplication
import com.hippo.util.BitmapUtils
import com.hippo.util.ExceptionUtils
import com.hippo.util.IoThreadPoolExecutor
import com.hippo.util.ReadableTime
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IntIdGenerator
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import java.io.File
import java.net.Proxy
import java.security.KeyStore
import java.util.Arrays
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
class EhApplication : SceneApplication() {
class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderFactory {
private val mIdGenerator = IntIdGenerator()
private val mGlobalStuffMap = HashMap<Int, Any>()
private val mActivityList = ArrayList<Activity>()
@@ -81,9 +89,10 @@ class EhApplication : SceneApplication() {
mActivityList.forEach { it.recreate() }
}
@OptIn(DelicateCoroutinesApi::class)
@SuppressLint("StaticFieldLeak")
override fun onCreate() {
ProcessLifecycleOwner.get().lifecycle.addObserver(EhlifecycleObserver())
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
application = this
val handler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
@@ -95,7 +104,7 @@ class EhApplication : SceneApplication() {
}
handler?.uncaughtException(t, e)
}
super.onCreate()
super<SceneApplication>.onCreate()
Native.initialize()
GetText.initialize(this)
Settings.initialize(this)
@@ -105,40 +114,43 @@ class EhApplication : SceneApplication() {
EhDB.initialize(this)
EhEngine.initialize()
BitmapUtils.initialize(this)
Coil.setImageLoader(ImageLoader.Builder(this).okHttpClient(okHttpClient).build())
// Locales can be managed by system automatically above Snow Cone v2
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Settings.getLocale()))
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
IoThreadPoolExecutor.getInstance().execute {
try {
val downloadLocation = Settings.getDownloadLocation()
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation)
} else {
CommonOperations.ensureNoMediaFile(downloadLocation)
GlobalScope.launch {
withContext(Dispatchers.IO) {
try {
val downloadLocation = Settings.getDownloadLocation()
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation)
} else {
CommonOperations.ensureNoMediaFile(downloadLocation)
}
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
// Clear temp files
try {
clearTempDir()
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
// Clear temp files
try {
clearTempDir()
} catch (t: Throwable) {
t.printStackTrace()
ExceptionUtils.throwIfFatal(t)
}
}
}
GlobalScope.launch {
theDawnOfNewDay()
}
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0))
initialized = true
theDawnOfNewDay()
}
private fun theDawnOfNewDay() {
private suspend fun theDawnOfNewDay() {
if (!Settings.getRequestNews()) {
return
}
@@ -150,17 +162,18 @@ class EhApplication : SceneApplication() {
EhCookieStore.KEY_IPD_PASS_HASH
)
) {
IoThreadPoolExecutor.getInstance().execute {
withContext(Dispatchers.IO) {
val referer = EhUrl.REFERER_E
val request = EhRequestBuilder(EhUrl.HOST_E + "news.php", referer).build()
val call = okHttpClient.newCall(request)
try {
val response = call.execute()
val responseBody = response.body
val body = responseBody.string()
val html = EventPaneParser.parse(body)
if (html != null) {
showEventPane(html)
call.execute().use { response ->
val responseBody = response.body
val body = responseBody.string()
val html = EventPaneParser.parse(body)
if (html != null) {
showEventPane(html)
}
}
} catch (e: Throwable) {
e.printStackTrace()
@@ -171,7 +184,7 @@ class EhApplication : SceneApplication() {
fun showEventPane(html: String) {
if (Settings.getHideHvEvents() && html.contains("You have encountered a monster!")) {
return;
return
}
val activity = topActivity
activity?.runOnUiThread {
@@ -242,12 +255,22 @@ class EhApplication : SceneApplication() {
mActivityList.remove(activity)
}
internal class EhlifecycleObserver : DefaultLifecycleObserver {
override fun onPause(owner: LifecycleOwner) {
if (!locked)
locked_last_leave_time = System.currentTimeMillis() / 1000
locked = true
}
override fun onPause(owner: LifecycleOwner) {
if (!locked)
locked_last_leave_time = System.currentTimeMillis() / 1000
locked = true
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this).apply {
okHttpClient(okHttpClient)
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
// Coil spawns a new thread for every image load by default
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8))
decoderDispatcher(Dispatchers.IO.limitedParallelism(2))
transformationDispatcher(Dispatchers.IO.limitedParallelism(2))
}.build()
}
companion object {
@@ -263,7 +286,7 @@ class EhApplication : SceneApplication() {
val ehCookieStore by lazy { EhCookieStore(application) }
@JvmStatic
val ehClient by lazy { EhClient(application) }
val ehClient by lazy { EhClient() }
@JvmStatic
val ehProxySelector by lazy { EhProxySelector() }
@@ -296,6 +319,7 @@ class EhApplication : SceneApplication() {
}
builder.sslSocketFactory(EhSSLSocketFactory(), trustManager)
builder.proxy(Proxy.NO_PROXY)
}
builder.build()
}
+9 -17
View File
@@ -234,7 +234,7 @@ public class EhDB {
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
LocalFavoritesDao dao = db.localFavoritesDao();
List<LocalFavoriteInfo> list = dao.list();
List<LocalFavoriteInfo> list = dao.list("%" + query + "%");
return new ArrayList<>(list);
}
@@ -293,11 +293,6 @@ public class EhDB {
}
}
public static synchronized void updateQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = db.quickSearchDao();
dao.update(quickSearch);
}
public static synchronized void deleteQuickSearch(QuickSearch quickSearch) {
QuickSearchDao dao = db.quickSearchDao();
dao.delete(quickSearch);
@@ -327,25 +322,22 @@ public class EhDB {
dao.update(list);
}
public static synchronized List<HistoryInfo> getHistoryList() {
return db.historyDao().list();
}
public static synchronized PagingSource<Integer, HistoryInfo> getHistoryLazyList() {
return db.historyDao().listLazy();
}
public static synchronized void putHistoryInfo(GalleryInfo galleryInfo) {
HistoryDao dao = db.historyDao();
HistoryInfo info = dao.load(galleryInfo.gid);
if (null != info) {
// Update time
info.time = System.currentTimeMillis();
HistoryInfo info;
if (galleryInfo instanceof HistoryInfo) {
info = (HistoryInfo) galleryInfo;
} else {
info = new HistoryInfo(galleryInfo);
}
info.time = System.currentTimeMillis();
if (null != dao.load(info.gid)) {
dao.update(info);
} else {
// New history
info = new HistoryInfo(galleryInfo);
info.time = System.currentTimeMillis();
dao.insert(info);
}
}
@@ -27,19 +27,13 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.scene.GalleryListScene;
import com.hippo.glgallery.GalleryView;
import com.hippo.unifile.UniFile;
import com.hippo.util.ExceptionUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.NumberUtils;
import java.io.File;
import java.util.Locale;
import java.util.Set;
@@ -53,10 +47,7 @@ public class Settings {
public static final String KEY_ACCOUNT = "account";
public static final String KEY_BLACK_DARK_THEME = "black_dark_theme";
public static final int THEME_LIGHT = 1;
public static final int THEME_SYSTEM = -1;
public static final int THEME_BLACK = 2;
public static final String KEY_APPLY_NAV_BAR_THEME_COLOR = "apply_nav_bar_theme_color";
public static final String KEY_GALLERY_SITE = "gallery_site";
public static final String KEY_LIST_MODE = "list_mode";
public static final String KEY_DETAIL_SIZE = "detail_size";
@@ -67,8 +58,6 @@ public class Settings {
public static final int DEFAULT_DEFAULT_CATEGORIES = EhConfig.ALL_CATEGORY;
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
/********************
****** Privacy and Security
********************/
@@ -87,45 +76,36 @@ public class Settings {
public static final String KEY_IMAGE_RESOLUTION = "image_size";
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
public static final String KEY_ENABLE_ANALYTICS = "enable_analytics";
/********************
****** Advanced
********************/
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
public static final String KEY_SECURITY = "require_unlock";
public static final String KEY_SECURITY_DELAY = "require_unlock_delay";
public static final String DEFAULT_SECURITY = "";
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
public static final int DEFAULT_READ_CACHE_SIZE = 320;
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts_2";
public static final String KEY_DOMAIN_FRONTING = "domain_fronting";
public static final String KEY_APP_LANGUAGE = "app_language";
public static final String KEY_LIST_THUMB_SIZE = "list_tile_size";
private static final String KEY_HIDE_HV_EVENTS = "hide_hv_events";
private static final boolean DEFAULT_HIDE_HV_EVENTS = true;
private static final String KEY_SHOW_COMMENTS = "show_gallery_comments";
private static final boolean DEFAULT_SHOW_COMMENTS = true;
private static final String KEY_PREVIEW_NUM = "preview_num";
private static final int DEFAULT_PREVIEW_NUM = 60;
private static final String TAG = Settings.class.getSimpleName();
private static final String KEY_VERSION_CODE = "version_code";
private static final int DEFAULT_VERSION_CODE = 0;
private static final String KEY_DISPLAY_NAME = "display_name";
private static final String DEFAULT_DISPLAY_NAME = null;
private static final int DEFAULT_LIST_THUMB_SIZE = 40;
private static final String KEY_AVATAR = "avatar";
private static final String DEFAULT_AVATAR = null;
private static final String KEY_SHOW_WARNING = "show_warning";
private static final boolean DEFAULT_SHOW_WARNING = true;
private static final String KEY_REMOVE_IMAGE_FILES = "include_pic";
private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true;
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
private static final boolean DEFAULT_NEED_SIGN_IN = true;
private static final String KEY_SELECT_SITE = "select_site";
private static final boolean DEFAULT_SELECT_SITE = true;
private static final String KEY_QUICK_SEARCH_TIP = "quick_search_tip";
private static final boolean DEFAULT_QUICK_SEARCH_TIP = true;
private static final int DEFAULT_THEME = THEME_SYSTEM;
private static final boolean DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
private static final int DEFAULT_GALLERY_SITE = 1;
private static final String KEY_LAUNCH_PAGE = "launch_page";
private static final int DEFAULT_LAUNCH_PAGE = 0;
@@ -144,21 +124,14 @@ public class Settings {
private static final boolean DEFAULT_METERED_NETWORK_WARNING = false;
private static final String KEY_APP_LINK_VERIFY_TIP = "app_link_verify_tip";
private static final boolean DEFAULT_APP_LINK_VERIFY_TIP = false;
private static final String KEY_NIGHT_MODE = "night_mode";
private static final String DEFAULT_NIGHT_MODE = "-1";
private static final String KEY_E_INK_MODE = "e_ink_mode_2";
private static final boolean DEFAULT_E_INK_MODE = false;
/********************
****** Read
********************/
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
private static final int DEFAULT_SCREEN_ROTATION = 0;
private static final String KEY_READING_DIRECTION = "reading_direction";
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
private static final String KEY_PAGE_SCALING = "page_scaling";
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
private static final String KEY_START_POSITION = "start_position";
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
@@ -235,21 +208,6 @@ public class Settings {
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
private static final String KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
private static final int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
/********************
****** Analytics
********************/
private static final String KEY_ASK_ANALYTICS = "ask_analytics";
private static final boolean DEFAULT_ASK_ANALYTICS = true;
private static final boolean DEFAULT_ENABLE_ANALYTICS = false;
private static final String KEY_USER_ID = "user_id";
private static final String FILENAME_USER_ID = ".user_id";
private static final int LENGTH_USER_ID = 32;
/********************
****** Update
********************/
private static final String KEY_BETA_UPDATE_CHANNEL = "beta_update_channel";
private static final String KEY_SKIP_UPDATE_VERSION = "skip_update_version";
private static final int DEFAULT_SKIP_UPDATE_VERSION = 0;
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = true;
private static final String KEY_SAVE_CRASH_LOG = "save_crash_log";
private static final boolean DEFAULT_SAVE_CRASH_LOG = true;
@@ -262,17 +220,6 @@ public class Settings {
private static final String DEFAULT_PROXY_IP = null;
private static final String KEY_PROXY_PORT = "proxy_port";
private static final int DEFAULT_PROXY_PORT = -1;
/********************
****** Guide
********************/
private static final String KEY_GUIDE_QUICK_SEARCH = "guide_quick_search";
private static final boolean DEFAULT_GUIDE_QUICK_SEARCH = true;
private static final String KEY_GUIDE_COLLECTIONS = "guide_collections";
private static final boolean DEFAULT_GUIDE_COLLECTIONS = true;
private static final String KEY_GUIDE_DOWNLOAD_THUMB = "guide_download_thumb";
private static final boolean DEFAULT_GUIDE_DOWNLOAD_THUMB = true;
private static final String KEY_GUIDE_DOWNLOAD_LABELS = "guide_download_labels";
private static final boolean DEFAULT_GUIDE_DOWNLOAD_LABELS = true;
private static final String KEY_GUIDE_GALLERY = "guide_gallery";
private static final boolean DEFAULT_GUIDE_GALLERY = true;
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
@@ -283,19 +230,19 @@ public class Settings {
private static final boolean DEFAULT_REQUEST_NEWS = true;
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
public static boolean LIST_THUMB_SIZE_INITED = false;
public static SharedPreferences sSettingsPre;
private static int LIST_THUMB_SIZE = 40;
private static Context sContext;
private static SharedPreferences sSettingsPre;
private static EhConfig sEhConfig;
public static void initialize(Context context) {
sContext = context.getApplicationContext();
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
sEhConfig = loadEhConfig();
fixDefaultValue(context);
fixDefaultValue();
}
private static void fixDefaultValue(Context context) {
private static void fixDefaultValue() {
if ("CN".equals(Locale.getDefault().getCountry())) {
// Enable domain fronting if the country is CN
if (!sSettingsPre.contains(KEY_BUILT_IN_HOSTS)) {
@@ -313,7 +260,6 @@ public class Settings {
}
public static Locale getLocale() {
Locale locale = null;
String language = Settings.getAppLanguage();
if (language != null && !language.equals("system")) {
return Locale.forLanguageTag(language);
@@ -358,32 +304,6 @@ public class Settings {
sSettingsPre.edit().putInt(key, value).apply();
}
public static long getLong(String key, long defValue) {
try {
return sSettingsPre.getLong(key, defValue);
} catch (ClassCastException e) {
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
return defValue;
}
}
public static void putLong(String key, long value) {
sSettingsPre.edit().putLong(key, value).apply();
}
public static float getFloat(String key, float defValue) {
try {
return sSettingsPre.getFloat(key, defValue);
} catch (ClassCastException e) {
Log.d(TAG, "Get ClassCastException when get " + key + " value", e);
return defValue;
}
}
public static void putFloat(String key, float value) {
sSettingsPre.edit().putFloat(key, value).apply();
}
public static String getString(String key, String defValue) {
try {
return sSettingsPre.getString(key, defValue);
@@ -434,22 +354,10 @@ public class Settings {
putString(KEY_DISPLAY_NAME, value);
}
public static String getAvatar() {
return getString(KEY_AVATAR, DEFAULT_AVATAR);
}
public static void putAvatar(String value) {
putString(KEY_AVATAR, value);
}
public static boolean getShowWarning() {
return getBoolean(KEY_SHOW_WARNING, DEFAULT_SHOW_WARNING);
}
public static void putShowWarning(boolean value) {
putBoolean(KEY_SHOW_WARNING, value);
}
public static boolean getRemoveImageFiles() {
return getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES);
}
@@ -482,14 +390,6 @@ public class Settings {
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
}
public static void putTheme(int theme) {
putIntToStr(KEY_THEME, theme);
}
public static boolean getApplyNavBarThemeColor() {
return getBoolean(KEY_APPLY_NAV_BAR_THEME_COLOR, DEFAULT_APPLY_NAV_BAR_THEME_COLOR);
}
public static int getGallerySite() {
return getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE);
}
@@ -500,17 +400,13 @@ public class Settings {
public static String getLaunchPageGalleryListSceneAction() {
int value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE);
switch (value) {
default:
case 0:
return GalleryListScene.ACTION_HOMEPAGE;
case 1:
return GalleryListScene.ACTION_SUBSCRIPTION;
case 2:
return GalleryListScene.ACTION_WHATS_HOT;
case 3:
return GalleryListScene.ACTION_TOP_LIST;
}
return switch (value) {
case 0 -> GalleryListScene.ACTION_HOMEPAGE;
case 1 -> GalleryListScene.ACTION_SUBSCRIPTION;
case 2 -> GalleryListScene.ACTION_WHATS_HOT;
case 3 -> GalleryListScene.ACTION_TOP_LIST;
default -> throw new IllegalStateException("Unexpected value: " + value);
};
}
public static int getListMode() {
@@ -523,13 +419,11 @@ public class Settings {
@DimenRes
public static int getDetailSizeResId() {
switch (getDetailSize()) {
default:
case 0:
return R.dimen.gallery_list_column_width_long;
case 1:
return R.dimen.gallery_list_column_width_short;
}
return switch (getDetailSize()) {
case 0 -> R.dimen.gallery_list_column_width_long;
case 1 -> R.dimen.gallery_list_column_width_short;
default -> throw new IllegalStateException("Unexpected value: " + getDetailSize());
};
}
public static int getThumbSize() {
@@ -581,32 +475,14 @@ public class Settings {
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
}
public static void putDefaultCategories(int value) {
sEhConfig.defaultCategories = value;
sEhConfig.setDirty();
putInt(KEY_DEFAULT_CATEGORIES, value);
}
public static int getExcludedTagNamespaces() {
return getInt(KEY_EXCLUDED_TAG_NAMESPACES, DEFAULT_EXCLUDED_TAG_NAMESPACES);
}
public static void putExcludedTagNamespaces(int value) {
sEhConfig.excludedNamespaces = value;
sEhConfig.setDirty();
putInt(KEY_EXCLUDED_TAG_NAMESPACES, value);
}
public static String getExcludedLanguages() {
return getString(KEY_EXCLUDED_LANGUAGES, DEFAULT_EXCLUDED_LANGUAGES);
}
public static void putExcludedLanguages(String value) {
sEhConfig.excludedLanguages = value;
sEhConfig.setDirty();
putString(KEY_EXCLUDED_LANGUAGES, value);
}
public static boolean getMeteredNetworkWarning() {
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
}
@@ -627,29 +503,14 @@ public class Settings {
putIntToStr(KEY_SCREEN_ROTATION, value);
}
@GalleryView.LayoutMode
public static int getReadingDirection() {
return GalleryView.sanitizeLayoutMode(getIntFromStr(KEY_READING_DIRECTION, DEFAULT_READING_DIRECTION));
}
public static void putReadingDirection(int value) {
putIntToStr(KEY_READING_DIRECTION, value);
}
@GalleryView.ScaleMode
public static int getPageScaling() {
return GalleryView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
}
public static void putPageScaling(int value) {
putIntToStr(KEY_PAGE_SCALING, value);
}
@GalleryView.StartPosition
public static int getStartPosition() {
return GalleryView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
}
public static void putStartPosition(int value) {
putIntToStr(KEY_START_POSITION, value);
}
@@ -746,25 +607,16 @@ public class Settings {
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
}
public static void putEnabledSecurity(boolean value) {
putBoolean(KEY_READING_FULLSCREEN, value);
}
@Nullable
public static UniFile getDownloadLocation() {
UniFile dir = null;
try {
Uri.Builder builder = new Uri.Builder();
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
dir = UniFile.fromUri(sContext, builder.build());
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
UniFile dir;
Uri.Builder builder = new Uri.Builder();
builder.scheme(getString(KEY_DOWNLOAD_SAVE_SCHEME, null));
builder.encodedAuthority(getString(KEY_DOWNLOAD_SAVE_AUTHORITY, null));
builder.encodedPath(getString(KEY_DOWNLOAD_SAVE_PATH, null));
builder.encodedQuery(getString(KEY_DOWNLOAD_SAVE_QUERY, null));
builder.encodedFragment(getString(KEY_DOWNLOAD_SAVE_FRAGMENT, null));
dir = UniFile.fromUri(sContext, builder.build());
return dir != null ? dir : UniFile.fromFile(AppConfig.getDefaultDownloadDir());
}
@@ -815,26 +667,14 @@ public class Settings {
return getIntFromStr(KEY_MULTI_THREAD_DOWNLOAD, DEFAULT_MULTI_THREAD_DOWNLOAD);
}
public static void putMultiThreadDownload(int value) {
putIntToStr(KEY_MULTI_THREAD_DOWNLOAD, value);
}
public static int getDownloadDelay() {
return getIntFromStr(KEY_DOWNLOAD_DELAY, DEFAULT_DOWNLOAD_DELAY);
}
public static void putDownloadDelay(int value) {
putIntToStr(KEY_DOWNLOAD_DELAY, value);
}
public static int getPreloadImage() {
return getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE);
}
public static void putPreloadImage(int value) {
putIntToStr(KEY_PRELOAD_IMAGE, value);
}
public static String getImageResolution() {
return getString(KEY_IMAGE_RESOLUTION, DEFAULT_IMAGE_RESOLUTION);
}
@@ -849,10 +689,6 @@ public class Settings {
return getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE);
}
public static void putDownloadOriginImage(boolean value) {
putBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, value);
}
public static String[] getFavCat() {
String[] favCat = new String[10];
favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0);
@@ -947,100 +783,10 @@ public class Settings {
putInt(KEY_DEFAULT_FAV_SLOT, value);
}
public static boolean getAskAnalytics() {
return getBoolean(KEY_ASK_ANALYTICS, DEFAULT_ASK_ANALYTICS);
}
public static void putAskAnalytics(boolean value) {
putBoolean(KEY_ASK_ANALYTICS, value);
}
public static boolean getEnableAnalytics() {
return getBoolean(KEY_ENABLE_ANALYTICS, DEFAULT_ENABLE_ANALYTICS);
}
public static void putEnableAnalytics(boolean value) {
putBoolean(KEY_ENABLE_ANALYTICS, value);
}
public static String getUserID() {
boolean writeXml = false;
boolean writeFile = false;
String userID = getString(KEY_USER_ID, null);
File file = AppConfig.getFileInExternalAppDir(FILENAME_USER_ID);
if (!isValidUserID(userID)) {
writeXml = true;
// Get use ID from out sd card file
userID = FileUtils.read(file);
if (!isValidUserID(userID)) {
writeFile = true;
userID = generateUserID();
}
} else {
writeFile = true;
}
if (writeXml) {
putString(KEY_USER_ID, userID);
}
if (writeFile) {
FileUtils.write(file, userID);
}
return userID;
}
@NonNull
private static String generateUserID() {
int length = LENGTH_USER_ID;
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
if (MathUtils.random(0, ('9' - '0' + 1) + ('z' - 'a' + 1)) <= '9' - '0') {
sb.append((char) MathUtils.random('0', '9' + 1));
} else {
sb.append((char) MathUtils.random('a', 'z' + 1));
}
}
return sb.toString();
}
private static boolean isValidUserID(@Nullable String userID) {
if (null == userID || LENGTH_USER_ID != userID.length()) {
return false;
}
for (int i = 0; i < LENGTH_USER_ID; i++) {
char ch = userID.charAt(i);
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z')) {
return false;
}
}
return true;
}
public static void putBetaUpdateChannel(boolean value) {
putBoolean(KEY_BETA_UPDATE_CHANNEL, value);
}
public static int getSkipUpdateVersion() {
return getInt(KEY_SKIP_UPDATE_VERSION, DEFAULT_SKIP_UPDATE_VERSION);
}
public static void putSkipUpdateVersion(int value) {
putInt(KEY_SKIP_UPDATE_VERSION, value);
}
public static boolean getSaveParseErrorBody() {
return getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY);
}
public static void putSaveParseErrorBody(boolean value) {
putBoolean(KEY_SAVE_PARSE_ERROR_BODY, value);
}
public static boolean getSaveCrashLog() {
return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG);
}
@@ -1057,10 +803,6 @@ public class Settings {
return getInt(KEY_SECURITY_DELAY, 0);
}
public static boolean getEnableFingerprint() {
return getBoolean(KEY_ENABLE_FINGERPRINT, true);
}
public static int getReadCacheSize() {
return getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE);
}
@@ -1085,10 +827,6 @@ public class Settings {
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
}
public static void putAppLanguage(String value) {
putString(KEY_APP_LANGUAGE, value);
}
public static int getProxyType() {
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
}
@@ -1113,38 +851,6 @@ public class Settings {
putInt(KEY_PROXY_PORT, value);
}
public static boolean getGuideQuickSearch() {
return getBoolean(KEY_GUIDE_QUICK_SEARCH, DEFAULT_GUIDE_QUICK_SEARCH);
}
public static void putGuideQuickSearch(boolean value) {
putBoolean(KEY_GUIDE_QUICK_SEARCH, value);
}
public static boolean getGuideCollections() {
return getBoolean(KEY_GUIDE_COLLECTIONS, DEFAULT_GUIDE_COLLECTIONS);
}
public static void putGuideCollections(boolean value) {
putBoolean(KEY_GUIDE_COLLECTIONS, value);
}
public static boolean getGuideDownloadThumb() {
return getBoolean(KEY_GUIDE_DOWNLOAD_THUMB, DEFAULT_GUIDE_DOWNLOAD_THUMB);
}
public static void putGuideDownloadThumb(boolean value) {
putBoolean(KEY_GUIDE_DOWNLOAD_THUMB, value);
}
public static boolean getGuideDownloadLabels() {
return getBoolean(KEY_GUIDE_DOWNLOAD_LABELS, DEFAULT_GUIDE_DOWNLOAD_LABELS);
}
public static void puttGuideDownloadLabels(boolean value) {
putBoolean(KEY_GUIDE_DOWNLOAD_LABELS, value);
}
public static boolean getGuideGallery() {
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
}
@@ -29,11 +29,12 @@ import androidx.browser.customtabs.CustomTabsIntent;
import com.hippo.ehviewer.client.EhUrlOpener;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
import com.hippo.scene.StageActivity;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
public final class UrlOpener {
private UrlOpener() {
@@ -56,19 +57,19 @@ public final class UrlOpener {
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
if (result != null) {
if (result.gid == galleryDetail.gid) {
intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(GalleryActivity.KEY_PAGE, result.page);
intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(ReaderActivity.KEY_PAGE, result.page);
context.startActivity(intent);
return;
}
} else if (url.startsWith("#c")) {
try {
intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(GalleryActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
intent.putExtra(ReaderActivity.KEY_PAGE, Integer.parseInt(url.replace("#c", "")) - 1);
context.startActivity(intent);
return;
} catch (NumberFormatException e) {
@@ -16,7 +16,6 @@
package com.hippo.ehviewer.client;
import android.content.Context;
import android.os.AsyncTask;
import com.hippo.ehviewer.EhApplication;
@@ -55,18 +54,18 @@ public class EhClient {
public static final int METHOD_ARCHIVE_LIST = 17;
public static final int METHOD_DOWNLOAD_ARCHIVE = 18;
public static final int METHOD_VOTE_TAG = 19;
public static final int METHOD_GET_UCONFIG = 20;
private static final OkHttpClient mOkHttpClient = EhApplication.getOkHttpClient();
private final ThreadPoolExecutor mRequestThreadPool;
private final OkHttpClient mOkHttpClient;
public EhClient(Context context) {
public EhClient() {
mRequestThreadPool = IoThreadPoolExecutor.getInstance();
mOkHttpClient = EhApplication.getOkHttpClient();
}
@SuppressWarnings("unchecked")
public void execute(EhRequest request) {
if (!request.isCancelled()) {
Task task = new Task(request.getMethod(), request.getCallback(), request.getEhConfig());
Task task = new Task(request.getMethod(), request.getCallback());
task.executeOnExecutor(mRequestThreadPool, request.getArgs());
request.task = task;
} else {
@@ -83,18 +82,17 @@ public class EhClient {
void onCancel();
}
public class Task extends AsyncTask<Object, Void, Object> {
@SuppressWarnings("deprecation")
public static class Task extends AsyncTask<Object, Void, Object> {
private final int mMethod;
private final AtomicReference<Call> mCall = new AtomicReference<>();
private final AtomicBoolean mStop = new AtomicBoolean();
private Callback mCallback;
private EhConfig mEhConfig;
private Callback<Object> mCallback;
public Task(int method, Callback callback, EhConfig ehConfig) {
public Task(int method, Callback<Object> callback) {
mMethod = method;
mCallback = callback;
mEhConfig = ehConfig;
}
// Called in Job thread
@@ -107,10 +105,6 @@ public class EhClient {
}
}
public EhConfig getEhConfig() {
return mEhConfig;
}
public void stop() {
if (!mStop.get()) {
mStop.lazySet(true);
@@ -133,7 +127,6 @@ public class EhClient {
// Clear
mCallback = null;
mEhConfig = null;
mCall.lazySet(null);
}
}
@@ -141,53 +134,51 @@ public class EhClient {
@Override
protected Object doInBackground(Object... params) {
try {
switch (mMethod) {
case METHOD_SIGN_IN:
return EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
case METHOD_GET_GALLERY_LIST:
return EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_GALLERY_DETAIL:
return EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_PREVIEW_SET:
return EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_RATE_GALLERY:
return EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
case METHOD_GET_COMMENT_GALLERY:
return EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
case METHOD_GET_GALLERY_TOKEN:
return EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
case METHOD_GET_FAVORITES:
return EhEngine.getFavorites(this, mOkHttpClient, (String) params[0], (Boolean) params[1]);
case METHOD_ADD_FAVORITES:
return EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
case METHOD_ADD_FAVORITES_RANGE:
return EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
case METHOD_MODIFY_FAVORITES:
return EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2], (Boolean) params[3]);
case METHOD_GET_TORRENT_LIST:
return EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_GET_PROFILE:
return EhEngine.getProfile(this, mOkHttpClient);
case METHOD_VOTE_COMMENT:
return EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
case METHOD_IMAGE_SEARCH:
return EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
case METHOD_ARCHIVE_LIST:
return EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_DOWNLOAD_ARCHIVE:
return EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
case METHOD_VOTE_TAG:
return EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
default:
return new IllegalStateException("Can't detect method " + mMethod);
}
return switch (mMethod) {
case METHOD_SIGN_IN ->
EhEngine.signIn(this, mOkHttpClient, (String) params[0], (String) params[1]);
case METHOD_GET_GALLERY_LIST ->
EhEngine.getGalleryList(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_GALLERY_DETAIL ->
EhEngine.getGalleryDetail(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_PREVIEW_SET ->
EhEngine.getPreviewSet(this, mOkHttpClient, (String) params[0]);
case METHOD_GET_RATE_GALLERY ->
EhEngine.rateGallery(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Float) params[4]);
case METHOD_GET_COMMENT_GALLERY ->
EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1], (String) params[2]);
case METHOD_GET_GALLERY_TOKEN ->
EhEngine.getGalleryToken(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2]);
case METHOD_GET_FAVORITES ->
EhEngine.getFavorites(this, mOkHttpClient, (String) params[0]);
case METHOD_ADD_FAVORITES ->
EhEngine.addFavorites(this, mOkHttpClient, (Long) params[0], (String) params[1], (Integer) params[2], (String) params[3]);
case METHOD_ADD_FAVORITES_RANGE ->
EhEngine.addFavoritesRange(this, mOkHttpClient, (long[]) params[0], (String[]) params[1], (Integer) params[2]);
case METHOD_MODIFY_FAVORITES ->
EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (long[]) params[1], (Integer) params[2]);
case METHOD_GET_TORRENT_LIST ->
EhEngine.getTorrentList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_GET_PROFILE -> EhEngine.getProfile(this, mOkHttpClient);
case METHOD_VOTE_COMMENT ->
EhEngine.voteComment(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (Long) params[4], (Integer) params[5]);
case METHOD_IMAGE_SEARCH ->
EhEngine.imageSearch(this, mOkHttpClient, (File) params[0], (Boolean) params[1], (Boolean) params[2], (Boolean) params[3]);
case METHOD_ARCHIVE_LIST ->
EhEngine.getArchiveList(this, mOkHttpClient, (String) params[0], (Long) params[1], (String) params[2]);
case METHOD_DOWNLOAD_ARCHIVE ->
EhEngine.downloadArchive(this, mOkHttpClient, (Long) params[0], (String) params[1], (String) params[2], (String) params[3]);
case METHOD_VOTE_TAG ->
EhEngine.voteTag(this, mOkHttpClient, (Long) params[0], (String) params[1], (Long) params[2], (String) params[3], (String) params[4], (Integer) params[5]);
case METHOD_GET_UCONFIG -> EhEngine.getUConfig(this, mOkHttpClient);
default -> new IllegalStateException("Can't detect method " + mMethod);
};
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
return e;
}
}
@SuppressWarnings("unchecked")
@Override
protected void onPostExecute(Object result) {
if (mCallback != null) {
@@ -205,7 +196,6 @@ public class EhClient {
// Clear
mCallback = null;
mEhConfig = null;
mCall.lazySet(null);
}
}
@@ -42,7 +42,7 @@ class EhDns : Dns {
put(
builtInHosts,
"e-hentai.org",
Pair("104.20.134.21", true),
Pair("104.20.134.21", false),
Pair("104.20.135.21", false),
Pair("172.67.0.127", false),
)
@@ -185,8 +185,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -263,8 +262,7 @@ public class EhEngine {
Headers headers = null;
GalleryListParser.Result result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -326,8 +324,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -354,8 +351,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -387,8 +383,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -429,8 +424,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -466,8 +460,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -510,8 +503,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -524,7 +516,7 @@ public class EhEngine {
}
public static FavoritesParser.Result getFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String url, boolean callApi) throws Throwable {
String url) throws Throwable {
String referer = EhUrl.getReferer();
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
@@ -539,8 +531,7 @@ public class EhEngine {
Headers headers = null;
FavoritesParser.Result result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -592,8 +583,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -617,7 +607,7 @@ public class EhEngine {
}
public static FavoritesParser.Result modifyFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String url, long[] gidArray, int dstCat, boolean callApi) throws Throwable {
String url, long[] gidArray, int dstCat) throws Throwable {
String catStr;
if (dstCat == -1) {
catStr = "delete";
@@ -648,8 +638,7 @@ public class EhEngine {
Headers headers = null;
FavoritesParser.Result result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -681,8 +670,7 @@ public class EhEngine {
Headers headers = null;
Pair<String, String>[] result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -712,8 +700,7 @@ public class EhEngine {
Headers headers = null;
Pair<String, Pair<String, String>[]> result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -754,8 +741,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -788,8 +774,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -816,8 +801,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -829,6 +813,55 @@ public class EhEngine {
}
}
public static Void getUConfig(@Nullable EhClient.Task task,
OkHttpClient okHttpClient) throws Throwable {
String url = EhUrl.getUConfigUrl();
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, null).build();
Call call = okHttpClient.newCall(request);
// Put call
if (null != task) {
task.setCall(call);
}
String body = null;
Headers headers = null;
int code = -1;
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
request = response.request();
throwException(call, code, headers, body, null);
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
throwException(call, code, headers, body, e);
throw e;
}
// TODO Use a better way to handle 302
if (!request.url().toString().equals(url)) {
Log.d(TAG, "Redirected! Retry " + url);
request = new EhRequestBuilder(url, null).build();
call = okHttpClient.newCall(request);
if (null != task) task.setCall(call);
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
throwException(call, code, headers, body, null);
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
throwException(call, code, headers, body, e);
throw e;
}
}
return null;
}
public static VoteCommentParser.Result voteComment(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
long apiUid, String apiKey, long gid, String token, long commentId, int commentVote) throws Throwable {
final JSONObject json = new JSONObject();
@@ -857,8 +890,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -898,8 +930,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -962,8 +993,7 @@ public class EhEngine {
Headers headers = null;
GalleryListParser.Result result;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
Log.d(TAG, "" + response.request().url());
@@ -997,8 +1027,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -1039,8 +1068,7 @@ public class EhEngine {
String body = null;
Headers headers = null;
int code = -1;
try {
Response response = call.execute();
try (Response response = call.execute()) {
code = response.code();
headers = response.headers();
body = response.body().string();
@@ -16,7 +16,6 @@
package com.hippo.ehviewer.client;
import android.content.Context;
import android.graphics.Color;
import android.text.TextUtils;
@@ -49,10 +48,10 @@ public class EhUtils {
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
public static final Pattern PATTERN_TITLE_PREFIX = Pattern.compile(
"^(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*");
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*");
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
public static final Pattern PATTERN_TITLE_SUFFIX = Pattern.compile(
"(?:\\s+ch.[\\s\\d-]+)?(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*$",
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
Pattern.CASE_INSENSITIVE);
private static final int[] CATEGORY_VALUES = {
@@ -106,40 +105,29 @@ public class EhUtils {
}
public static int getCategoryColor(int category) {
switch (category) {
case EhConfig.DOUJINSHI:
return BG_COLOR_DOUJINSHI;
case EhConfig.MANGA:
return BG_COLOR_MANGA;
case EhConfig.ARTIST_CG:
return BG_COLOR_ARTIST_CG;
case EhConfig.GAME_CG:
return BG_COLOR_GAME_CG;
case EhConfig.WESTERN:
return BG_COLOR_WESTERN;
case EhConfig.NON_H:
return BG_COLOR_NON_H;
case EhConfig.IMAGE_SET:
return BG_COLOR_IMAGE_SET;
case EhConfig.COSPLAY:
return BG_COLOR_COSPLAY;
case EhConfig.ASIAN_PORN:
return BG_COLOR_ASIAN_PORN;
case EhConfig.MISC:
return BG_COLOR_MISC;
default:
return BG_COLOR_UNKNOWN;
}
return switch (category) {
case EhConfig.DOUJINSHI -> BG_COLOR_DOUJINSHI;
case EhConfig.MANGA -> BG_COLOR_MANGA;
case EhConfig.ARTIST_CG -> BG_COLOR_ARTIST_CG;
case EhConfig.GAME_CG -> BG_COLOR_GAME_CG;
case EhConfig.WESTERN -> BG_COLOR_WESTERN;
case EhConfig.NON_H -> BG_COLOR_NON_H;
case EhConfig.IMAGE_SET -> BG_COLOR_IMAGE_SET;
case EhConfig.COSPLAY -> BG_COLOR_COSPLAY;
case EhConfig.ASIAN_PORN -> BG_COLOR_ASIAN_PORN;
case EhConfig.MISC -> BG_COLOR_MISC;
default -> BG_COLOR_UNKNOWN;
};
}
public static void signOut(Context context) {
public static void signOut() {
EhApplication.getEhCookieStore().signOut();
Settings.putAvatar(null);
Settings.putDisplayName(null);
Settings.putNeedSignIn(true);
}
public static boolean needSignedIn(Context context) {
public static boolean needSignedIn() {
return Settings.getNeedSignIn() && !EhApplication.getEhCookieStore().hasSignedIn();
}
@@ -19,6 +19,7 @@ package com.hippo.ehviewer.client.parser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryCommentList;
@@ -67,6 +68,7 @@ public class GalleryDetailParser {
private static final Pattern PATTERN_NORMAL_PREVIEW = Pattern.compile("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"");
private static final Pattern PATTERN_LARGE_PREVIEW = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img alt=\"([\\d,]+)\".+?src=\"(.+?)\"");
private static final Pattern PATTERN_NEWER_DATE = Pattern.compile(", added (.+?)<br />");
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("/fav.png\\); background-position:0px -(\\d+)px");
private static final GalleryTagGroup[] EMPTY_GALLERY_TAG_GROUP_ARRAY = new GalleryTagGroup[0];
private static final GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
@@ -78,6 +80,23 @@ public class GalleryDetailParser {
private static final String PINING_STRING =
"<p>This gallery is pining for the fjords.</p>";
private static final String[] LANGUAGES = {
"English",
"Chinese",
"Spanish",
"Korean",
"Russian",
"French",
"Portuguese",
"Thai",
"German",
"Italian",
"Vietnamese",
"Polish",
"Hungarian",
"Dutch",
};
public static GalleryDetail parse(String body) throws EhException {
if (body.contains(OFFENSIVE_STRING)) {
throw new OffensiveException();
@@ -100,6 +119,15 @@ public class GalleryDetailParser {
galleryDetail.comments = parseComments(document);
galleryDetail.previewPages = parsePreviewPages(document, body);
galleryDetail.previewSet = parsePreviewSet(document, body);
// Generate simpleLanguage for local favorites
for (int i = 0; i < LANGUAGES.length; i++) {
if (galleryDetail.language.equals(LANGUAGES[i])) {
galleryDetail.simpleLanguage = GalleryInfo.S_LANGS[i];
break;
}
}
return galleryDetail;
}
@@ -191,14 +219,9 @@ public class GalleryDetailParser {
gd.size = "";
gd.pages = 0;
gd.favoriteCount = 0;
try {
Elements es = gdd.child(0).child(0).children();
for (int i = 0, n = es.size(); i < n; i++) {
parseDetailInfo(gd, es.get(i));
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
Elements es = gdd.child(0).child(0).children();
for (int i = 0, n = es.size(); i < n; i++) {
parseDetailInfo(gd, es.get(i));
}
// Rating count
@@ -230,15 +253,24 @@ public class GalleryDetailParser {
// isFavorited
Element gdf = gm.getElementById("gdf");
gd.isFavorited = null != gdf && !StringUtils.trim(gdf.text()).equals("Add to Favorites");
gd.isFavorited = false;
if (gdf != null) {
final String favoriteName = StringUtils.trim(gdf.text());
if (favoriteName.equals("Add to Favorites")) {
gd.favoriteName = null;
} else {
gd.isFavorited = true;
gd.favoriteName = StringUtils.trim(gdf.text());
matcher = PATTERN_FAVORITE_SLOT.matcher(body);
if (matcher.find()) {
gd.favoriteSlot = (NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19;
}
}
}
if (gd.favoriteSlot == -2 && EhDB.containLocalFavorites(gd.gid)) {
gd.favoriteSlot = -1;
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
throw new ParseException("Can't parse gallery detail", body);
@@ -20,10 +20,11 @@ public class HistoryInfo extends GalleryInfo {
return new HistoryInfo[size];
}
};
@ColumnInfo(name = "MODE")
public int mode;
@ColumnInfo(name = "TIME")
public long time;
// Use MODE for favoriteSlot
@ColumnInfo(name = "MODE")
private int mode;
protected HistoryInfo(Parcel in) {
super(in);
@@ -43,10 +44,11 @@ public class HistoryInfo extends GalleryInfo {
this.rating = galleryInfo.rating;
this.simpleTags = galleryInfo.simpleTags;
this.simpleLanguage = galleryInfo.simpleLanguage;
this.favoriteSlot = galleryInfo.favoriteSlot;
}
public HistoryInfo(int mode, long time) {
this.mode = mode;
this.favoriteSlot = mode - 2;
this.time = time;
}
@@ -131,11 +133,11 @@ public class HistoryInfo extends GalleryInfo {
}
public int getMode() {
return mode;
return favoriteSlot + 2;
}
public void setMode(int mode) {
this.mode = mode;
this.favoriteSlot = mode - 2;
}
public long getTime() {
@@ -44,6 +44,7 @@ public class LocalFavoriteInfo extends GalleryInfo {
}
public LocalFavoriteInfo() {
this.favoriteSlot = -1;
}
public long getGid() {
@@ -46,7 +46,9 @@ import java.util.Set;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
public class ArchiveGalleryProvider extends GalleryProvider2 {
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
public class ArchivePageLoader extends PageLoader2 {
private static final AtomicInteger sIdGenerator = new AtomicInteger();
public static Handler showPasswd;
public static String passwd;
@@ -61,10 +63,10 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
new Thread(new DecodeTask())
};
private Thread archiveThread;
private volatile int size = STATE_WAIT;
private volatile int size = PageLoader.STATE_WAIT;
private String error;
public ArchiveGalleryProvider(Context context, Uri uri) {
public ArchivePageLoader(Context context, Uri uri) {
UriArchiveAccessor archiveAccessor1;
try {
archiveAccessor1 = new UriArchiveAccessor(context, uri);
@@ -77,8 +79,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
@Override
public void start() {
super.start();
int id = sIdGenerator.incrementAndGet();
archiveThread = new PriorityThread(
@@ -91,7 +91,6 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
@Override
public void stop() {
super.stop();
if (archiveThread != null) {
archiveThread.interrupt();
archiveThread = null;
@@ -217,7 +216,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
size = 0;
}
if (size <= 0) {
size = STATE_ERROR;
size = PageLoader.STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
notifyDataChanged();
return;
@@ -289,7 +288,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
// Check index valid
if (index < 0 || index >= size) {
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
notifyPageFailed(index);
continue;
}
@@ -347,7 +346,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
if (image != null) {
notifyPageSucceed(index, image);
} else {
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
notifyPageFailed(index);
}
}
}
@@ -24,7 +24,6 @@ import androidx.annotation.Nullable;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.image.Image;
import com.hippo.unifile.FilenameFilter;
import com.hippo.unifile.UniFile;
@@ -44,9 +43,11 @@ import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
private static final String TAG = DirGalleryProvider.class.getSimpleName();
public class DirPageLoader extends PageLoader2 implements Runnable {
private static final String TAG = DirPageLoader.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static final FilenameFilter imageFilter =
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
@@ -60,21 +61,19 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
};
private final UniFile mDir;
private final Stack<Integer> mRequests = new Stack<>();
private final AtomicInteger mDecodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private final AtomicInteger mDecodingIndex = new AtomicInteger(-1);
private final AtomicReference<UniFile[]> mFileList = new AtomicReference<>();
@Nullable
private Thread mBgThread;
private volatile int mSize = STATE_WAIT;
private volatile int mSize = PageLoader.STATE_WAIT;
private String mError;
public DirGalleryProvider(@NonNull UniFile dir) {
public DirPageLoader(@NonNull UniFile dir) {
mDir = dir;
}
@Override
public void start() {
super.start();
mBgThread = new PriorityThread(this, TAG + '-' + sIdGenerator.incrementAndGet(),
Process.THREAD_PRIORITY_BACKGROUND);
mBgThread.start();
@@ -82,8 +81,6 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
@Override
public void stop() {
super.stop();
if (mBgThread != null) {
mBgThread.interrupt();
mBgThread = null;
@@ -213,7 +210,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
UniFile[] files = mDir.listFiles(imageFilter);
if (files == null) {
mSize = STATE_ERROR;
mSize = PageLoader.STATE_ERROR;
mError = GetText.getString(R.string.error_not_folder_path);
// Notify to to show error
@@ -251,8 +248,8 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
// Check index valid
if (index < 0 || index >= files.length) {
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
mDecodingIndex.lazySet(-1);
notifyPageFailed(index);
continue;
}
@@ -260,15 +257,15 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
try {
is = files[index].openInputStream();
Image image = Image.decode((FileInputStream) is);
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
mDecodingIndex.lazySet(-1);
notifyPageSucceed(index, image);
} catch (IOException e) {
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_reading_failed));
mDecodingIndex.lazySet(-1);
notifyPageFailed(index);
} finally {
IOUtils.closeQuietly(is);
}
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
mDecodingIndex.lazySet(-1);
}
// Clear file list
@@ -23,37 +23,36 @@ import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.spider.SpiderQueen;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.SimpleHandler;
import java.util.Locale;
public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.OnSpiderListener {
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
public class EhPageLoader extends PageLoader2 implements SpiderQueen.OnSpiderListener {
private final Context mContext;
private final GalleryInfo mGalleryInfo;
@Nullable
private SpiderQueen mSpiderQueen;
public EhGalleryProvider(Context context, GalleryInfo galleryInfo) {
public EhPageLoader(Context context, GalleryInfo galleryInfo) {
mContext = context;
mGalleryInfo = galleryInfo;
}
@Override
public void start() {
super.start();
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mContext, mGalleryInfo, SpiderQueen.MODE_READ);
mSpiderQueen.addOnSpiderListener(this);
if (mSpiderQueen.size() > 0)
notifyDataChanged();
}
@Override
public void stop() {
super.stop();
if (mSpiderQueen != null) {
mSpiderQueen.removeOnSpiderListener(this);
// Activity recreate may called, so wait 3000s
@@ -120,7 +119,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
if (mSpiderQueen != null) {
return mSpiderQueen.size();
} else {
return GalleryProvider.STATE_ERROR;
return PageLoader.STATE_ERROR;
}
}
@@ -131,7 +130,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
if (object instanceof Float) {
notifyPagePercent(index, (Float) object);
} else if (object instanceof String) {
notifyPageFailed(index, (String) object);
notifyPageFailed(index);
} else if (object == null) {
notifyPageWait(index);
}
@@ -145,7 +144,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
if (object instanceof Float) {
notifyPagePercent(index, (Float) object);
} else if (object instanceof String) {
notifyPageFailed(index, (String) object);
notifyPageFailed(index);
} else if (object == null) {
notifyPageWait(index);
}
@@ -192,7 +191,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
@Override
public void onPageFailure(int index, String error, int finished, int downloaded, int total) {
notifyPageFailed(index, error);
notifyPageFailed(index);
}
@Override
@@ -206,7 +205,7 @@ public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.O
@Override
public void onGetImageFailure(int index, String error) {
notifyPageFailed(index, error);
notifyPageFailed(index);
}
private static class ReleaseTask implements Runnable {
@@ -19,10 +19,11 @@ package com.hippo.ehviewer.gallery;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.unifile.UniFile;
public abstract class GalleryProvider2 extends GalleryProvider {
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
public abstract class PageLoader2 extends PageLoader {
// With dot
public static final String[] SUPPORT_IMAGE_EXTENSIONS = {
@@ -86,7 +86,7 @@ class AccountPreference @JvmOverloads constructor(
}
}
builder.setPositiveButton(R.string.settings_eh_sign_out) { _: DialogInterface?, _: Int ->
EhUtils.signOut(context)
EhUtils.signOut()
mActivity.showTip(R.string.settings_eh_sign_out_tip, BaseScene.LENGTH_SHORT)
}
}
@@ -28,7 +28,7 @@ import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.ehviewer.gallery.PageLoader2;
import com.hippo.io.UniFileInputStreamPipe;
import com.hippo.io.UniFileOutputStreamPipe;
import com.hippo.streampipe.InputStreamPipe;
@@ -118,7 +118,7 @@ public final class SpiderDen {
@Nullable
private static UniFile findImageFile(UniFile dir, int index) {
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
for (String extension : PageLoader2.SUPPORT_IMAGE_EXTENSIONS) {
String filename = generateImageFilename(index, extension);
UniFile file = dir.findFile(filename);
if (file != null) {
@@ -179,10 +179,10 @@ public final class SpiderDen {
* @param extension with dot
*/
private String fixExtension(String extension) {
if (Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
if (Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
return extension;
} else {
return GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
return PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
}
}
@@ -261,8 +261,8 @@ public final class SpiderDen {
}
boolean result = false;
for (int i = 0, n = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
String filename = generateImageFilename(index, GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[i]);
for (int i = 0, n = PageLoader2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
String filename = generateImageFilename(index, PageLoader2.SUPPORT_IMAGE_EXTENSIONS[i]);
UniFile file = dir.subFile(filename);
if (file != null) {
result |= file.delete();
@@ -48,9 +48,7 @@ import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser;
import com.hippo.ehviewer.client.parser.GalleryPageApiParser;
import com.hippo.ehviewer.client.parser.GalleryPageParser;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.ehviewer.gallery.PageLoader2;
import com.hippo.image.Image;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
@@ -85,6 +83,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@@ -157,7 +156,7 @@ public final class SpiderQueen implements Runnable {
// For download, when it go to mPageStateArray.size(), done
private volatile int mDownloadPage = -1;
private SpiderQueen(EhApplication application, @NonNull GalleryInfo galleryInfo) {
private SpiderQueen(@NonNull GalleryInfo galleryInfo) {
mHttpClient = EhApplication.getOkHttpClient();
mSpiderInfoCache = EhApplication.getSpiderInfoCache();
mGalleryInfo = galleryInfo;
@@ -167,7 +166,7 @@ public final class SpiderQueen implements Runnable {
mPreloadNumber = MathUtils.clamp(Settings.getPreloadImage(), 0, 100);
for (int i = 0; i < DECODE_THREAD_NUM; i++) {
mDecodeIndexArray[i] = GalleryPageView.INVALID_INDEX;
mDecodeIndexArray[i] = -1;
}
mWorkerPoolExecutor = new ThreadPoolExecutor(mWorkerMaxCount, mWorkerMaxCount,
@@ -183,8 +182,8 @@ public final class SpiderQueen implements Runnable {
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
if (queen == null) {
EhApplication application = (EhApplication) context.getApplicationContext();
queen = new SpiderQueen(application, galleryInfo);
context.getApplicationContext();
queen = new SpiderQueen(galleryInfo);
sQueenMap.put(galleryInfo.gid, queen);
// Set mode
queen.setMode(mode);
@@ -358,12 +357,8 @@ public final class SpiderQueen implements Runnable {
private void setMode(@Mode int mode) {
switch (mode) {
case MODE_READ:
mReadReference++;
break;
case MODE_DOWNLOAD:
mDownloadReference++;
break;
case MODE_READ -> mReadReference++;
case MODE_DOWNLOAD -> mDownloadReference++;
}
if (mDownloadReference > 1) {
@@ -375,12 +370,8 @@ public final class SpiderQueen implements Runnable {
private void clearMode(@Mode int mode) {
switch (mode) {
case MODE_READ:
mReadReference--;
break;
case MODE_DOWNLOAD:
mDownloadReference--;
break;
case MODE_READ -> mReadReference--;
case MODE_DOWNLOAD -> mDownloadReference--;
}
if (mReadReference < 0 || mDownloadReference < 0) {
@@ -407,9 +398,9 @@ public final class SpiderQueen implements Runnable {
public int size() {
if (mQueenThread == null) {
return GalleryProvider.STATE_ERROR;
return PageLoader.STATE_ERROR;
} else if (mPageStateArray == null) {
return GalleryProvider.STATE_WAIT;
return PageLoader.STATE_WAIT;
} else {
return mPageStateArray.length;
}
@@ -523,21 +514,16 @@ public final class SpiderQueen implements Runnable {
Object result;
switch (state) {
default:
case STATE_NONE:
result = null;
break;
case STATE_DOWNLOADING:
result = mPagePercentMap.get(index);
break;
case STATE_FAILED:
case STATE_NONE -> result = null;
case STATE_DOWNLOADING -> result = mPagePercentMap.get(index);
case STATE_FAILED -> {
String error = mPageErrorMap.get(index);
if (error == null) {
error = GetText.getString(R.string.error_unknown);
}
result = error;
break;
case STATE_FINISHED:
}
case STATE_FINISHED -> {
synchronized (mDecodeRequestQueue) {
if (!contain(mDecodeIndexArray, index) && !mDecodeRequestQueue.contains(index)) {
mDecodeRequestQueue.add(index);
@@ -545,7 +531,8 @@ public final class SpiderQueen implements Runnable {
}
}
result = null;
break;
}
default -> throw new IllegalStateException("Unexpected value: " + state);
}
tryToEnsureWorkers();
@@ -645,7 +632,6 @@ public final class SpiderQueen implements Runnable {
return null;
}
OutputStream os = null;
try {
pipe.obtain();
@@ -661,7 +647,6 @@ public final class SpiderQueen implements Runnable {
} finally {
pipe.close();
pipe.release();
IOUtils.closeQuietly(os);
}
}
@@ -751,14 +736,13 @@ public final class SpiderQueen implements Runnable {
}
private SpiderInfo readSpiderInfoFromInternet() {
try {
SpiderInfo spiderInfo = new SpiderInfo();
spiderInfo.gid = mGalleryInfo.gid;
spiderInfo.token = mGalleryInfo.token;
SpiderInfo spiderInfo = new SpiderInfo();
spiderInfo.gid = mGalleryInfo.gid;
spiderInfo.token = mGalleryInfo.token;
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
Response response = mHttpClient.newCall(request).execute();
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
try (Response response = mHttpClient.newCall(request).execute()) {
String body = response.body().string();
spiderInfo.pages = GalleryDetailParser.parsePages(body);
@@ -777,15 +761,14 @@ public final class SpiderQueen implements Runnable {
return null;
}
try {
String url = EhUrl.getGalleryMultiPageViewerUrl(
mGalleryInfo.gid, mGalleryInfo.token);
if (DEBUG_PTOKEN) {
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
}
String referer = EhUrl.getReferer();
Request request = new EhRequestBuilder(url, referer).build();
Response response = mHttpClient.newCall(request).execute();
String url = EhUrl.getGalleryMultiPageViewerUrl(
mGalleryInfo.gid, mGalleryInfo.token);
if (DEBUG_PTOKEN) {
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
}
String referer = EhUrl.getReferer();
Request request = new EhRequestBuilder(url, referer).build();
try (Response response = mHttpClient.newCall(request).execute()) {
String body = response.body().string();
ArrayList<String> list = GalleryMultiPageViewerPTokenParser.parse(body);
@@ -824,16 +807,15 @@ public final class SpiderQueen implements Runnable {
previewIndex = Math.min(previewIndex, spiderInfo.previewPages - 1);
}
try {
String url = EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
String referer = EhUrl.getReferer();
if (DEBUG_PTOKEN) {
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
}
Request request = new EhRequestBuilder(url, referer).build();
Response response = mHttpClient.newCall(request).execute();
String url = EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
String referer = EhUrl.getReferer();
if (DEBUG_PTOKEN) {
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
}
Request request = new EhRequestBuilder(url, referer).build();
try (Response response = mHttpClient.newCall(request).execute()) {
String body = response.body().string();
readPreviews(body, previewIndex, spiderInfo);
@@ -858,7 +840,7 @@ public final class SpiderQueen implements Runnable {
UniFile file = downloadDir.createFile(SPIDER_INFO_FILENAME);
try {
spiderInfo.write(file.openOutputStream());
} catch (Throwable e) {
} catch (IOException e) {
ExceptionUtils.throwIfFatal(e);
// Ignore
}
@@ -1150,7 +1132,7 @@ public final class SpiderQueen implements Runnable {
}
// false for stop
private boolean downloadImage(long gid, int index, String pToken, String previousPToken, boolean force) {
private boolean downloadImage(long gid, int index, String pToken, String previousPToken) {
String skipHathKey = null;
List<String> skipHathKeys = new ArrayList<>(5);
String originImageUrl = null;
@@ -1284,12 +1266,6 @@ public final class SpiderQueen implements Runnable {
continue;
}
if (responseBody == null) {
error = "Empty response body";
forceHtml = true;
continue;
}
// Get extension
String extension = null;
MediaType mediaType = responseBody.contentType();
@@ -1297,8 +1273,8 @@ public final class SpiderQueen implements Runnable {
extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mediaType.toString());
}
// Ensure extension
if (!Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
extension = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
if (!Utilities.contain(PageLoader2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
extension = PageLoader2.SUPPORT_IMAGE_EXTENSIONS[0];
}
OutputStreamPipe osPipe = null;
@@ -1575,7 +1551,7 @@ public final class SpiderQueen implements Runnable {
}
// Get image url
return downloadImage(mGid, index, pToken, previousPToken, force);
return downloadImage(mGid, index, pToken, previousPToken);
}
@Override
@@ -1596,7 +1572,7 @@ public final class SpiderQueen implements Runnable {
Log.e(TAG, "WTF, mWorkerCount < 0, not thread safe or something wrong");
mWorkerCount = 0;
}
finish = mWorkerCount <= 0;
finish = mWorkerCount == 0;
}
if (finish) {
@@ -1619,7 +1595,7 @@ public final class SpiderQueen implements Runnable {
private void resetDecodeIndex() {
synchronized (mDecodeRequestQueue) {
mDecodeIndexArray[mThreadIndex] = GalleryPageView.INVALID_INDEX;
mDecodeIndexArray[mThreadIndex] = -1;
}
}
File diff suppressed because it is too large. Load diff
@@ -136,7 +136,7 @@ public final class MainActivity extends StageActivity
@NonNull
@Override
protected Announcer getLaunchAnnouncer() {
if (EhUtils.needSignedIn(this)) {
if (EhUtils.needSignedIn()) {
return new Announcer(SignInScene.class);
} else if (Settings.getSelectSite()) {
return new Announcer(SelectSiteScene.class);
@@ -150,7 +150,7 @@ public final class MainActivity extends StageActivity
// Sometimes scene can't show directly
private Announcer processAnnouncer(Announcer announcer) {
if (0 == getSceneCount()) {
if (EhUtils.needSignedIn(this)) {
if (EhUtils.needSignedIn()) {
Bundle newArgs = new Bundle();
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.getArgs());
@@ -16,6 +16,7 @@
package com.hippo.ehviewer.ui.fragment;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
@@ -70,7 +71,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
private static final String KEY_OPEN_BY_DEFAULT = "open_by_default";
private static final String KEY_BACKUP_FAVORITE = "backup_favorite";
ActivityResultLauncher<String> exportLauncher = registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
new ActivityResultContracts.CreateDocument("application/vnd.sqlite3"),
uri -> {
if (uri != null) {
try {
@@ -107,7 +108,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
}
});
ActivityResultLauncher<String> dumpLogcatLauncher = registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
new ActivityResultContracts.CreateDocument("application/zip"),
uri -> {
if (uri != null) {
try {
@@ -291,7 +292,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
return true;
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
try {
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
@SuppressLint("InlinedApi") Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + requireContext().getPackageName()));
startActivity(intent);
} catch (Throwable t) {
@@ -312,36 +313,35 @@ public class AdvancedFragment extends BasePreferenceFragment {
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
request.setCallback(new EhClient.Callback() {
request.setCallback(new EhClient.Callback<FavoritesParser.Result>() {
@Override
public void onSuccess(Object result) {
public void onSuccess(FavoritesParser.Result result) {
try {
FavoritesParser.Result favResult = (FavoritesParser.Result) result;
if (favResult.galleryInfoList.isEmpty()) {
if (result.galleryInfoList.isEmpty()) {
showTip(R.string.settings_advanced_backup_favorite_nothing, BaseScene.LENGTH_SHORT);
} else {
if (favTotal == 0 && favResult.countArray != null) {
if (favTotal == 0 && result.countArray != null) {
int totalFav = 0;
for (int i = 0; i < 10; i++) {
totalFav = totalFav + favResult.countArray[i];
totalFav = totalFav + result.countArray[i];
}
favTotal = (int) Math.ceil((double) totalFav / favResult.galleryInfoList.size());
favTotal = (int) Math.ceil((double) totalFav / result.galleryInfoList.size());
}
String status = "(" + favIndex + "/" + favTotal + ")";
showTip(GetText.getString(R.string.settings_advanced_backup_favorite_start, status), BaseScene.LENGTH_SHORT);
Log.d("LocalFavorites", "now backup page " + status);
EhDB.putLocalFavorites(favResult.galleryInfoList);
EhDB.putLocalFavorites(result.galleryInfoList);
if (favResult.nextPage != null) {
if (result.nextPage != null) {
try {
Thread.sleep(Settings.getDownloadDelay());
} catch (InterruptedException e) {
e.printStackTrace();
}
favIndex++;
favListUrlBuilder.setNext(favResult.nextPage);
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
favListUrlBuilder.setNext(result.nextPage);
request.setArgs(favListUrlBuilder.build());
mClient.execute(request);
} else {
showTip(R.string.settings_advanced_backup_favorite_success, BaseScene.LENGTH_SHORT);
@@ -363,7 +363,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
}
});
request.setArgs(favListUrlBuilder.build(), Settings.getShowJpnTitle());
request.setArgs(favListUrlBuilder.build());
mClient.execute(request);
}
@@ -35,7 +35,9 @@ import com.google.android.material.textfield.TextInputLayout;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.util.ClipboardUtilKt;
@@ -68,31 +70,6 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
@Nullable
private TextView mFromClipboard;
// false for error
private static boolean checkIpbMemberId(String id) {
for (int i = 0, n = id.length(); i < n; i++) {
char ch = id.charAt(i);
if (!(ch >= '0' && ch <= '9')) {
return false;
}
}
return true;
}
private static boolean checkIpbPassHash(String hash) {
if (32 != hash.length()) {
return false;
}
for (int i = 0, n = hash.length(); i < n; i++) {
char ch = hash.charAt(i);
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z')) {
return false;
}
}
return true;
}
private static Cookie newCookie(String name, String value, String domain) {
return new Cookie.Builder().name(name).value(value)
.domain(domain).expiresAt(Long.MAX_VALUE).build();
@@ -216,20 +193,12 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
hideSoftInput();
if (!checkIpbMemberId(ipbMemberId) || !(checkIpbPassHash(ipbPassHash))) {
new BaseDialogBuilder(context).setTitle(R.string.waring)
.setMessage(R.string.wrong_cookie_warning)
.setPositiveButton(R.string.i_will_check_it, null)
.setNegativeButton(R.string.i_dont_think_so, (dialog, which) -> {
storeCookie(ipbMemberId, ipbPassHash, igneous);
setResult(RESULT_OK, null);
finish();
}).show();
} else {
storeCookie(ipbMemberId, ipbPassHash, igneous);
setResult(RESULT_OK, null);
finish();
}
storeCookie(ipbMemberId, ipbPassHash, igneous);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_PROFILE)
.setCallback(new CookieSignInListener());
EhApplication.getEhClient().execute(request);
}
private void storeCookie(String id, String hash, String igneous) {
@@ -238,7 +207,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
return;
}
EhUtils.signOut(context);
EhUtils.signOut();
EhCookieStore store = EhApplication.getEhCookieStore();
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E));
@@ -252,8 +221,11 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
}
private void fillCookiesFromClipboard() {
Context context = requireContext();
hideSoftInput();
String text = ClipboardUtilKt.getTextFromClipboard(ClipboardUtilKt.getClipboardManager(getContext()), getContext());
String text = ClipboardUtilKt.getTextFromClipboard(
ClipboardUtilKt.getClipboardManager(context), context);
if (text == null) {
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
return;
@@ -309,4 +281,25 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
}
}
private class CookieSignInListener implements EhClient.Callback<Object> {
@Override
public void onSuccess(Object result) {
setResult(RESULT_OK, null);
finish();
}
@Override
public void onFailure(Exception e) {
EhApplication.getEhCookieStore().signOut();
new BaseDialogBuilder(requireContext()).setTitle(R.string.sign_in_failed)
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.wrong_cookie_warning))
.setPositiveButton(R.string.get_it, null).show();
}
@Override
public void onCancel() {
EhApplication.getEhCookieStore().signOut();
}
}
}
@@ -70,7 +70,6 @@ import com.hippo.ehviewer.dao.DownloadLabel;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.download.DownloadService;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.widget.SimpleRatingView;
import com.hippo.scene.Announcer;
import com.hippo.unifile.UniFile;
@@ -89,6 +88,7 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import rikka.core.res.ResourcesKt;
@SuppressLint("RtlHardcoded")
@@ -596,9 +596,9 @@ public class DownloadsScene extends ToolbarScene
return false;
}
Intent intent = new Intent(activity, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, list.get(position));
Intent intent = new Intent(activity, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, list.get(position));
startActivity(intent);
return true;
}
@@ -86,7 +86,7 @@ import java.util.List;
import rikka.core.res.ResourcesKt;
// TODO Get favorite, modify favorite, add favorite, what a mess!
@SuppressLint("RtlHardcoded")
@SuppressLint({"NotifyDataSetChanged", "RtlHardcoded"})
public class FavoritesScene extends SearchBarScene implements
FastScroller.OnDragHandlerListener,
FabLayout.OnClickFabListener, FabLayout.OnExpandListener,
@@ -103,8 +103,6 @@ public class FavoritesScene extends SearchBarScene implements
public int current; // -1 for error
public int limit; // -1 for error
@Nullable
private ContentLayout mContentLayout;
@Nullable
@ViewLifeCircle
private EasyRecyclerView mRecyclerView;
@Nullable
@@ -231,12 +229,12 @@ public class FavoritesScene extends SearchBarScene implements
mUrlBuilder = null;
}
@Nullable
@NonNull
@Override
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_favorites, container, false);
mContentLayout = view.findViewById(R.id.content_layout);
ContentLayout mContentLayout = view.findViewById(R.id.content_layout);
MainActivity activity = getMainActivity();
AssertUtils.assertNotNull(activity);
setOnApplySearch((query) -> {
@@ -247,7 +245,8 @@ public class FavoritesScene extends SearchBarScene implements
mRecyclerView = mContentLayout.getRecyclerView();
FastScroller fastScroller = mContentLayout.getFastScroller();
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
AssertUtils.assertNotNull(container);
container.addView(mFabLayout);
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
@@ -291,7 +290,7 @@ public class FavoritesScene extends SearchBarScene implements
// Restore search mode
if (mSearchMode) {
mSearchMode = false;
enterSearchMode(false);
enterSearchMode();
}
// Only refresh for the first time
@@ -366,7 +365,7 @@ public class FavoritesScene extends SearchBarScene implements
mRecyclerView = null;
}
if (null != mFabLayout) {
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
removeAboveSnackView(mFabLayout);
mFabLayout = null;
}
@@ -427,7 +426,7 @@ public class FavoritesScene extends SearchBarScene implements
} else if (mFabLayout != null && mFabLayout.isExpanded()) {
mFabLayout.toggle();
} else if (mSearchMode) {
exitSearchMode(true);
exitSearchMode();
} else if (isSearchViewShown()) {
hideSearchView();
} else {
@@ -451,7 +450,7 @@ public class FavoritesScene extends SearchBarScene implements
showSearchBar();
}
public boolean onItemClick(View view, int position) {
public boolean onItemClick(int position) {
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) {
// Skip if in search mode
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
@@ -470,12 +469,7 @@ public class FavoritesScene extends SearchBarScene implements
return true;
}
// Ensure outOfCustomChoiceMode to avoid error
//if (mRecyclerView != null) {
// mRecyclerView.isInCustomChoice();
//}
exitSearchMode(true);
exitSearchMode();
mUrlBuilder.setKeyword(null);
mUrlBuilder.setFavCat(newFavCat);
@@ -524,12 +518,7 @@ public class FavoritesScene extends SearchBarScene implements
return;
}
// Ensure outOfCustomChoiceMode to avoid error
//if (mRecyclerView != null) {
// mRecyclerView.isInCustomChoice();
//}
exitSearchMode(true);
exitSearchMode();
mUrlBuilder.setKeyword(query);
updateSearchBar();
@@ -566,7 +555,7 @@ public class FavoritesScene extends SearchBarScene implements
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
ArrayList listValidators = new ArrayList<>();
ArrayList<CalendarConstraints.DateValidator> listValidators = new ArrayList<>();
listValidators.add(DateValidatorPointForward.from(fromDate));
listValidators.add(DateValidatorPointBackward.before(toDate));
@@ -581,9 +570,7 @@ public class FavoritesScene extends SearchBarScene implements
.setSelection(toDate)
.build();
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
datePicker.addOnPositiveButtonClickListener(v -> {
mHelper.goTo(v);
});
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v));
}
@Override
@@ -623,11 +610,9 @@ public class FavoritesScene extends SearchBarScene implements
}
switch (position) {
case 3: { // Check all
mRecyclerView.checkAll();
break;
}
case 4: { // Download
case 3 -> // Check all
mRecyclerView.checkAll();
case 4 -> { // Download
Activity activity = getMainActivity();
if (activity != null) {
CommonOperations.startDownload(getMainActivity(), mModifyGiList, false);
@@ -636,9 +621,8 @@ public class FavoritesScene extends SearchBarScene implements
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
mRecyclerView.outOfCustomChoiceMode();
}
break;
}
case 5: { // Delete
case 5 -> { // Delete
DeleteDialogHelper helper = new DeleteDialogHelper();
new BaseDialogBuilder(context)
.setTitle(R.string.delete_favorites_dialog_title)
@@ -646,9 +630,8 @@ public class FavoritesScene extends SearchBarScene implements
.setPositiveButton(android.R.string.ok, helper)
.setOnCancelListener(helper)
.show();
break;
}
case 6: { // Move
case 6 -> { // Move
MoveDialogHelper helper = new MoveDialogHelper();
// First is local favorite, the other 10 is cloud favorite
String[] array = new String[11];
@@ -659,7 +642,6 @@ public class FavoritesScene extends SearchBarScene implements
.setItems(array, helper)
.setOnCancelListener(helper)
.show();
break;
}
}
}
@@ -723,7 +705,7 @@ public class FavoritesScene extends SearchBarScene implements
}
}
private void enterSearchMode(boolean animation) {
private void enterSearchMode() {
if (mSearchMode) {
return;
}
@@ -731,7 +713,7 @@ public class FavoritesScene extends SearchBarScene implements
showSearchBar();
}
private void exitSearchMode(boolean animation) {
private void exitSearchMode() {
if (!mSearchMode) {
return;
}
@@ -756,9 +738,8 @@ public class FavoritesScene extends SearchBarScene implements
}
updateSearchBar();
assert mUrlBuilder != null;
int pages = 0;
int pages;
if (result.nextPage == null)
pages = mHelper.pgCounter + 1;
else
@@ -967,7 +948,7 @@ public class FavoritesScene extends SearchBarScene implements
holder.value.setText(Integer.toString(mFavCountArray[position - 2]));
holder.itemView.setEnabled(true);
}
holder.itemView.setOnClickListener(v -> onItemClick(holder.itemView, position));
holder.itemView.setOnClickListener(v -> onItemClick(position));
}
@Override
@@ -1080,7 +1061,7 @@ public class FavoritesScene extends SearchBarScene implements
@Override
void onItemClick(View view, int position) {
FavoritesScene.this.onItemClick(view, position);
FavoritesScene.this.onItemClick(position);
}
@Override
@@ -1114,8 +1095,8 @@ public class FavoritesScene extends SearchBarScene implements
boolean local = mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_LOCAL;
long[] gidArray = new long[mModifyGiList.size()];
if (mModifyAdd) {
long[] gidArray = new long[mModifyGiList.size()];
String[] tokenArray = new String[mModifyGiList.size()];
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
GalleryInfo gi = mModifyGiList.get(i);
@@ -1133,7 +1114,6 @@ public class FavoritesScene extends SearchBarScene implements
request.setArgs(gidArray, tokenArray, mModifyFavCat);
mClient.execute(request);
} else {
long[] gidArray = new long[mModifyGiList.size()];
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
gidArray[i] = mModifyGiList.get(i).gid;
}
@@ -1153,7 +1133,7 @@ public class FavoritesScene extends SearchBarScene implements
request.setCallback(new GetFavoritesListener(getContext(),
activity.getStageId(), getTag(),
taskId, local, mUrlBuilder.getKeyword()));
request.setArgs(url, gidArray, mModifyFavCat, Settings.getShowJpnTitle());
request.setArgs(url, gidArray, mModifyFavCat);
mClient.execute(request);
}
} else if (mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_LOCAL) {
@@ -1164,7 +1144,7 @@ public class FavoritesScene extends SearchBarScene implements
if (jumpTo != null) {
next = Integer.toString(minGid);
operation = 0;
} else if (page != 0) {
} else if (page != 0 && mHelper != null) {
if (page >= mHelper.getPageForTop()) {
next = lowerPage;
operation = 1;
@@ -1185,7 +1165,7 @@ public class FavoritesScene extends SearchBarScene implements
request.setCallback(new GetFavoritesListener(getContext(),
activity.getStageId(), getTag(),
taskId, false, mUrlBuilder.getKeyword()));
request.setArgs(url, Settings.getShowJpnTitle());
request.setArgs(url);
mClient.execute(request);
}
}
@@ -1208,9 +1188,9 @@ public class FavoritesScene extends SearchBarScene implements
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (mAdapter != null) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@@ -1251,16 +1231,18 @@ public class FavoritesScene extends SearchBarScene implements
operation = 0;
}
@NonNull
@Override
protected Parcelable saveInstanceState(Parcelable superState) {
protected Parcelable saveInstanceState(@NonNull Parcelable superState) {
Bundle bundle = (Bundle) super.saveInstanceState(superState);
bundle.putString(KEY_UPPER_PAGE, upperPage);
bundle.putString(KEY_LOWER_PAGE, lowerPage);
return bundle;
}
@NonNull
@Override
protected Parcelable restoreInstanceState(Parcelable state) {
protected Parcelable restoreInstanceState(@NonNull Parcelable state) {
Bundle bundle = (Bundle) state;
upperPage = bundle.getString(KEY_UPPER_PAGE);
lowerPage = bundle.getString(KEY_LOWER_PAGE);
@@ -65,7 +65,6 @@ import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.app.CheckBoxDialogBuilder;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.R;
@@ -90,11 +89,10 @@ import com.hippo.ehviewer.client.parser.RateGalleryParser;
import com.hippo.ehviewer.client.parser.VoteTagParser;
import com.hippo.ehviewer.dao.DownloadInfo;
import com.hippo.ehviewer.dao.Filter;
import com.hippo.ehviewer.gallery.EhGalleryProvider;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.ehviewer.gallery.EhPageLoader;
import com.hippo.ehviewer.gallery.PageLoader2;
import com.hippo.ehviewer.spider.SpiderDen;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.GalleryInfoBottomSheet;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
@@ -119,13 +117,13 @@ import com.hippo.yorozuya.IntIdGenerator;
import com.hippo.yorozuya.ViewUtils;
import com.hippo.yorozuya.collect.IntList;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import coil.Coil;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import okhttp3.HttpUrl;
import rikka.core.res.ResourcesKt;
@@ -454,7 +452,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
super.onResume();
if (mRead != null) {
try {
GalleryProvider2 galleryProvider = new EhGalleryProvider(requireContext(), mGalleryInfo);
PageLoader2 galleryProvider = new EhPageLoader(requireContext(), mGalleryInfo);
galleryProvider.start();
int startPage = galleryProvider.getStartPage();
if (startPage != 0) {
@@ -868,10 +866,10 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (mPage != 0) {
Snackbar.make(requireActivity().findViewById(R.id.snackbar), getString(R.string.read_from, mPage), Snackbar.LENGTH_LONG)
.setAction(R.string.read, v -> {
Intent intent = new Intent(requireContext(), GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryDetail);
intent.putExtra(GalleryActivity.KEY_PAGE, mPage);
Intent intent = new Intent(requireContext(), ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryDetail);
intent.putExtra(ReaderActivity.KEY_PAGE, mPage);
startActivity(intent);
})
.show();
@@ -1166,9 +1164,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
galleryInfo = mGalleryDetail;
}
if (galleryInfo != null) {
Intent intent = new Intent(activity, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryInfo);
Intent intent = new Intent(activity, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryInfo);
startActivity(intent);
}
} else if (mNewerVersion == v) {
@@ -1300,10 +1298,10 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
o = v.getTag(R.id.index);
if (null != galleryInfo && o instanceof Integer) {
int index = (Integer) o;
Intent intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, galleryInfo);
intent.putExtra(GalleryActivity.KEY_PAGE, index);
Intent intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryInfo);
intent.putExtra(ReaderActivity.KEY_PAGE, index);
startActivity(intent);
}
}
@@ -23,7 +23,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
import android.text.Spannable;
@@ -88,7 +87,6 @@ import com.hippo.ehviewer.dao.DownloadLabel;
import com.hippo.ehviewer.dao.QuickSearch;
import com.hippo.ehviewer.download.DownloadManager;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter;
import com.hippo.ehviewer.widget.GalleryInfoContentHelper;
@@ -118,6 +116,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import rikka.core.res.ResourcesKt;
@SuppressLint("RtlHardcoded")
@@ -140,8 +139,6 @@ public final class GalleryListScene extends SearchBarScene
private final static int STATE_SEARCH = 2;
private final static int STATE_SEARCH_SHOW_LIST = 3;
private static final long ANIMATE_TIME = 300L;
// Double click back exit
private final long mPressBackTime = 0;
/*---------------
Whole life cycle
---------------*/
@@ -149,11 +146,6 @@ public final class GalleryListScene extends SearchBarScene
private EhClient mClient;
@Nullable
private ListUrlBuilder mUrlBuilder;
/*---------------
View life cycle
---------------*/
@Nullable
private ContentLayout mContentLayout;
@Nullable
private EasyRecyclerView mRecyclerView;
@Nullable
@@ -247,17 +239,13 @@ public final class GalleryListScene extends SearchBarScene
} else if (ListUrlBuilder.MODE_WHATS_HOT == urlBuilder.getMode()) {
return resources.getString(R.string.whats_hot);
} else if (ListUrlBuilder.MODE_TOPLIST == urlBuilder.getMode()) {
switch (urlBuilder.getKeyword()) {
case "11":
return resources.getString(R.string.toplist_alltime);
case "12":
return resources.getString(R.string.toplist_pastyear);
case "13":
return resources.getString(R.string.toplist_pastmonth);
case "15":
return resources.getString(R.string.toplist_yesterday);
}
return null;
return switch (urlBuilder.getKeyword()) {
case "11" -> resources.getString(R.string.toplist_alltime);
case "12" -> resources.getString(R.string.toplist_pastyear);
case "13" -> resources.getString(R.string.toplist_pastmonth);
case "15" -> resources.getString(R.string.toplist_yesterday);
default -> null;
};
} else if (!TextUtils.isEmpty(keyword)) {
return keyword;
} else if (MathUtils.hammingWeight(category) == 1) {
@@ -267,11 +255,6 @@ public final class GalleryListScene extends SearchBarScene
}
}
@Override
public int getMenuResId() {
return R.menu.scene_gallery_list_searchbar_menu;
}
public static void startScene(SceneFragment scene, ListUrlBuilder lub) {
scene.startScene(getStartAnnouncer(lub));
}
@@ -283,6 +266,11 @@ public final class GalleryListScene extends SearchBarScene
return new Announcer(GalleryListScene.class).setArgs(args);
}
@Override
public int getMenuResId() {
return R.menu.scene_gallery_list_searchbar_menu;
}
@Override
public int getNavCheckedItem() {
return mNavCheckedId;
@@ -325,6 +313,7 @@ public final class GalleryListScene extends SearchBarScene
showSearchBar();
}
@SuppressLint("NotifyDataSetChanged")
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -547,7 +536,7 @@ public final class GalleryListScene extends SearchBarScene
mShowActionFab = true;
View mainLayout = ViewUtils.$$(view, R.id.main_layout);
mContentLayout = (ContentLayout) ViewUtils.$$(mainLayout, R.id.content_layout);
ContentLayout mContentLayout = (ContentLayout) ViewUtils.$$(mainLayout, R.id.content_layout);
mRecyclerView = mContentLayout.getRecyclerView();
FastScroller fastScroller = mContentLayout.getFastScroller();
mSearchLayout = (SearchLayout) ViewUtils.$$(mainLayout, R.id.search_layout);
@@ -558,7 +547,7 @@ public final class GalleryListScene extends SearchBarScene
mFabLayout,
(View) mSearchFab.getParent()
));
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
container.addView(mFabLayout);
int paddingTopSB = resources.getDimensionPixelOffset(R.dimen.gallery_padding_top_search_bar);
@@ -601,7 +590,7 @@ public final class GalleryListScene extends SearchBarScene
int colorID = ResourcesKt.resolveColor(getTheme(), com.google.android.material.R.attr.colorOnSurface);
mActionFabDrawable = new AddDeleteDrawable(context, colorID);
mFabLayout.getPrimaryFab().setImageDrawable(mActionFabDrawable);
mSearchFab.setOnClickListener( (v) -> {
mSearchFab.setOnClickListener((v) -> {
if (STATE_NORMAL != mState) {
onApplySearch();
hideSoftInput();
@@ -640,7 +629,7 @@ public final class GalleryListScene extends SearchBarScene
mRecyclerView = null;
}
if (null != mFabLayout) {
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
((ViewGroup) mFabLayout.getParent()).removeView(mFabLayout);
removeAboveSnackView(mFabLayout);
mFabLayout = null;
}
@@ -651,8 +640,7 @@ public final class GalleryListScene extends SearchBarScene
mActionFabDrawable = null;
}
private void showQuickSearchTipDialog(final QsDrawerAdapter adapter,
final EasyRecyclerView recyclerView, final TextView tip) {
private void showQuickSearchTipDialog() {
Context context = getContext();
if (null == context) {
return;
@@ -756,7 +744,7 @@ public final class GalleryListScene extends SearchBarScene
if (id == R.id.action_add) {
showAddQuickSearchDialog(qsDrawerAdapter, recyclerView, tip);
} else if (id == R.id.action_help) {
showQuickSearchTipDialog(qsDrawerAdapter, recyclerView, tip);
showQuickSearchTipDialog();
}
return true;
});
@@ -778,15 +766,7 @@ public final class GalleryListScene extends SearchBarScene
}
if (isSearchViewShown())
return false;
switch (mState) {
case STATE_NORMAL:
return getStackIndex() == 0;
case STATE_SIMPLE_SEARCH:
case STATE_SEARCH:
case STATE_SEARCH_SHOW_LIST:
return false;
}
return false;
return mState == STATE_NORMAL && getStackIndex() == 0;
}
@Override
@@ -817,7 +797,7 @@ public final class GalleryListScene extends SearchBarScene
}
}
public boolean onItemClick(View view, int position) {
public boolean onItemClick(int position) {
if (null == mHelper || null == mRecyclerView) {
return false;
}
@@ -884,7 +864,7 @@ public final class GalleryListScene extends SearchBarScene
long fromDate = local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
long toDate = MaterialDatePicker.todayInUtcMilliseconds();
ArrayList listValidators = new ArrayList<>();
ArrayList<CalendarConstraints.DateValidator> listValidators = new ArrayList<>();
listValidators.add(DateValidatorPointForward.from(fromDate));
listValidators.add(DateValidatorPointBackward.before(toDate));
@@ -899,9 +879,7 @@ public final class GalleryListScene extends SearchBarScene
.setSelection(toDate)
.build();
datePicker.show(requireActivity().getSupportFragmentManager(), "date-picker");
datePicker.addOnPositiveButtonClickListener(v -> {
mHelper.goTo(v);
});
datePicker.addOnPositiveButtonClickListener(v -> mHelper.goTo(v));
}
}
@@ -999,9 +977,9 @@ public final class GalleryListScene extends SearchBarScene
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
switch (which) {
case 0: // Read
Intent intent = new Intent(activity, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi);
Intent intent = new Intent(activity, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, gi);
startActivity(intent);
break;
case 1: // Download
@@ -1186,21 +1164,12 @@ public final class GalleryListScene extends SearchBarScene
}
break;
case STATE_SEARCH:
if (state == STATE_NORMAL) {
mViewTransition.showView(0, animation);
selectActionFab(animation);
} else if (state == STATE_SIMPLE_SEARCH) {
mViewTransition.showView(0, animation);
} else if (state == STATE_SEARCH_SHOW_LIST) {
}
break;
case STATE_SEARCH_SHOW_LIST:
if (state == STATE_NORMAL) {
mViewTransition.showView(0, animation);
selectActionFab(animation);
} else if (state == STATE_SIMPLE_SEARCH) {
mViewTransition.showView(0, animation);
} else if (state == STATE_SEARCH) {
}
break;
}
@@ -1208,12 +1177,6 @@ public final class GalleryListScene extends SearchBarScene
((StageActivity) requireActivity()).updateBackPressCallBackStatus();
}
public void onClickTitle() {
if (mState == STATE_NORMAL) {
setState(STATE_SIMPLE_SEARCH);
}
}
@Override
public boolean onMenuItemClick(@NonNull MenuItem item) {
if (mState == STATE_NORMAL) {
@@ -1251,15 +1214,6 @@ public final class GalleryListScene extends SearchBarScene
setState(STATE_NORMAL);
}
public void onReceiveContent(Uri uri) {
if (mSearchLayout == null || uri == null) {
return;
}
mSearchLayout.setSearchMode(SearchLayout.SEARCH_MODE_IMAGE);
mSearchLayout.setImageUri(uri);
setState(STATE_SEARCH);
}
@Override
public void onStartDragHandler() {
// Lock right drawer
@@ -1308,7 +1262,7 @@ public final class GalleryListScene extends SearchBarScene
: R.string.gallery_list_empty_hit);
mHelper.setEmptyString(emptyString);
int pages = 0;
int pages;
if (mIsTopList)
pages = 200;
else if (result.nextGid == 0)
@@ -1619,7 +1573,7 @@ public final class GalleryListScene extends SearchBarScene
@Override
void onItemClick(View view, int position) {
GalleryListScene.this.onItemClick(view, position);
GalleryListScene.this.onItemClick(position);
}
@Override
@@ -1692,6 +1646,7 @@ public final class GalleryListScene extends SearchBarScene
return GalleryListScene.this.getContext();
}
@SuppressLint("NotifyDataSetChanged")
@Override
protected void notifyDataSetChanged() {
if (null != mAdapter) {
@@ -1700,9 +1655,9 @@ public final class GalleryListScene extends SearchBarScene
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (null != mAdapter) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@@ -21,7 +21,6 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -50,7 +49,6 @@ import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.data.GalleryPreview;
import com.hippo.ehviewer.client.data.PreviewSet;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.ehviewer.ui.GalleryActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.widget.ContentLayout;
@@ -63,6 +61,8 @@ import com.hippo.yorozuya.ViewUtils;
import java.util.ArrayList;
import java.util.Locale;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
public class GalleryPreviewsScene extends ToolbarScene {
public static final String KEY_GALLERY_INFO = "gallery_info";
@@ -76,11 +76,6 @@ public class GalleryPreviewsScene extends ToolbarScene {
@Nullable
private GalleryInfo mGalleryInfo;
/*---------------
View life cycle
---------------*/
@Nullable
private ContentLayout mContentLayout;
@Nullable
private EasyRecyclerView mRecyclerView;
@Nullable
@@ -132,11 +127,11 @@ public class GalleryPreviewsScene extends ToolbarScene {
outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo);
}
@Nullable
@NonNull
@Override
public View onCreateViewWithToolbar(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mContentLayout = (ContentLayout) inflater.inflate(
ContentLayout mContentLayout = (ContentLayout) inflater.inflate(
R.layout.scene_gallery_previews, container, false);
mContentLayout.hideFastScroll();
mRecyclerView = mContentLayout.getRecyclerView();
@@ -144,7 +139,6 @@ public class GalleryPreviewsScene extends ToolbarScene {
Context context = getContext();
AssertUtils.assertNotNull(context);
Resources resources = context.getResources();
mAdapter = new GalleryPreviewAdapter();
mRecyclerView.setAdapter(mAdapter);
@@ -191,12 +185,14 @@ public class GalleryPreviewsScene extends ToolbarScene {
super.onViewCreated(view, savedInstanceState);
setTitle(R.string.gallery_previews);
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
if (((GalleryDetail) mGalleryInfo).previewPages > 2)
showMenu(R.menu.scene_gallery_previews);
if (mGalleryInfo != null) {
if (((GalleryDetail) mGalleryInfo).previewPages > 2)
showMenu(R.menu.scene_gallery_previews);
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
public boolean onMenuItemClick(@NonNull MenuItem item) {
Context context = getContext();
if (null == context) {
return false;
@@ -232,10 +228,10 @@ public class GalleryPreviewsScene extends ToolbarScene {
if (null != context && null != mHelper && null != mGalleryInfo) {
GalleryPreview p = mHelper.getDataAtEx(position);
if (p != null) {
Intent intent = new Intent(context, GalleryActivity.class);
intent.setAction(GalleryActivity.ACTION_EH);
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryInfo);
intent.putExtra(GalleryActivity.KEY_PAGE, p.getPosition());
Intent intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryInfo);
intent.putExtra(ReaderActivity.KEY_PAGE, p.getPosition());
startActivity(intent);
}
}
@@ -368,6 +364,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return GalleryPreviewsScene.this.getContext();
}
@SuppressLint("NotifyDataSetChanged")
@Override
protected void notifyDataSetChanged() {
if (mAdapter != null) {
@@ -376,9 +373,9 @@ public class GalleryPreviewsScene extends ToolbarScene {
}
@Override
protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (mAdapter != null) {
mAdapter.notifyItemRangeRemoved(positionStart, itemCount);
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@@ -45,7 +45,6 @@ import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.dao.HistoryInfo
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.GalleryActivity
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
import com.hippo.ehviewer.widget.SimpleRatingView
import com.hippo.scene.Announcer
@@ -54,6 +53,7 @@ import com.hippo.view.ViewTransition
import com.hippo.widget.LoadImageView
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager
import com.hippo.yorozuya.ViewUtils
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import rikka.core.res.resolveColor
@@ -267,9 +267,9 @@ class HistoryScene : ToolbarScene() {
) { _: DialogInterface?, which: Int ->
when (which) {
0 -> {
val intent = Intent(activity, GalleryActivity::class.java)
intent.action = GalleryActivity.ACTION_EH
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi)
val intent = Intent(activity, ReaderActivity::class.java)
intent.action = ReaderActivity.ACTION_EH
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, gi)
startActivity(intent)
}
@@ -371,6 +371,7 @@ class HistoryScene : ToolbarScene() {
val simpleLanguage: TextView = itemView.findViewById(R.id.simple_language)
val pages: TextView = itemView.findViewById(R.id.pages)
val downloaded: ImageView = itemView.findViewById(R.id.downloaded)
val favourited: ImageView = itemView.findViewById(R.id.favourited)
}
private inner class MoveDialogHelper(
@@ -433,6 +434,8 @@ class HistoryScene : ToolbarScene() {
}
holder.downloaded.visibility =
if (mDownloadManager.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
holder.favourited.visibility =
if (gi.favoriteSlot != -2) View.VISIBLE else View.GONE
holder.card.setOnClickListener { onItemClick(gi) }
holder.card.setOnLongClickListener { onItemLongClick(gi) }
}
@@ -41,7 +41,8 @@ abstract class SearchBarScene : ToolbarScene() {
inflater.inflate(R.layout.scene_searchbar, container, false) as ViewGroup
mToolbar = view.findViewById(R.id.toolbar)
mAppBarLayout = view.findViewById(R.id.appbar)
mAppBarLayout?.statusBarForeground = MaterialShapeDrawable.createWithElevationOverlay(context)
mAppBarLayout?.statusBarForeground =
MaterialShapeDrawable.createWithElevationOverlay(context)
mSearchView = view.findViewById(R.id.searchview)
mRecyclerView = view.findViewById(R.id.search_bar_list)
@@ -52,7 +53,7 @@ abstract class SearchBarScene : ToolbarScene() {
(mToolbar as SearchBar).text = mSearchView!!.text
mSearchView?.hide()
onApplySearch()
false
true
}
mSuggestionList = ArrayList()
mSuggestionAdapter = SuggestionAdapter(LayoutInflater.from(context))
@@ -219,7 +220,8 @@ abstract class SearchBarScene : ToolbarScene() {
}
inner class TagSuggestion constructor(var mHint: String, var mKeyword: String) : Suggestion() {
inner class TagSuggestion constructor(private var mHint: String, private var mKeyword: String) :
Suggestion() {
override fun getText(textView: TextView): CharSequence {
return if (textView.id == android.R.id.text1) {
@@ -113,7 +113,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
outState.putInt(KEY_REQUEST_ID, mRequestId);
}
@Nullable
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
@@ -280,9 +280,9 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
showProgress(true);
// Clean up for sign in
EhUtils.signOut(context);
EhUtils.signOut();
EhCallback callback = new SignInListener(context,
EhCallback<?, ?> callback = new SignInListener(context,
activity.getStageId(), getTag());
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
@@ -304,13 +304,16 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
hideSoftInput();
showProgress(true);
EhCallback callback = new GetProfileListener(context,
EhCallback<?, ?> callback = new GetProfileListener(context,
activity.getStageId(), getTag());
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_PROFILE)
.setCallback(callback);
EhApplication.getEhClient().execute(request);
EhApplication.getEhClient().execute(new EhRequest()
.setMethod(EhClient.METHOD_GET_UCONFIG));
}
private void redirectTo() {
@@ -65,7 +65,7 @@ public class WebViewSignInScene extends SolidScene {
Context context = getContext();
AssertUtils.assertNotNull(context);
EhUtils.signOut(context);
EhUtils.signOut();
// http://stackoverflow.com/questions/32284642/how-to-handle-an-uncatched-exception
CookieManager cookieManager = CookieManager.getInstance();
@@ -117,7 +117,7 @@ public class WebViewSignInScene extends SolidScene {
return cookies != null ? cookies : Collections.emptyList();
}
private void addCookie(Context context, String domain, Cookie cookie) {
private void addCookie(String domain, Cookie cookie) {
EhApplication.getEhCookieStore().addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
}
@@ -142,8 +142,8 @@ public class WebViewSignInScene extends SolidScene {
} else if (EhCookieStore.KEY_IPD_PASS_HASH.equals(cookie.name())) {
getHash = true;
}
addCookie(context, EhUrl.DOMAIN_EX, cookie);
addCookie(context, EhUrl.DOMAIN_E, cookie);
addCookie(EhUrl.DOMAIN_EX, cookie);
addCookie(EhUrl.DOMAIN_E, cookie);
}
if (getId && getHash) {
@@ -1,202 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.yorozuya.ViewUtils;
public class GalleryGuideView extends ViewGroup implements View.OnClickListener {
private final float[] mPoints = new float[3 * 4];
private int mBgColor;
private Paint mPaint;
private int mStep;
private TextView mLeftText;
private TextView mRightText;
private TextView mMenuText;
private TextView mProgressText;
private TextView mLongClickText;
public GalleryGuideView(Context context) {
super(context);
init(context);
}
public GalleryGuideView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public GalleryGuideView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mBgColor = Color.GRAY;
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(context.getResources().getDimension(R.dimen.gallery_guide_divider_width));
setOnClickListener(this);
setWillNotDraw(false);
bind();
}
private void bind() {
// Clear up
removeAllViews();
mLeftText = null;
mRightText = null;
mMenuText = null;
mProgressText = null;
mLongClickText = null;
switch (mStep) {
case 0:
bind1();
break;
default:
case 1:
bind2();
break;
}
}
private void bind1() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_gallery_guide_1, this);
mLeftText = (TextView) getChildAt(0);
mRightText = (TextView) getChildAt(1);
mMenuText = (TextView) getChildAt(2);
mProgressText = (TextView) getChildAt(3);
}
private void bind2() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_gallery_guide_2, this);
mLongClickText = (TextView) getChildAt(0);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (MeasureSpec.EXACTLY != widthMode || MeasureSpec.EXACTLY != heightMode) {
throw new IllegalStateException();
}
switch (mStep) {
case 0:
mLeftText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
mRightText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
mMenuText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize / 2, MeasureSpec.EXACTLY));
mProgressText.measure(MeasureSpec.makeMeasureSpec(widthSize / 3, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize / 2, MeasureSpec.EXACTLY));
break;
default:
case 1:
mLongClickText.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
break;
}
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int height = b - t;
switch (mStep) {
case 0:
mLeftText.layout(0, 0, width / 3, height);
mRightText.layout(width * 2 / 3, 0, width, height);
mMenuText.layout(width / 3, 0, width * 2 / 3, height / 2);
mProgressText.layout(width / 3, height / 2, width * 2 / 3, height);
break;
default:
case 1:
mLongClickText.layout(0, 0, width, height);
break;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (0 == mStep) {
mPoints[0] = w / 3;
mPoints[1] = 0;
mPoints[2] = w / 3;
mPoints[3] = h;
mPoints[4] = w * 2 / 3;
mPoints[5] = 0;
mPoints[6] = w * 2 / 3;
mPoints[7] = h;
mPoints[8] = w / 3;
mPoints[9] = h / 2;
mPoints[10] = w * 2 / 3;
mPoints[11] = h / 2;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(mBgColor);
if (0 == mStep) {
canvas.drawLines(mPoints, mPaint);
}
}
@Override
public void onClick(View v) {
switch (mStep) {
case 0:
mStep++;
bind();
break;
default:
case 1:
Settings.putGuideGallery(false);
ViewUtils.removeFromParent(this);
break;
}
}
}
@@ -1,210 +0,0 @@
/*
* Copyright 2019 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.view.DisplayCutoutCompat;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ObjectUtils;
public class GalleryHeader extends ViewGroup {
private final Rect batteryRect = new Rect();
private final Rect progressRect = new Rect();
private final Rect clockRect = new Rect();
private final int[] location = new int[2];
private DisplayCutoutCompat displayCutout;
private int topInsets = 0;
private View battery;
private View progress;
private View clock;
private int lastX = 0;
private int lastY = 0;
public GalleryHeader(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setDisplayCutout(@Nullable DisplayCutoutCompat displayCutout) {
if (!ObjectUtils.equal(this.displayCutout, displayCutout)) {
this.displayCutout = displayCutout;
requestLayout();
}
}
public void setTopInsets(int topInsets) {
if (this.topInsets != topInsets) {
this.topInsets = topInsets;
requestLayout();
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
battery = findViewById(R.id.battery);
progress = findViewById(R.id.progress);
clock = findViewById(R.id.clock);
}
private void measureChild(Rect rect, View view, int width, int paddingLeft, int paddingRight) {
int left;
MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
if (view == battery) {
left = paddingLeft + lp.leftMargin;
} else if (view == progress) {
left = paddingLeft + (width - paddingLeft - paddingRight) / 2 - view.getMeasuredWidth() / 2;
} else {
left = width - paddingRight - lp.rightMargin - view.getMeasuredWidth();
}
rect.set(left, lp.topMargin + topInsets, left + view.getMeasuredWidth(), lp.topMargin + topInsets + view.getMeasuredHeight());
}
@RequiresApi(api = Build.VERSION_CODES.P)
private boolean offsetVertically(Rect rect, View view, int width) {
int offset = 0;
measureChild(rect, view, width, 0, 0);
rect.offset(lastX, lastY);
for (Rect notch : displayCutout.getBoundingRects()) {
if (Rect.intersects(notch, rect)) {
offset = Math.max(offset, notch.bottom - lastY);
}
}
if (offset != 0) {
rect.offset(-lastX, -lastY);
rect.offset(0, offset);
return true;
} else {
return false;
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
private int getOffsetLeft(Rect rect, View view, int width) {
int offset = 0;
measureChild(rect, view, width, 0, 0);
rect.offset(lastX, lastY);
for (Rect notch : displayCutout.getBoundingRects()) {
if (Rect.intersects(notch, rect)) {
offset = Math.max(offset, notch.right - lastX);
}
}
return offset;
}
@RequiresApi(api = Build.VERSION_CODES.P)
private int getOffsetRight(Rect rect, View view, int width) {
int offset = 0;
measureChild(rect, view, width, 0, 0);
rect.offset(lastX, lastY);
for (Rect notch : displayCutout.getBoundingRects()) {
if (Rect.intersects(notch, rect)) {
offset = Math.max(offset, lastX + width - notch.left);
}
}
return offset;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
throw new IllegalStateException();
}
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
height = Math.max(height, child.getMeasuredHeight() + lp.topMargin + topInsets);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && displayCutout != null) {
// Check progress covered
if (offsetVertically(progressRect, progress, width)) {
offsetVertically(batteryRect, battery, width);
offsetVertically(clockRect, clock, width);
height = Math.max(progressRect.bottom, Math.max(batteryRect.bottom, clockRect.bottom));
} else {
// Clamp left and right
int paddingLeft = getOffsetLeft(batteryRect, battery, width);
int paddingRight = getOffsetRight(clockRect, clock, width);
measureChild(batteryRect, battery, width, paddingLeft, paddingRight);
measureChild(progressRect, progress, width, paddingLeft, paddingRight);
measureChild(clockRect, clock, width, paddingLeft, paddingRight);
}
} else {
measureChild(batteryRect, battery, width, 0, 0);
measureChild(progressRect, progress, width, 0, 0);
measureChild(clockRect, clock, width, 0, 0);
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
battery.layout(batteryRect.left, batteryRect.top, batteryRect.right, batteryRect.bottom);
progress.layout(progressRect.left, progressRect.top, progressRect.right, progressRect.bottom);
clock.layout(clockRect.left, clockRect.top, clockRect.right, clockRect.bottom);
getLocationOnScreen(location);
if (lastX != location[0] || lastY != location[1]) {
lastX = location[0];
lastY = location[1];
requestLayout();
}
}
@Override
public MarginLayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams;
}
@Override
protected MarginLayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
if (lp instanceof MarginLayoutParams) {
return new MarginLayoutParams((MarginLayoutParams) lp);
}
return new MarginLayoutParams(lp);
}
}
@@ -1,78 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.appcompat.widget.AppCompatSeekBar;
public class ReversibleSeekBar extends AppCompatSeekBar {
private boolean mReverse;
public ReversibleSeekBar(Context context) {
super(context);
}
public ReversibleSeekBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReversibleSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setReverse(boolean reverse) {
mReverse = reverse;
invalidate();
}
@Override
public void draw(Canvas canvas) {
boolean reverse = mReverse;
int saveCount = 0;
if (reverse) {
saveCount = canvas.save();
float px = this.getWidth() / 2.0f;
float py = this.getHeight() / 2.0f;
canvas.scale(-1, 1, px, py);
}
super.draw(canvas);
if (reverse) {
canvas.restoreToCount(saveCount);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean reverse = mReverse;
float x = 0.0f, y = 0.0f;
if (reverse) {
x = event.getX();
y = event.getY();
event.setLocation(getWidth() - x, y);
}
boolean result = super.onTouchEvent(event);
if (reverse) {
event.setLocation(x, y);
}
return result;
}
}
@@ -28,7 +28,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.annotation.IntDef
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DefaultItemAnimator
@@ -97,7 +96,7 @@ class SearchLayout @JvmOverloads constructor(
val mCategoryStored = mSharePref.getInt(SEARCH_CATEGORY_PREF, EhConfig.ALL_CATEGORY)
mCategoryGroup = mNormalView.findViewById(R.id.search_category_chipgroup)
for (mPair in mCategoryTable) {
val mChip = IdentifiedChip(context)
val mChip = inflate(context, R.layout.filter_chip, null) as IdentifiedChip
mChip.isCheckable = true
mChip.setText(mPair.second)
mChip.idt = mPair.first
@@ -328,17 +327,6 @@ class SearchLayout @JvmOverloads constructor(
}
}
inner class IdentifiedChip @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : Chip(
ContextThemeWrapper(
context,
com.google.android.material.R.style.Widget_Material3_Chip_Filter
), attrs
) {
var idt = 0
}
companion object {
const val SEARCH_MODE_NORMAL = 0
const val SEARCH_MODE_IMAGE = 1
@@ -368,4 +356,12 @@ class SearchLayout @JvmOverloads constructor(
)
)
}
}
class IdentifiedChip @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : Chip(
context, attrs
) {
var idt = 0
}
@@ -1,87 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ViewUtils;
public class SeekBarPanel extends LinearLayout {
private final int[] mLocation = new int[2];
private SeekBar mSeekBar;
public SeekBarPanel(Context context) {
super(context);
init();
}
public SeekBarPanel(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public SeekBarPanel(Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public SeekBarPanel(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
post(() -> {
var rootWindowInsets = getRootWindowInsets();
if (rootWindowInsets != null) {
setPadding(0, 0, 0, rootWindowInsets.getSystemWindowInsetBottom());
}
});
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSeekBar = (SeekBar) ViewUtils.$$(this, R.id.seek_bar);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (mSeekBar == null) {
return super.onTouchEvent(event);
} else {
ViewUtils.getLocationInAncestor(mSeekBar, mLocation, this);
final float offsetX = -mLocation[0];
final float offsetY = -mLocation[1];
event.offsetLocation(offsetX, offsetY);
mSeekBar.onTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
return true;
}
}
}
@@ -1,70 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glgallery;
import android.view.MotionEvent;
class DownUpDetector {
private final DownUpListener mListener;
private boolean mStillDown;
public DownUpDetector(DownUpListener listener) {
mListener = listener;
}
private void setState(boolean down, MotionEvent e) {
if (down == mStillDown) return;
mStillDown = down;
if (down) {
mListener.onDown(e);
} else {
mListener.onUp(e);
}
}
public void onTouchEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
setState(true, ev);
break;
case MotionEvent.ACTION_POINTER_DOWN:
mListener.onPointerDown(ev);
break;
case MotionEvent.ACTION_UP:
mListener.onPointerUp(ev);
setState(false, ev);
break;
case MotionEvent.ACTION_CANCEL:
setState(false, ev);
break;
}
}
public boolean isDown() {
return mStillDown;
}
public interface DownUpListener {
void onDown(MotionEvent e);
void onUp(MotionEvent e);
void onPointerDown(MotionEvent e);
void onPointerUp(MotionEvent e);
}
}
@@ -1,137 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glgallery;
import android.content.Context;
import android.hardware.SensorManager;
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import com.hippo.glview.anim.Animation;
abstract class Fling extends Animation {
private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
private static final float START_TENSION = 0.5f;
private static final float END_TENSION = 1.0f;
private static final float P1 = START_TENSION * INFLEXION;
private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
private static final float FLING_FRICTION = ViewConfiguration.getScrollFriction();
private static final int NB_SAMPLES = 100;
private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
private static final Interpolator FLING_INTERPOLATOR = new Interpolator() {
@Override
public float getInterpolation(float input) {
final int index = (int) (NB_SAMPLES * input);
float distanceCoef = 1.f;
float velocityCoef;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (input - t_inf) * velocityCoef;
}
return distanceCoef;
}
};
private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
static {
float x_min = 0.0f;
float y_min = 0.0f;
for (int i = 0; i < NB_SAMPLES; i++) {
final float alpha = (float) i / NB_SAMPLES;
float x_max = 1.0f;
float x, tx, coef;
while (true) {
x = x_min + (x_max - x_min) / 2.0f;
coef = 3.0f * x * (1.0f - x);
tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
if (Math.abs(tx - alpha) < 1E-5) break;
if (tx > alpha) x_max = x;
else x_min = x;
}
SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
float y_max = 1.0f;
float y, dy;
while (true) {
y = y_min + (y_max - y_min) / 2.0f;
coef = 3.0f * y * (1.0f - y);
dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
if (Math.abs(dy - alpha) < 1E-5) break;
if (dy > alpha) y_max = y;
else y_min = y;
}
SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
}
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
}
private final float mPhysicalCoeff;
public Fling(Context context) {
final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* ppi
* 0.84f; // look and feel tuning
setInterpolator(FLING_INTERPOLATOR);
}
private double getSplineDeceleration(int velocity) {
return Math.log(INFLEXION * Math.abs(velocity) / (FLING_FRICTION * mPhysicalCoeff));
}
/* Returns the duration, expressed in milliseconds */
protected int getSplineFlingDuration(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return (int) (1000.0 * Math.exp(l / decelMinusOne));
}
protected double getSplineFlingDistance(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return FLING_FRICTION * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
/**
* Modifies mDuration to the duration it takes to get from start to newFinal using the
* spline interpolation. The previous duration was needed to get to oldFinal.
**/
protected int adjustDuration(int start, int oldFinal, int newFinal, int duration) {
final int oldDistance = oldFinal - start;
final int newDistance = newFinal - start;
final float x = Math.abs((float) newDistance / oldDistance);
final int index = (int) (NB_SAMPLES * x);
if (index < NB_SAMPLES) {
final float x_inf = (float) index / NB_SAMPLES;
final float x_sup = (float) (index + 1) / NB_SAMPLES;
final float t_inf = SPLINE_TIME[index];
final float t_sup = SPLINE_TIME[index + 1];
final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
duration *= timeCoef;
}
return duration;
}
}
@@ -1,197 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glgallery;
import com.hippo.glview.glrenderer.BasicTexture;
import com.hippo.glview.glrenderer.Texture;
import com.hippo.glview.image.GLImageMovableTextView;
import com.hippo.glview.image.ImageMovableTextTexture;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.view.Gravity;
import com.hippo.glview.widget.GLFrameLayout;
import com.hippo.glview.widget.GLLinearLayout;
import com.hippo.glview.widget.GLProgressView;
import com.hippo.glview.widget.GLTextureView;
public class GalleryPageView extends GLFrameLayout {
public static final int INVALID_INDEX = -1;
public static final float PROGRESS_GONE = -1.0f;
public static final float PROGRESS_INDETERMINATE = -2.0f;
private final ImageView mImage;
private final GLLinearLayout mInfo;
private final GLImageMovableTextView mPage;
private final GLTextureView mError;
private final GLProgressView mProgress;
private final int mMinHeight;
private int mIndex = INVALID_INDEX;
public GalleryPageView(ImageMovableTextTexture pageTextTexture,
int progressColor, int progressBgColor, int progressSize,
int minHeight, int infoInterval) {
// Add image
mImage = new ImageView();
GravityLayoutParams glp = new GravityLayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
addComponent(mImage, glp);
// Add other panel
mInfo = new GLLinearLayout();
mInfo.setOrientation(GLLinearLayout.VERTICAL);
mInfo.setInterval(infoInterval);
glp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
glp.gravity = Gravity.CENTER;
addComponent(mInfo, glp);
// Add page
mPage = new GLImageMovableTextView();
mPage.setTextTexture(pageTextTexture);
GLLinearLayout.LayoutParams lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mPage, lp);
// Add error
mError = new GLTextureView();
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mError, lp);
// Add progress
mProgress = new GLProgressView();
mProgress.setBgColor(progressBgColor);
mProgress.setColor(progressColor);
mProgress.setMinimumWidth(progressSize);
mProgress.setMinimumHeight(progressSize);
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mProgress, lp);
mMinHeight = minHeight;
}
@Override
protected int getSuggestedMinimumHeight() {
// The height of the actual image may be smaller than mPageMinHeight.
// Set min height as 0 when the image is visible.
// For PageLayoutManager, min height is useless.
if (mImage.getVisibility() == VISIBLE) {
return 0;
} else {
return mMinHeight;
}
}
int getIndex() {
return mIndex;
}
void setIndex(int index) {
mIndex = index;
}
public void showImage() {
mImage.setVisibility(VISIBLE);
mInfo.setVisibility(GONE);
}
public void showInfo() {
// For image valid rect
mImage.setVisibility(INVISIBLE);
mInfo.setVisibility(VISIBLE);
}
private void unbindImage() {
ImageTexture texture = mImage.getImageTexture();
if (texture != null) {
mImage.setImageTexture(null);
texture.recycle();
}
}
public void setImage(ImageTexture imageTexture) {
unbindImage();
if (imageTexture != null) {
mImage.setImageTexture(imageTexture);
}
}
public void setPage(int page) {
mPage.setVisibility(VISIBLE);
mPage.setText(Integer.toString(page));
}
public void hidePage() {
mPage.setVisibility(GONE);
}
public void setProgress(float progress) {
if (progress == PROGRESS_GONE) {
mProgress.setVisibility(GONE);
} else if (progress == PROGRESS_INDETERMINATE) {
mProgress.setVisibility(VISIBLE);
mProgress.setIndeterminate(true);
} else {
mProgress.setVisibility(VISIBLE);
mProgress.setIndeterminate(false);
mProgress.setProgress(progress);
}
}
private void unbindError() {
Texture texture = mError.getTexture();
if (texture != null) {
mError.setTexture(null);
if (texture instanceof BasicTexture) {
((BasicTexture) texture).recycle();
}
}
}
public void setError(String error, GalleryView galleryView) {
unbindError();
if (error == null) {
mError.setVisibility(GONE);
} else {
mError.setVisibility(VISIBLE);
galleryView.bindErrorView(mError, error);
}
}
ImageView getImageView() {
return mImage;
}
boolean isLoaded() {
return mImage.getVisibility() == VISIBLE;
}
boolean isError() {
return mError.getVisibility() == VISIBLE;
}
boolean isUnderInfo(float x, float y) {
return mInfo.bounds().contains((int) x, (int) y);
}
}
@@ -1,264 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glgallery;
import android.util.LruCache;
import androidx.annotation.IntDef;
import androidx.annotation.UiThread;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.image.ImageWrapper;
import com.hippo.glview.view.GLRoot;
import com.hippo.image.Image;
import com.hippo.yorozuya.ConcurrentPool;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.OSUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public abstract class GalleryProvider {
public static final int STATE_WAIT = -1;
public static final int STATE_ERROR = -2;
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
private final ImageCache mImageCache = new ImageCache();
private volatile Listener mListener;
private volatile GLRoot mGLRoot;
private boolean mStarted = false;
@UiThread
public void start() {
OSUtils.checkMainLoop();
if (mStarted) {
throw new IllegalStateException("Can't start it twice");
}
mStarted = true;
}
@UiThread
public void stop() {
OSUtils.checkMainLoop();
mImageCache.evictAll();
}
public void setGLRoot(GLRoot glRoot) {
mGLRoot = glRoot;
}
/**
* @return {@link #STATE_WAIT} for wait,
* {@link #STATE_ERROR} for error, {@link #getError()} to get error message,
* 0 for empty
*/
public abstract int size();
public final void request(int index) {
ImageWrapper imageWrapper = mImageCache.get(index);
if (imageWrapper != null) {
notifyPageSucceed(index, imageWrapper);
} else {
onRequest(index);
}
}
public final void forceRequest(int index) {
onForceRequest(index);
}
public void removeCache(int index) {
mImageCache.remove(index);
}
protected abstract void onRequest(int index);
protected abstract void onForceRequest(int index);
public final void cancelRequest(int index) {
onCancelRequest(index);
}
protected abstract void onCancelRequest(int index);
public abstract String getError();
public void setListener(Listener listener) {
mListener = listener;
}
public void notifyDataChanged() {
notify(NotifyTask.TYPE_DATA_CHANGED, -1, 0.0f, null, null);
}
public void notifyDataChanged(int index) {
notify(NotifyTask.TYPE_DATA_CHANGED, index, 0.0f, null, null);
}
public void notifyPageWait(int index) {
notify(NotifyTask.TYPE_WAIT, index, 0.0f, null, null);
}
public void notifyPagePercent(int index, float percent) {
notify(NotifyTask.TYPE_PERCENT, index, percent, null, null);
}
public void notifyPageSucceed(int index, Image image) {
ImageWrapper imageWrapper = new ImageWrapper(image);
mImageCache.add(index, imageWrapper);
notifyPageSucceed(index, imageWrapper);
}
public void notifyPageSucceed(int index, ImageWrapper image) {
notify(NotifyTask.TYPE_SUCCEED, index, 0.0f, image, null);
}
public void notifyPageFailed(int index, String error) {
notify(NotifyTask.TYPE_FAILED, index, 0.0f, null, error);
}
private void notify(@NotifyTask.Type int type, int index, float percent, ImageWrapper image, String error) {
Listener listener = mListener;
if (listener == null) {
return;
}
GLRoot glRoot = mGLRoot;
if (glRoot == null) {
return;
}
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask(listener, mNotifyTaskPool);
}
task.setData(type, index, percent, image, error);
glRoot.addOnGLIdleListener(task);
}
public interface Listener {
void onDataChanged();
void onPageWait(int index);
void onPagePercent(int index, float percent);
void onPageSucceed(int index, ImageWrapper image);
void onPageFailed(int index, String error);
void onDataChanged(int index);
}
private static class NotifyTask implements GLRoot.OnGLIdleListener {
public static final int TYPE_DATA_CHANGED = 0;
public static final int TYPE_WAIT = 1;
public static final int TYPE_PERCENT = 2;
public static final int TYPE_SUCCEED = 3;
public static final int TYPE_FAILED = 4;
private final Listener mListener;
private final ConcurrentPool<NotifyTask> mPool;
@Type
private int mType;
private int mIndex;
private float mPercent;
private ImageWrapper mImage;
private String mError;
public NotifyTask(Listener listener, ConcurrentPool<NotifyTask> pool) {
mListener = listener;
mPool = pool;
}
public void setData(@Type int type, int index, float percent, ImageWrapper image, String error) {
mType = type;
mIndex = index;
mPercent = percent;
mImage = image;
mError = error;
}
@Override
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
switch (mType) {
case TYPE_DATA_CHANGED:
if (mIndex < 0) {
mListener.onDataChanged();
} else {
mListener.onDataChanged(mIndex);
}
break;
case TYPE_WAIT:
mListener.onPageWait(mIndex);
break;
case TYPE_PERCENT:
mListener.onPagePercent(mIndex, mPercent);
break;
case TYPE_SUCCEED:
mListener.onPageSucceed(mIndex, mImage);
break;
case TYPE_FAILED:
mListener.onPageFailed(mIndex, mError);
break;
}
// Clean data
mImage = null;
mError = null;
// Push back
mPool.push(this);
return false;
}
@IntDef({TYPE_DATA_CHANGED, NotifyTask.TYPE_WAIT, TYPE_PERCENT, TYPE_SUCCEED, TYPE_FAILED})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {
}
}
private static class ImageCache extends LruCache<Integer, ImageWrapper> {
private static final int CACHE_SIZE = 512 * 1024 * 1024;
public ImageCache() {
super(CACHE_SIZE);
}
public void add(Integer key, ImageWrapper value) {
if (!value.getAnimated())
if (value.obtain())
put(key, value);
}
@Override
protected int sizeOf(Integer key, ImageWrapper value) {
return value.getWidth() * value.getHeight() * 4;
}
@Override
protected void entryRemoved(boolean evicted, Integer key, ImageWrapper oldValue, ImageWrapper newValue) {
if (oldValue != null) {
oldValue.release();
}
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,186 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glgallery;
import android.content.Context;
import android.os.SystemClock;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import androidx.core.view.GestureDetectorCompat;
// This class aggregates three gesture detectors: GestureDetector,
// ScaleGestureDetector, and DownUpDetector.
class GestureRecognizer {
@SuppressWarnings("unused")
private static final String TAG = "GestureRecognizer";
private final GestureDetectorCompat mGestureDetector;
private final ScaleGestureDetector mScaleDetector;
private final DownUpDetector mDownUpDetector;
private final Listener mListener;
public GestureRecognizer(Context context, Listener listener) {
mListener = listener;
MyGestureListener gestureListener = new MyGestureListener();
mGestureDetector = new GestureDetectorCompat(context, gestureListener,
null /* ignoreMultitouch */);
mGestureDetector.setOnDoubleTapListener(gestureListener);
mScaleDetector = new ScaleGestureDetector(
context, new MyScaleListener());
mDownUpDetector = new DownUpDetector(new MyDownUpListener());
}
public void onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
mDownUpDetector.onTouchEvent(event);
}
public boolean isDown() {
return mDownUpDetector.isDown();
}
public void cancelScale() {
long now = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mScaleDetector.onTouchEvent(cancelEvent);
cancelEvent.recycle();
}
public interface Listener {
boolean onSingleTapUp(float x, float y);
boolean onSingleTapConfirmed(float x, float y);
boolean onDoubleTap(float x, float y);
boolean onDoubleTapConfirmed(float x, float y);
void onLongPress(float x, float y);
boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y);
/**
* @param velocityX Finger from top to bottom is positive
* @param velocityY Finger from left to right is positive
*/
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
boolean onScaleBegin(float focusX, float focusY);
boolean onScale(float focusX, float focusY, float scale);
void onScaleEnd();
void onDown(float x, float y);
void onUp();
void onPointerDown(float x, float y);
void onPointerUp();
}
private class MyGestureListener
extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return mListener.onSingleTapUp(e.getX(), e.getY());
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return mListener.onSingleTapConfirmed(e.getX(), e.getY());
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
return mListener.onDoubleTapConfirmed(e.getX(), e.getY());
} else {
return true;
}
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return mListener.onDoubleTap(e.getX(), e.getY());
}
@Override
public void onLongPress(MotionEvent e) {
mListener.onLongPress(e.getX(), e.getY());
}
@Override
public boolean onScroll(
MotionEvent e1, MotionEvent e2, float dx, float dy) {
return mListener.onScroll(
dx, dy, e2.getX() - e1.getX(), e2.getY() - e1.getY(), e2.getX(), e2.getY());
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return mListener.onFling(e1, e2, velocityX, velocityY);
}
}
private class MyScaleListener
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return mListener.onScaleBegin(
detector.getFocusX(), detector.getFocusY());
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
return mListener.onScale(detector.getFocusX(),
detector.getFocusY(), detector.getScaleFactor());
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
mListener.onScaleEnd();
}
}
private class MyDownUpListener implements DownUpDetector.DownUpListener {
@Override
public void onDown(MotionEvent e) {
mListener.onDown(e.getX(), e.getY());
}
@Override
public void onUp(MotionEvent e) {
mListener.onUp();
}
@Override
public void onPointerDown(MotionEvent e) {
mListener.onPointerDown(e.getX(), e.getY());
}
@Override
public void onPointerUp(MotionEvent e) {
mListener.onPointerUp();
}
}
}
@@ -1,597 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glgallery;
import android.graphics.Rect;
import android.graphics.RectF;
import com.hippo.glview.anim.AlphaAnimation;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.glrenderer.Texture;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.view.GLView;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.MathUtils;
import java.util.Arrays;
class ImageView extends GLView implements ImageTexture.Callback {
public static final int SCALE_ORIGIN = 0;
public static final int SCALE_FIT_WIDTH = 1;
public static final int SCALE_FIT_HEIGHT = 2;
public static final int SCALE_FIT = 3;
public static final int SCALE_FIXED = 4;
public static final int START_POSITION_TOP_LEFT = 0;
public static final int START_POSITION_TOP_RIGHT = 1;
public static final int START_POSITION_BOTTOM_LEFT = 2;
public static final int START_POSITION_BOTTOM_RIGHT = 3;
public static final int START_POSITION_CENTER = 4;
// TODO adjust scale max and min according to image size and screen size
private static final float SCALE_MIN = 1 / 10.0f;
private static final float SCALE_MAX = 10.0f;
private static final long ALPHA_ANIMATION_DURING = 300L;
private final RectF mDst = new RectF();
private final RectF mSrcActual = new RectF();
private final RectF mDstActual = new RectF();
private final Rect mValidRect = new Rect();
private final AlphaAnimation mAlphaAnimation;
private ImageTexture mImageTexture;
private int mTextureWidth;
private int mTextureHeight;
private int mScaleMode = SCALE_FIT;
private int mStartPosition = START_POSITION_TOP_RIGHT;
private float mScaleValue = 1.0f;
private float mScale = 1.0f;
private boolean mScaleOffsetDirty = true;
private boolean mPositionInRootDirty = true;
public ImageView() {
mAlphaAnimation = new AlphaAnimation(0.0f, 1.0f);
mAlphaAnimation.setDuration(ALPHA_ANIMATION_DURING);
mAlphaAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
}
public static int sanitizeScaleMode(int scaleMode) {
if (scaleMode != SCALE_ORIGIN &&
scaleMode != SCALE_FIT_WIDTH &&
scaleMode != SCALE_FIT_HEIGHT &&
scaleMode != SCALE_FIT &&
scaleMode != SCALE_FIXED) {
return SCALE_FIT;
} else {
return scaleMode;
}
}
public static int sanitizeStartPosition(int startPosition) {
if (startPosition != START_POSITION_TOP_LEFT &&
startPosition != START_POSITION_TOP_RIGHT &&
startPosition != START_POSITION_BOTTOM_LEFT &&
startPosition != START_POSITION_BOTTOM_RIGHT &&
startPosition != START_POSITION_CENTER) {
return START_POSITION_TOP_RIGHT;
} else {
return startPosition;
}
}
@Override
protected int getSuggestedMinimumWidth() {
return Math.max(super.getSuggestedMinimumWidth(),
mImageTexture == null ? 0 : mTextureWidth);
}
@Override
protected int getSuggestedMinimumHeight() {
return Math.max(super.getSuggestedMinimumHeight(),
mImageTexture == null ? 0 : mTextureHeight);
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mImageTexture == null) {
super.onMeasure(widthSpec, heightSpec);
} else {
float ratio = (float) mTextureWidth / mTextureHeight;
int widthSize = MeasureSpec.getSize(widthSpec);
int heightSize = MeasureSpec.getSize(heightSpec);
int widthMode = MeasureSpec.getMode(widthSpec);
int heightMode = MeasureSpec.getMode(heightSpec);
int measureWidth = -1;
int measureHeight = -1;
if (widthMode == MeasureSpec.EXACTLY) {
measureWidth = widthSize;
if (heightMode == MeasureSpec.EXACTLY) {
measureHeight = heightSize;
} else {
measureHeight = (int) (widthSize / ratio);
if (heightMode == MeasureSpec.AT_MOST) {
measureHeight = Math.min(measureHeight, heightSize);
}
}
} else if (heightMode == MeasureSpec.EXACTLY) {
measureHeight = heightSize;
measureWidth = (int) (heightSize * ratio);
if (widthMode == MeasureSpec.AT_MOST) {
measureWidth = Math.min(measureWidth, widthSize);
}
}
if (measureWidth == -1 || measureHeight == -1) {
super.onMeasure(widthSpec, heightSpec);
} else {
setMeasuredSize(measureWidth, measureHeight);
}
}
}
@Override
protected void onSizeChanged(int newW, int newH, int oldW, int oldH) {
mScaleOffsetDirty = true;
mPositionInRootDirty = true;
}
@Override
protected void onPositionInRootChanged(int x, int y, int oldX, int oldY) {
mPositionInRootDirty = true;
if (mImageTexture != null) {
getValidRect(mValidRect);
if (!mValidRect.isEmpty()) {
mImageTexture.start();
} else {
mImageTexture.stop();
}
}
}
public void getScaleDefault(float[] scaleDefault) {
if (mImageTexture == null) {
return;
}
scaleDefault[0] = 1.0f;
scaleDefault[1] = (float) getWidth() / mTextureWidth;
scaleDefault[2] = (float) getHeight() / mTextureHeight;
scaleDefault[3] = Math.max(scaleDefault[1], scaleDefault[2]) * 2;
scaleDefault[0] = MathUtils.clamp(scaleDefault[0], SCALE_MIN, SCALE_MAX);
scaleDefault[1] = MathUtils.clamp(scaleDefault[1], SCALE_MIN, SCALE_MAX);
scaleDefault[2] = MathUtils.clamp(scaleDefault[2], SCALE_MIN, SCALE_MAX);
scaleDefault[3] = MathUtils.clamp(scaleDefault[3], SCALE_MIN, SCALE_MAX);
Arrays.sort(scaleDefault);
}
public ImageTexture getImageTexture() {
return mImageTexture;
}
public void setImageTexture(ImageTexture imageTexture) {
// Remove callback
if (mImageTexture != null) {
mImageTexture.setCallback(null);
mImageTexture.stop();
}
int oldTextureWidth = mTextureWidth;
int oldTextureHeight = mTextureHeight;
mImageTexture = imageTexture;
if (imageTexture != null) {
imageTexture.setCallback(this);
mTextureWidth = imageTexture.getWidth();
mTextureHeight = imageTexture.getHeight();
// Avoid zero and negative
if (mTextureWidth <= 0) {
mTextureWidth = 1;
}
if (mTextureHeight <= 0) {
mTextureHeight = 1;
}
// Start alpha animation, do not show animation for image has no valid rect
getValidRect(mValidRect);
if (!mValidRect.isEmpty()) {
startAnimation(mAlphaAnimation, true);
mImageTexture.start();
}
} else {
mTextureWidth = 1;
mTextureHeight = 1;
}
mScaleOffsetDirty = true;
mPositionInRootDirty = true;
if (oldTextureWidth != mTextureWidth || oldTextureHeight != mTextureHeight) {
requestLayout();
}
}
public boolean isLoaded() {
return mImageTexture != null;
}
public boolean canFlingVertically() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.top < 0.0f || mDst.bottom > getHeight();
}
public boolean canFlingHorizontally() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.left < 0.0f || mDst.right > getWidth();
}
public boolean canFling() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.left < 0.0f || mDst.top < 0.0f || mDst.right > getWidth() || mDst.bottom > getHeight();
}
public int getMaxDx() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.max(0, -(int) mDst.left);
}
public int getMinDx() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.min(0, getWidth() - (int) mDst.right);
}
public int getMaxDy() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.max(0, -(int) mDst.top);
}
public int getMinDy() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.min(0, getHeight() - (int) mDst.bottom);
}
public float getScale() {
return mScale;
}
/**
* If target is shorter then screen, make it in screen center. If target is
* longer then parent, make sure target fill parent over
*/
private void adjustPosition() {
RectF dst = mDst;
int screenWidth = getWidth();
int screenHeight = getHeight();
float targetWidth = dst.width();
float targetHeight = dst.height();
if (targetWidth > screenWidth) {
float fixXOffset = dst.left;
if (fixXOffset > 0) {
dst.left -= fixXOffset;
dst.right -= fixXOffset;
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
dst.left += fixXOffset;
dst.right += fixXOffset;
}
} else {
float left = (screenWidth - targetWidth) / 2;
dst.offsetTo(left, dst.top);
}
if (targetHeight > screenHeight) {
float fixYOffset = dst.top;
if (fixYOffset > 0) {
dst.top -= fixYOffset;
dst.bottom -= fixYOffset;
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
dst.top += fixYOffset;
dst.bottom += fixYOffset;
}
} else {
float top = (screenHeight - targetHeight) / 2;
dst.offsetTo(dst.left, top);
}
}
public void setScaleOffset(int scaleMode, int startPosition, float scaleValue) {
mScaleMode = scaleMode;
mStartPosition = startPosition;
mScaleValue = scaleValue;
int screenWidth = getWidth();
int screenHeight = getHeight();
if (mImageTexture == null || screenWidth == 0 || screenHeight == 0) {
mScaleOffsetDirty = true;
return;
}
int textureWidth = mTextureWidth;
int textureHeight = mTextureHeight;
// Set scale
float targetWidth;
float targetHeight;
switch (scaleMode) {
case SCALE_ORIGIN:
mScale = 1.0f;
targetWidth = textureWidth;
targetHeight = textureHeight;
break;
case SCALE_FIT_WIDTH:
mScale = (float) screenWidth / textureWidth;
targetWidth = screenWidth;
targetHeight = textureHeight * mScale;
break;
case SCALE_FIT_HEIGHT:
mScale = (float) screenHeight / textureHeight;
targetWidth = textureWidth * mScale;
targetHeight = screenHeight;
break;
case SCALE_FIT:
float scaleX = (float) screenWidth / textureWidth;
float scaleY = (float) screenHeight / textureHeight;
if (scaleX < scaleY) {
mScale = scaleX;
targetWidth = screenWidth;
targetHeight = textureHeight * scaleX;
} else {
mScale = scaleY;
targetWidth = textureWidth * scaleY;
targetHeight = screenHeight;
break;
}
break;
case SCALE_FIXED:
default:
mScale = scaleValue;
targetWidth = textureWidth * scaleValue;
targetHeight = textureHeight * scaleValue;
break;
}
// adjust scale, not too big, not too small
if (mScale < SCALE_MIN) {
mScale = SCALE_MIN;
targetWidth = textureWidth * SCALE_MIN;
targetHeight = textureHeight * SCALE_MIN;
} else if (mScale > SCALE_MAX) {
mScale = SCALE_MAX;
targetWidth = textureWidth * SCALE_MAX;
targetHeight = textureHeight * SCALE_MAX;
}
// Set mDst.left and mDst.right
RectF dst = mDst;
switch (startPosition) {
case START_POSITION_TOP_LEFT:
dst.left = 0;
dst.top = 0;
break;
case START_POSITION_TOP_RIGHT:
dst.left = screenWidth - targetWidth;
dst.top = 0;
break;
case START_POSITION_BOTTOM_LEFT:
dst.left = 0;
dst.top = screenHeight - targetHeight;
break;
case START_POSITION_BOTTOM_RIGHT:
dst.left = screenWidth - targetWidth;
dst.top = screenHeight - targetHeight;
break;
case START_POSITION_CENTER:
default:
dst.left = (screenWidth - targetWidth) / 2;
dst.top = (screenHeight - targetHeight) / 2;
break;
}
// Set mDst.right and mDst.bottom
dst.right = dst.left + targetWidth;
dst.bottom = dst.top + targetHeight;
// adjust position
adjustPosition();
mScaleOffsetDirty = false;
mPositionInRootDirty = true;
}
public void scroll(int dx, int dy, int[] remain) {
// Only work after layout
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mScaleOffsetDirty) {
remain[0] = dx;
remain[1] = dy;
return;
}
RectF dst = mDst;
int screenWidth = getWidth();
int screenHeight = getHeight();
float targetWidth = dst.width();
float targetHeight = dst.height();
if (targetWidth > screenWidth) {
dst.left -= dx;
dst.right -= dx;
float fixXOffset = dst.left;
if (fixXOffset > 0) {
dst.left -= fixXOffset;
dst.right -= fixXOffset;
remain[0] = -(int) fixXOffset;
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
dst.left += fixXOffset;
dst.right += fixXOffset;
remain[0] = (int) fixXOffset;
} else {
remain[0] = 0;
}
} else {
remain[0] = dx;
}
if (targetHeight > screenHeight) {
dst.top -= dy;
dst.bottom -= dy;
float fixYOffset = dst.top;
if (fixYOffset > 0) {
dst.top -= fixYOffset;
dst.bottom -= fixYOffset;
remain[1] = -(int) fixYOffset;
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
dst.top += fixYOffset;
dst.bottom += fixYOffset;
remain[1] = (int) fixYOffset;
} else {
remain[1] = 0;
}
} else {
remain[1] = dy;
}
if (dx != remain[0] || dy != remain[1]) {
mPositionInRootDirty = true;
invalidate();
}
}
public void scale(float focusX, float focusY, float scale) {
// Only work after layout
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mScaleOffsetDirty) {
return;
}
if ((mScale == SCALE_MAX && scale >= 1.0f) || (mScale == SCALE_MIN && scale < 1.0f)) {
return;
}
float newScale = mScale * scale;
newScale = MathUtils.clamp(newScale, SCALE_MIN, SCALE_MAX);
mScale = newScale;
RectF dst = mDst;
float left = (focusX - ((focusX - dst.left) * scale));
float top = (focusY - ((focusY - dst.top) * scale));
dst.set(left, top,
(left + (mImageTexture.getWidth() * newScale)),
(top + (mImageTexture.getHeight() * newScale)));
// adjust position
adjustPosition();
mPositionInRootDirty = true;
invalidate();
}
private void applyPositionInRoot() {
int width = mImageTexture.getWidth();
int height = mImageTexture.getHeight();
RectF dst = mDst;
RectF dstActual = mDstActual;
RectF srcActual = mSrcActual;
dstActual.set(dst);
getValidRect(mValidRect);
if (dstActual.intersect(mValidRect.left, mValidRect.top, mValidRect.right, mValidRect.bottom)) {
srcActual.left = MathUtils.lerp(0, width,
MathUtils.delerp(dst.left, dst.right, dstActual.left));
srcActual.right = MathUtils.lerp(0, width,
MathUtils.delerp(dst.left, dst.right, dstActual.right));
srcActual.top = MathUtils.lerp(0, height,
MathUtils.delerp(dst.top, dst.bottom, dstActual.top));
srcActual.bottom = MathUtils.lerp(0, height,
MathUtils.delerp(dst.top, dst.bottom, dstActual.bottom));
} else {
// Can't be seen, set src and dst empty
srcActual.setEmpty();
dstActual.setEmpty();
}
mPositionInRootDirty = false;
}
@Override
public void onRender(GLCanvas canvas) {
Texture texture = mImageTexture;
if (texture == null) {
return;
}
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mPositionInRootDirty) {
applyPositionInRoot();
}
if (!mSrcActual.isEmpty()) {
texture.draw(canvas, mSrcActual, mDstActual);
}
}
@Override
public void invalidateImageTexture(ImageTexture who) {
invalidate();
}
}
@@ -1,963 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glgallery;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
import android.view.animation.Interpolator;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import com.hippo.glview.anim.Animation;
import com.hippo.glview.view.GLView;
import com.hippo.glview.widget.GLProgressView;
import com.hippo.glview.widget.GLTextureView;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.MathUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
class PagerLayoutManager extends GalleryView.LayoutManager {
public static final int MODE_LEFT_TO_RIGHT = 0;
public static final int MODE_RIGHT_TO_LEFT = 1;
private static final String TAG = PagerLayoutManager.class.getSimpleName();
private static final Interpolator SMOOTH_SCROLLER_INTERPOLATOR = new Interpolator() {
@Override
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
};
private final int[] mScrollRemain = new int[2];
private final float[] mScaleDefault = new float[4];
private final SmoothScroller mSmoothScroller;
private final PageFling mPageFling;
private final SmoothScaler mSmoothScaler;
private final Rect mTempRect = new Rect();
private GalleryView.Adapter mAdapter;
private GLProgressView mProgress;
private String mErrorStr;
private GLTextureView mErrorView;
private GalleryPageView mPrevious;
private GalleryPageView mCurrent;
private GalleryPageView mNext;
@Mode
private int mMode = MODE_RIGHT_TO_LEFT;
private int mScaleMode;
private int mStartPosition;
private float mScaleValue;
private int mOffset;
private int mDeltaX;
private int mDeltaY;
private boolean mCanScrollBetweenPages = false;
private boolean mStopAnimationFinger;
private int mInterval;
// Current index
private int mIndex;
public PagerLayoutManager(Context context, @NonNull GalleryView galleryView,
int scaleMode, int startPoint, float scaleValue, int interval) {
super(galleryView);
mScaleMode = scaleMode;
mStartPosition = startPoint;
mScaleValue = scaleValue;
mInterval = interval;
mSmoothScroller = new SmoothScroller();
mPageFling = new PageFling(context);
mSmoothScaler = new SmoothScaler();
}
public void setInterval(int interval) {
if (mInterval == interval) {
return;
}
if (mAdapter != null) {
int index = getInternalCurrentIndex();
GalleryView.Adapter adapter = onDetach();
mInterval = interval;
onAttach(adapter);
setCurrentIndex(index);
mGalleryView.requestFill();
} else {
mInterval = interval;
}
}
private void resetParameters() {
mOffset = 0;
mDeltaX = 0;
mDeltaY = 0;
mCanScrollBetweenPages = false;
mStopAnimationFinger = false;
}
private boolean cancelAllAnimations() {
boolean running = mSmoothScroller.isRunning() ||
mPageFling.isRunning() ||
mSmoothScaler.isRunning();
mSmoothScroller.cancel();
mPageFling.cancel();
mSmoothScaler.cancel();
return running;
}
public void setMode(@Mode int mode) {
if (mMode == mode) {
return;
}
mMode = mode;
if (mAdapter != null) {
// It is attached, refill
// Cancel all animations
cancelAllAnimations();
// Remove all view
removeProgress();
removeErrorView();
removeAllPages();
// Reset parameters
resetParameters();
// Request fill
mGalleryView.requestFill();
}
}
public void setScaleMode(int scaleMode) {
if (mScaleMode == scaleMode) {
return;
}
mScaleMode = scaleMode;
if (mCurrent != null) {
mCurrent.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mPrevious != null) {
mPrevious.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mNext != null) {
mNext.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
}
public void setStartPosition(int startPosition) {
if (mStartPosition == startPosition) {
return;
}
mStartPosition = startPosition;
if (mCurrent != null) {
mCurrent.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mPrevious != null) {
mPrevious.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mNext != null) {
mNext.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
}
@Override
public void onAttach(GalleryView.Adapter adapter) {
AssertUtils.assertNull("The PagerLayoutManager is attached", mAdapter);
AssertUtils.assertNotNull("The adapter is null", adapter);
mAdapter = adapter;
// Reset parameters
resetParameters();
}
private void removeProgress() {
if (mProgress != null) {
mGalleryView.removeComponent(mProgress);
mGalleryView.releaseProgress(mProgress);
mProgress = null;
}
}
private void removeErrorView() {
if (mErrorView != null) {
mGalleryView.removeComponent(mErrorView);
mGalleryView.releaseErrorView(mErrorView);
mErrorView = null;
mErrorStr = null;
}
}
private void removePage(@NonNull GalleryPageView page) {
mGalleryView.removeComponent(page);
mAdapter.unbind(page);
mGalleryView.releasePage(page);
}
private void removeAllPages() {
// Remove gallery view
if (mPrevious != null) {
removePage(mPrevious);
mPrevious = null;
}
if (mCurrent != null) {
removePage(mCurrent);
mCurrent = null;
}
if (mNext != null) {
removePage(mNext);
mNext = null;
}
}
@Override
public GalleryView.Adapter onDetach() {
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
// Cancel all animations
cancelAllAnimations();
// Remove all view
removeProgress();
removeErrorView();
removeAllPages();
// Clear iterator
GalleryView.Adapter adapter = mAdapter;
mAdapter = null;
return adapter;
}
private GalleryPageView getLeftPage() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
return mPrevious;
default:
case MODE_RIGHT_TO_LEFT:
return mNext;
}
}
private GalleryPageView getRightPage() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
return mNext;
default:
case MODE_RIGHT_TO_LEFT:
return mPrevious;
}
}
private GalleryPageView obtainPage() {
GalleryPageView page = mGalleryView.obtainPage();
page.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
return page;
}
private void layoutPage(GalleryPageView page, int widthSpec, int heightSpec,
int left, int top, int right, int bottom) {
Rect rect = mTempRect;
page.getValidRect(rect);
boolean oldValid = !rect.isEmpty();
page.measure(widthSpec, heightSpec);
page.layout(left, top, right, bottom);
page.getValidRect(rect);
boolean newValid = !rect.isEmpty();
if (!oldValid && newValid) {
page.getImageView().setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
}
@Override
public void onFill() {
GalleryView.Adapter adapter = mAdapter;
GalleryView galleryView = mGalleryView;
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", adapter);
int width = galleryView.getWidth();
int height = galleryView.getHeight();
int size = adapter.size();
String errorStr = adapter.getError();
if (size == GalleryProvider.STATE_WAIT) { // Wait here, show progress bar
// Remove error view and all pages
removeErrorView();
removeAllPages();
// Ensure progress
if (mProgress == null) {
mProgress = galleryView.obtainProgress();
galleryView.addComponent(mProgress);
}
// Place progress center
placeCenter(mProgress);
} else if (size <= GalleryProvider.STATE_ERROR || size == 0) { // Get error or empty, show error text
// Ensure error is not null
if (0 == size) {
errorStr = galleryView.getEmptyStr();
} else if (null == errorStr) {
errorStr = galleryView.getDefaultErrorStr();
}
// Remove progress and all pages
removeProgress();
removeAllPages();
// Ensure error view
if (mErrorView == null) {
mErrorView = galleryView.obtainErrorView();
galleryView.addComponent(mErrorView);
}
// Update error string
if (!errorStr.equals(mErrorStr)) {
mErrorStr = errorStr;
galleryView.bindErrorView(mErrorView, errorStr);
}
// Place error view center
placeCenter(mErrorView);
} else {
// Remove progress and error view
removeProgress();
removeErrorView();
// Ensure index in range
int index = mIndex;
if (index < 0) {
index = 0;
mIndex = index;
removeAllPages();
Log.e(TAG, "index < 0, index = " + index);
} else if (index >= size) {
index = size - 1;
mIndex = index;
removeAllPages();
Log.e(TAG, "index >= size, index = " + index + ", size = " + size);
}
// Ensure pages
if (mCurrent == null) {
mCurrent = obtainPage();
galleryView.addComponent(mCurrent);
adapter.bind(mCurrent, index);
}
if (mPrevious == null && index > 0) {
mPrevious = obtainPage();
galleryView.addComponent(mPrevious);
adapter.bind(mPrevious, index - 1);
} else if (mPrevious != null && index == 0) {
removePage(mPrevious);
}
if (mNext == null && index < size - 1) {
mNext = obtainPage();
galleryView.addComponent(mNext);
adapter.bind(mNext, index + 1);
} else if (mNext != null && index == size - 1) {
removePage(mNext);
}
GalleryPageView leftPage = getLeftPage();
GalleryPageView rightPage = getRightPage();
// Fix offset
final int min = rightPage == null ? 0 : -width - mInterval + 1;
final int max = leftPage == null ? 0 : width + mInterval - 1;
mOffset = MathUtils.clamp(mOffset, min, max);
// Measure and layout pages
final int offset = mOffset;
final int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY);
final int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.EXACTLY);
if (mCurrent != null) {
layoutPage(mCurrent, widthSpec, heightSpec,
offset, 0, width + offset, height);
}
if (leftPage != null) {
layoutPage(leftPage, widthSpec, heightSpec,
-mInterval - width + offset, 0, -mInterval + offset, height);
}
if (rightPage != null) {
layoutPage(rightPage, widthSpec, heightSpec,
width + mInterval + offset, 0, width + mInterval + width + offset, height);
}
}
}
@Override
public void onDown() {
mDeltaX = 0;
mDeltaY = 0;
mStopAnimationFinger = cancelAllAnimations();
}
@Override
public void onUp() {
if (mCurrent == null) {
return;
}
// Scroll
if (mOffset != 0) {
int width = mGalleryView.getWidth();
int dx;
if (mOffset >= mInterval && getLeftPage() != null) {
dx = mOffset - width - mInterval;
} else if (mOffset <= -mInterval && getRightPage() != null) {
dx = mOffset + width + mInterval;
} else {
dx = mOffset;
}
final float pageDelta = 7 * (float) Math.abs(mOffset) / (width + mInterval);
int duration = (int) ((pageDelta + 1) * 100);
mSmoothScroller.startSmoothScroll(dx, 0, duration);
}
}
@Override
public void onDoubleTapConfirmed(float x, float y) {
if (mCurrent == null || !mCurrent.getImageView().isLoaded()) {
return;
}
float[] scales = mScaleDefault;
ImageView image = mCurrent.getImageView();
image.getScaleDefault(scales);
float scale = image.getScale();
float endScale = scales[0];
for (float value : scales) {
if (scale < value - 0.01f) {
endScale = value;
break;
}
}
mSmoothScaler.startSmoothScaler(x, y, scale, endScale, 300);
}
@Override
public void onLongPress(float x, float y) {
}
private void pagePrevious() {
if (mIndex <= 0) {
return;
}
mIndex--;
if (mNext != null) {
removePage(mNext);
}
mNext = mCurrent;
mCurrent = mPrevious;
mPrevious = null;
if (mIndex > 0) {
mPrevious = obtainPage();
mGalleryView.addComponent(mPrevious);
mAdapter.bind(mPrevious, mIndex - 1);
}
}
private void pageNext() {
GalleryView.Adapter adapter = mAdapter;
int size = adapter.size();
if (mIndex >= size - 1) {
return;
}
mIndex++;
if (mPrevious != null) {
removePage(mPrevious);
}
mPrevious = mCurrent;
mCurrent = mNext;
mNext = null;
if (mIndex < size - 1) {
mNext = obtainPage();
mGalleryView.addComponent(mNext);
adapter.bind(mNext, mIndex + 1);
}
}
private void pageLeft() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
pagePrevious();
break;
default:
case MODE_RIGHT_TO_LEFT:
pageNext();
break;
}
}
private void pageRight() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
pageNext();
break;
default:
case MODE_RIGHT_TO_LEFT:
pagePrevious();
break;
}
}
private int scrollBetweenPages(int dx) {
GalleryPageView leftPage = getLeftPage();
GalleryPageView rightPage = getRightPage();
int width = mGalleryView.getWidth();
int remain;
if (dx < 0) { // Try to show left
int limit;
if (leftPage == null) {
limit = 0;
} else {
limit = width + mInterval;
}
if (dx > mOffset - limit) {
remain = 0;
mOffset -= dx;
} else {
// Go to left page if left page not null
if (leftPage != null) {
pageLeft();
}
remain = dx + limit - mOffset;
mOffset = 0;
}
} else { // Try to show right
int limit;
if (rightPage == null) {
limit = 0;
} else {
limit = -width - mInterval;
}
if (dx < mOffset - limit) {
remain = 0;
mOffset -= dx;
} else {
// Go to right page if right page not null
if (rightPage != null) {
pageRight();
}
remain = dx + limit - mOffset;
mOffset = 0;
}
}
return remain;
}
public void scrollInternal(float dx, float dy, float x, float y) {
if (mCurrent == null) {
return;
}
boolean needFill = false;
boolean canImageScroll = true;
int remainX = (int) dx;
int remainY = (int) dy;
if (mGalleryView.isFirstScroll()) {
mCanScrollBetweenPages = Math.abs(dx) > Math.abs(dy) * 1.5;
}
while (remainX != 0 || remainY != 0) {
if (mOffset == 0 && canImageScroll) {
ImageView image = mCurrent.getImageView();
image.scroll(remainX, remainY, mScrollRemain);
remainX = mScrollRemain[0];
remainY = mScrollRemain[1];
canImageScroll = false;
mDeltaX = 0;
mDeltaY = 0;
} else if (remainX == 0 ||
(getLeftPage() == null && mOffset == 0 && remainX < 0) ||
(getRightPage() == null && mOffset == 0 && remainX > 0)) {
// On edge
remainX = 0;
remainY = 0;
} else if (mCanScrollBetweenPages) {
remainX = scrollBetweenPages(remainX);
canImageScroll = true;
needFill = true;
mDeltaX = 0;
mDeltaY = 0;
} else {
remainX = 0;
remainY = 0;
mDeltaX = 0;
mDeltaY = 0;
}
}
if (needFill) {
mGalleryView.requestFill();
}
}
@Override
public void onScroll(float dx, float dy, float totalX, float totalY, float x, float y) {
scrollInternal(dx, dy, x, y);
}
@Override
public void onFling(float velocityX, float velocityY) {
if (mCurrent == null || mOffset != 0 || !mCurrent.getImageView().isLoaded() ||
!mCurrent.getImageView().canFling()) {
return;
}
ImageView image = mCurrent.getImageView();
mPageFling.startFling((int) velocityX, image.getMinDx(), image.getMaxDx(),
(int) velocityY, image.getMinDy(), image.getMaxDy());
}
@Override
public boolean canScale() {
return mCurrent != null && mOffset == 0 && mCurrent.getImageView().isLoaded();
}
@Override
public void onScale(float focusX, float focusY, float scale) {
if (mCurrent == null || !mCurrent.getImageView().isLoaded()) {
return;
}
mCurrent.getImageView().scale(focusX, focusY, scale);
mScaleValue = mCurrent.getImageView().getScale();
}
@Override
public boolean onUpdateAnimation(long time) {
boolean invalidate = mSmoothScroller.calculate(time);
invalidate |= mPageFling.calculate(time);
invalidate |= mSmoothScaler.calculate(time);
return invalidate;
}
@Override
public void onDataChanged() {
AssertUtils.assertNotNull("The PagerLayoutManager is not attached", mAdapter);
// Cancel all animations
cancelAllAnimations();
// Remove all views
removeProgress();
removeErrorView();
removeAllPages();
// Reset parameters
resetParameters();
mGalleryView.requestFill();
}
@Override
public void onPageLeft() {
int size = mAdapter.size();
if (size <= 0 || mCurrent == null) {
return;
}
if (mMode == MODE_LEFT_TO_RIGHT) {
if (mIndex != 0) {
setCurrentIndex(mIndex - 1);
}
} else {
if (mIndex < size - 1) {
setCurrentIndex(mIndex + 1);
}
}
}
@Override
public void onPageRight() {
int size = mAdapter.size();
if (size <= 0 || mCurrent == null) {
return;
}
if (mMode == MODE_LEFT_TO_RIGHT) {
if (mIndex < size - 1) {
setCurrentIndex(mIndex + 1);
}
} else {
if (mIndex != 0) {
setCurrentIndex(mIndex - 1);
}
}
}
@Override
public boolean isTapOrPressEnable() {
return !mStopAnimationFinger;
}
@Override
public GalleryPageView findPageByIndex(int index) {
if (mCurrent != null && mCurrent.getIndex() == index) {
return mCurrent;
}
if (mPrevious != null && mPrevious.getIndex() == index) {
return mPrevious;
}
if (mNext != null && mNext.getIndex() == index) {
return mNext;
}
return null;
}
@Override
public int getCurrentIndex() {
if (mCurrent != null) {
return mCurrent.getIndex();
} else {
return GalleryPageView.INVALID_INDEX;
}
}
@Override
public void setCurrentIndex(int index) {
int size = mAdapter.size();
if (size <= 0) {
// Can't get size now, assume size is MAX
size = Integer.MAX_VALUE;
}
if (index == mIndex || index < 0 || index >= size) {
return;
}
if (mCurrent == null) {
mIndex = index;
} else if (index == mIndex - 1) {
// Cancel all animations
cancelAllAnimations();
// Reset parameters
resetParameters();
// Go to previous
pagePrevious();
// Request fill
mGalleryView.requestFill();
} else if (index == mIndex + 1) {
// Cancel all animations
cancelAllAnimations();
// Reset parameters
resetParameters();
// Go to next
pageNext();
// Request fill
mGalleryView.requestFill();
} else {
mIndex = index;
// It is attached, refill
// Cancel all animations
cancelAllAnimations();
// Remove all view
removeProgress();
removeErrorView();
removeAllPages();
// Reset parameters
resetParameters();
// Request fill
mGalleryView.requestFill();
}
}
@Override
public int getIndexUnder(float x, float y) {
if (mCurrent == null) {
return GalleryPageView.INVALID_INDEX;
} else {
int intX = (int) x;
int intY = (int) y;
if (mCurrent.bounds().contains(intX, intY)) {
return mCurrent.getIndex();
} else if (mPrevious != null && mPrevious.bounds().contains(intX, intY)) {
return mPrevious.getIndex();
} else if (mNext != null && mNext.bounds().contains(intX, intY)) {
return mNext.getIndex();
} else {
return GalleryPageView.INVALID_INDEX;
}
}
}
@Override
int getInternalCurrentIndex() {
int currentIndex = getCurrentIndex();
if (currentIndex == GalleryPageView.INVALID_INDEX) {
currentIndex = mIndex;
}
return currentIndex;
}
@IntDef({MODE_LEFT_TO_RIGHT, MODE_RIGHT_TO_LEFT})
@Retention(RetentionPolicy.SOURCE)
private @interface Mode {
}
private class SmoothScroller extends Animation {
private int mDx;
private int mDy;
private int mLastX;
private int mLastY;
public SmoothScroller() {
setInterpolator(SMOOTH_SCROLLER_INTERPOLATOR);
}
public void startSmoothScroll(int dx, int dy, int duration) {
mDx = dx;
mDy = dy;
mLastX = 0;
mLastY = 0;
setDuration(duration);
start();
mGalleryView.invalidate();
}
@Override
protected void onCalculate(float progress) {
int x = (int) (mDx * progress);
int y = (int) (mDy * progress);
int offsetX = x - mLastX;
while (offsetX != 0) {
int oldOffsetX = offsetX;
offsetX = scrollBetweenPages(offsetX);
// Avoid loop infinitely
if (offsetX == oldOffsetX) {
break;
} else {
mGalleryView.requestFill();
}
}
mLastX = x;
mLastY = y;
}
}
private class PageFling extends Fling {
private final int[] mTemp = new int[2];
private int mVelocityX;
private int mVelocityY;
private int mDx;
private int mDy;
private int mLastX;
private int mLastY;
public PageFling(Context context) {
super(context);
}
public void startFling(int velocityX, int minX, int maxX,
int velocityY, int minY, int maxY) {
mVelocityX = velocityX;
mVelocityY = velocityY;
mDx = (int) (getSplineFlingDistance(velocityX) * Math.signum(velocityX));
mDy = (int) (getSplineFlingDistance(velocityY) * Math.signum(velocityY));
mLastX = 0;
mLastY = 0;
int durationX = getSplineFlingDuration(velocityX);
int durationY = getSplineFlingDuration(velocityY);
if (mDx < minX) {
durationX = adjustDuration(0, mDx, minX, durationX);
mDx = minX;
}
if (mDx > maxX) {
durationX = adjustDuration(0, mDx, maxX, durationX);
mDx = maxX;
}
if (mDy < minY) {
durationY = adjustDuration(0, mDy, minY, durationY);
mDy = minY;
}
if (mDy > maxY) {
durationY = adjustDuration(0, mDy, maxY, durationY);
mDy = maxY;
}
if (mDx == 0 && mDy == 0) {
return;
}
setDuration(Math.max(durationX, durationY));
start();
mGalleryView.invalidate();
}
@Override
protected void onCalculate(float progress) {
int x = (int) (mDx * progress);
int y = (int) (mDy * progress);
int offsetX = x - mLastX;
int offsetY = y - mLastY;
if (mCurrent != null && (offsetX != 0 || offsetY != 0)) {
mCurrent.getImageView().scroll(-offsetX, -offsetY, mTemp);
}
mLastX = x;
mLastY = y;
}
}
private class SmoothScaler extends Animation {
private float mFocusX;
private float mFocusY;
private float mStartScale;
private float mEndScale;
private float mLastScale;
public SmoothScaler() {
setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
}
public void startSmoothScaler(float focusX, float focusY,
float startScale, float endScale, int duration) {
mFocusX = focusX;
mFocusY = focusY;
mStartScale = startScale;
mEndScale = endScale;
mLastScale = startScale;
setDuration(duration);
start();
mGalleryView.invalidate();
}
@Override
protected void onCalculate(float progress) {
if (mCurrent == null) {
return;
}
float scale = MathUtils.lerp(mStartScale, mEndScale, progress);
mCurrent.getImageView().scale(mFocusX, mFocusY, scale / mLastScale);
mLastScale = scale;
mScaleValue = scale;
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,163 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glgallery;
import androidx.annotation.NonNull;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.image.ImageWrapper;
import com.hippo.glview.view.GLRootView;
public class SimpleAdapter extends GalleryView.Adapter implements GalleryProvider.Listener {
private final GalleryProvider mProvider;
private final ImageTexture.Uploader mUploader;
private boolean mShowIndex = true;
public SimpleAdapter(@NonNull GLRootView glRootView, @NonNull GalleryProvider provider) {
mProvider = provider;
mUploader = new ImageTexture.Uploader(glRootView);
}
public void clearUploader() {
mUploader.clear();
}
public void setShowIndex(boolean show) {
mShowIndex = show;
}
@Override
public void onBind(GalleryPageView view, int index) {
mProvider.request(index);
view.showInfo();
view.setImage(null);
if (mShowIndex) {
view.setPage(index + 1);
} else {
view.hidePage();
}
view.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
view.setError(null, null);
}
@Override
public void onUnbind(GalleryPageView view, int index) {
mProvider.cancelRequest(index);
view.setImage(null);
view.setError(null, null);
}
@Override
public String getError() {
return mProvider.getError();
}
@Override
public int size() {
return mProvider.size();
}
@Override
public void onDataChanged() {
mGalleryView.onDataChanged();
}
@Override
public void onPageWait(int index) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
page.showInfo();
page.setImage(null);
if (mShowIndex) {
page.setPage(index + 1);
} else {
page.hidePage();
}
page.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
page.setError(null, null);
}
}
@Override
public void onPagePercent(int index, float percent) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
page.showInfo();
page.setImage(null);
if (mShowIndex) {
page.setPage(index + 1);
} else {
page.hidePage();
}
page.setProgress(percent);
page.setError(null, null);
}
}
@Override
public void onPageSucceed(int index, ImageWrapper image) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
if (image.obtain()) {
ImageTexture imageTexture = new ImageTexture(image);
mUploader.addTexture(imageTexture);
page.showImage();
page.setImage(imageTexture);
if (mShowIndex) {
page.setPage(index + 1);
} else {
page.hidePage();
}
page.setProgress(GalleryPageView.PROGRESS_GONE);
page.setError(null, null);
} else {
// The image is recycled, request again.
// TODO request loop ?
mProvider.request(index);
}
}
}
@Override
public void onPageFailed(int index, String error) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
page.showInfo();
page.setImage(null);
if (mShowIndex) {
page.setPage(index + 1);
} else {
page.hidePage();
}
page.setProgress(GalleryPageView.PROGRESS_GONE);
page.setError(error, mGalleryView);
}
}
@Override
public void onDataChanged(int index) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
mProvider.request(index);
}
}
private GalleryPageView findPageByIndex(int index) {
return mGalleryView != null ? mGalleryView.findPageByIndex(index) : null;
}
}
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.anim;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.yorozuya.MathUtils;
public class AlphaAnimation extends CanvasAnimation {
private final float mStartAlpha;
private final float mEndAlpha;
private float mCurrentAlpha;
public AlphaAnimation(float from, float to) {
mStartAlpha = from;
mEndAlpha = to;
mCurrentAlpha = from;
}
@Override
public void apply(GLCanvas canvas) {
canvas.multiplyAlpha(mCurrentAlpha);
}
@Override
public int getCanvasSaveFlags() {
return GLCanvas.SAVE_FLAG_ALPHA;
}
@Override
protected void onCalculate(float progress) {
mCurrentAlpha = MathUtils.clamp(mStartAlpha +
(mEndAlpha - mStartAlpha) * progress, 0f, 1f);
}
}
@@ -1,165 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.anim;
import android.os.SystemClock;
import android.view.animation.Interpolator;
import com.hippo.yorozuya.MathUtils;
// Animation calculates a value according to the current input time.
//
// 1. First we need to use setDuration(int) to set the duration of the
// animation. The duration is in milliseconds.
// 2. Then we should call start(). The actual start time is the first value
// passed to calculate(long).
// 3. Each time we want to get an animation value, we call
// calculate(long currentTimeMillis) to ask the Animation to calculate it.
// The parameter passed to calculate(long) should be nonnegative.
// 4. Use get() to get that value.
//
// In step 3, onCalculate(float progress) is called so subclasses can calculate
// the value according to progress (progress is a value in [0,1]).
//
// Before onCalculate(float) is called, There is an optional interpolator which
// can change the progress value. The interpolator can be set by
// setInterpolator(Interpolator). If the interpolator is used, the value passed
// to onCalculate may be (for example, the overshoot effect).
//
// The isActive() method returns true after the animation start() is called and
// before calculate is passed a value which reaches the duration of the
// animation.
//
// The start() method can be called again to restart the Animation.
//
abstract public class Animation {
/**
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
* or a positive value, the animation restarts from the beginning.
*/
public static final int RESTART = 1;
/**
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
* or a positive value, the animation reverses direction on every iteration.
*/
public static final int REVERSE = 2;
/**
* This value used used with the {@link #setRepeatCount(int)} property to repeat
* the animation indefinitely.
*/
public static final int INFINITE = -1;
private static final long ANIMATION_START = -1;
private static final long NO_ANIMATION = -2;
private long mStartTime = NO_ANIMATION;
private long mDuration;
private Interpolator mInterpolator;
private int mRepeatCount;
private int mRepeatMode;
private int mRunnedCount;
private long mLastFrameTime;
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
}
public void setDuration(long duration) {
mDuration = duration;
}
public void setRepeatCount(int repeatCount) {
mRepeatCount = repeatCount;
}
public void setRepeatMode(int repeatMode) {
mRepeatMode = repeatMode;
}
public boolean isRunning() {
return mStartTime > 0 || mStartTime == ANIMATION_START;
}
public long getLastFrameTime() {
return mLastFrameTime;
}
public void start() {
if (mStartTime == NO_ANIMATION) {
mStartTime = ANIMATION_START;
mRunnedCount = 0;
mLastFrameTime = 0;
}
}
public void startAt(long time) {
start();
setStartTime(time);
}
public void startNow() {
start();
setStartTime(SystemClock.uptimeMillis());
}
public void setStartTime(long time) {
mStartTime = time;
}
public void cancel() {
mStartTime = NO_ANIMATION;
}
public void reset() {
mStartTime = ANIMATION_START;
mRunnedCount = 0;
mLastFrameTime = 0;
}
// TODO mRepeatMode
public boolean calculate(long currentTimeMillis) {
if (mStartTime == NO_ANIMATION) {
return false;
}
if (mStartTime == ANIMATION_START) {
mStartTime = currentTimeMillis;
}
mLastFrameTime = currentTimeMillis;
long elapse = currentTimeMillis - mStartTime;
float x = MathUtils.clamp(mDuration == 0.0f ? 1.0f : (float) elapse / mDuration, 0.0f, 1.0f); // Avoid NaN
Interpolator i = mInterpolator;
onCalculate(i != null ? i.getInterpolation(x) : x);
// It is ok to call cancel() in onCalculate()
if (mStartTime != NO_ANIMATION && elapse >= mDuration) {
mRunnedCount++;
if (mRunnedCount >= mRepeatCount && mRepeatCount != INFINITE) {
onFinish();
mStartTime = NO_ANIMATION;
} else {
mStartTime += elapse;
}
}
return mStartTime != NO_ANIMATION;
}
abstract protected void onCalculate(float progress);
protected void onFinish() {
}
}
@@ -1,26 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.anim;
import com.hippo.glview.glrenderer.GLCanvas;
public abstract class CanvasAnimation extends Animation {
public abstract int getCanvasSaveFlags();
public abstract void apply(GLCanvas canvas);
}
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.anim;
import com.hippo.yorozuya.MathUtils;
public class FloatAnimation extends Animation {
private float mFrom;
private float mTo;
private float mCurrent;
public void setRange(float from, float to) {
mFrom = from;
mTo = to;
}
@Override
protected void onCalculate(float progress) {
mCurrent = MathUtils.lerp(mFrom, mTo, progress);
}
public float get() {
return mCurrent;
}
}
@@ -1,27 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
public @interface RenderThread {
}
@@ -1,214 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.RectF;
import android.util.Log;
import com.hippo.yorozuya.MathUtils;
import java.util.WeakHashMap;
// BasicTexture is a Texture corresponds to a real GL texture.
// The state of a BasicTexture indicates whether its data is loaded to GL memory.
// If a BasicTexture is loaded into GL memory, it has a GL texture id.
public abstract class BasicTexture implements Texture {
protected static final int UNSPECIFIED = -1;
protected static final int STATE_UNLOADED = 0;
protected static final int STATE_LOADED = 1;
protected static final int STATE_ERROR = -1;
private static final String TAG = "BasicTexture";
// Log a warning if a texture is larger along a dimension
private static final int MAX_TEXTURE_SIZE = 4096;
private final static WeakHashMap<BasicTexture, Object> sAllTextures
= new WeakHashMap<>();
private static final ThreadLocal<Class> sInFinalizer = new ThreadLocal<>();
protected int mId = -1;
protected int mState;
protected int mWidth = UNSPECIFIED;
protected int mHeight = UNSPECIFIED;
protected int mTextureWidth;
protected int mTextureHeight;
protected GLCanvas mCanvasRef = null;
private boolean mHasBorder;
protected BasicTexture(GLCanvas canvas, int id, int state) {
setAssociatedCanvas(canvas);
mId = id;
mState = state;
synchronized (sAllTextures) {
sAllTextures.put(this, null);
}
}
protected BasicTexture() {
this(null, 0, STATE_UNLOADED);
}
// This is for deciding if we can call Bitmap's recycle().
// We cannot call Bitmap's recycle() in finalizer because at that point
// the finalizer of Bitmap may already be called so recycle() will crash.
public static boolean inFinalizer() {
return sInFinalizer.get() != null;
}
public static void yieldAllTextures() {
synchronized (sAllTextures) {
for (BasicTexture t : sAllTextures.keySet()) {
t.yield();
}
}
}
public static void invalidateAllTextures() {
synchronized (sAllTextures) {
for (BasicTexture t : sAllTextures.keySet()) {
t.mState = STATE_UNLOADED;
t.setAssociatedCanvas(null);
}
}
}
protected void setAssociatedCanvas(GLCanvas canvas) {
mCanvasRef = canvas;
}
/**
* Sets the content size of this texture. In OpenGL, the actual texture
* size must be of power of 2, the size of the content may be smaller.
*/
public void setSize(int width, int height) {
mWidth = width;
mHeight = height;
mTextureWidth = width > 0 ? MathUtils.nextPowerOf2(width) : 0;
mTextureHeight = height > 0 ? MathUtils.nextPowerOf2(height) : 0;
if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
Log.w(TAG, String.format("texture is too large: %d x %d",
mTextureWidth, mTextureHeight), new Exception());
}
}
public boolean isFlippedVertically() {
return false;
}
public int getId() {
return mId;
}
@Override
public int getWidth() {
return mWidth;
}
@Override
public int getHeight() {
return mHeight;
}
// Returns the width rounded to the next power of 2.
public int getTextureWidth() {
return mTextureWidth;
}
// Returns the height rounded to the next power of 2.
public int getTextureHeight() {
return mTextureHeight;
}
// Returns true if the texture has one pixel transparent border around the
// actual content. This is used to avoid jigged edges.
//
// The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
// mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
// covered by the texture will use the color of the edge texel. If we add
// the transparent border, the color of the edge texel will be mixed with
// appropriate amount of transparent.
//
// Currently our background is black, so we can draw the thumbnails without
// enabling blending.
public boolean hasBorder() {
return mHasBorder;
}
protected void setBorder(boolean hasBorder) {
mHasBorder = hasBorder;
}
@Override
public void draw(GLCanvas canvas, int x, int y) {
canvas.drawTexture(this, x, y, getWidth(), getHeight());
}
@Override
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
canvas.drawTexture(this, x, y, w, h);
}
@Override
public void draw(GLCanvas canvas, RectF source, RectF target) {
canvas.drawTexture(this, source, target);
}
// onBind is called before GLCanvas binds this texture.
// It should make sure the data is uploaded to GL memory.
abstract protected boolean onBind(GLCanvas canvas);
// Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
abstract protected int getTarget();
public boolean isLoaded() {
return mState == STATE_LOADED;
}
// recycle() is called when the texture will never be used again,
// so it can free all resources.
public void recycle() {
freeResource();
}
// yield() is called when the texture will not be used temporarily,
// so it can free some resources.
// The default implementation unloads the texture from GL memory, so
// the subclass should make sure it can reload the texture to GL memory
// later, or it will have to override this method.
public void yield() {
freeResource();
}
private void freeResource() {
GLCanvas canvas = mCanvasRef;
if (canvas != null && mId != -1) {
canvas.unloadTexture(this);
mId = -1; // Don't free it again.
}
mState = STATE_UNLOADED;
setAssociatedCanvas(null);
}
@Override
protected void finalize() throws Throwable {
try {
sInFinalizer.set(BasicTexture.class);
recycle();
sInFinalizer.set(null);
} finally {
super.finalize();
}
}
}
@@ -1,54 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import com.hippo.yorozuya.AssertUtils;
// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
//
// The texture does not own the Bitmap. The user should make sure the Bitmap
// is valid during the texture's lifetime. When the texture is recycled, it
// does not free the Bitmap.
public class BitmapTexture extends UploadedTexture {
protected Bitmap mContentBitmap;
public BitmapTexture(Bitmap bitmap) {
this(bitmap, false);
}
public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
super(hasBorder);
AssertUtils.assertTrue(bitmap != null && !bitmap.isRecycled());
mContentBitmap = bitmap;
}
@Override
protected void onFreeBitmap(Bitmap bitmap) {
// Do nothing.
}
@Override
protected Bitmap onGetBitmap() {
return mContentBitmap;
}
public Bitmap getBitmap() {
return mContentBitmap;
}
}
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
// CanvasTexture is a texture whose content is the drawing on a Canvas.
// The subclasses should override onDraw() to draw on the bitmap.
// By default CanvasTexture is not opaque.
abstract class CanvasTexture extends UploadedTexture {
private final Config mConfig;
public CanvasTexture(int width, int height) {
mConfig = Config.ARGB_8888;
setSize(width, height);
setOpaque(false);
}
@Override
protected Bitmap onGetBitmap() {
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig);
Canvas canvas = new Canvas(bitmap);
onDraw(canvas, bitmap);
return bitmap;
}
@Override
protected void onFreeBitmap(Bitmap bitmap) {
if (!inFinalizer()) {
bitmap.recycle();
}
}
abstract protected void onDraw(Canvas canvas, Bitmap backing);
}
@@ -1,74 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.RectF;
import com.hippo.yorozuya.ColorUtils;
// ColorTexture is a texture which fills the rectangle with the specified color.
public class ColorTexture implements Texture {
private final int mColor;
private int mWidth = 0;
private int mHeight = 0;
public ColorTexture(int color) {
mColor = color;
}
private boolean isValidSize() {
return mWidth > 0 && mHeight > 0;
}
@Override
public void draw(GLCanvas canvas, int x, int y) {
if (isValidSize()) {
draw(canvas, x, y, mWidth, mHeight);
}
}
@Override
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
canvas.fillRect(x, y, w, h, mColor);
}
@Override
public void draw(GLCanvas canvas, RectF source, RectF target) {
canvas.fillRect(target.left, target.top, target.width(), target.height(), mColor);
}
@Override
public boolean isOpaque() {
return ColorUtils.isOpaque(mColor);
}
public void setSize(int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public int getWidth() {
return mWidth;
}
@Override
public int getHeight() {
return mHeight;
}
}
@@ -1,60 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
// ExtTexture is a texture whose content comes from a external texture.
// Before drawing, setSize() should be called.
public class ExtTexture extends BasicTexture {
private final int mTarget;
public ExtTexture(GLCanvas canvas, int target) {
GLId glId = canvas.getGLId();
mId = glId.generateTexture();
mTarget = target;
}
private void uploadToCanvas(GLCanvas canvas) {
canvas.setTextureParameters(this);
setAssociatedCanvas(canvas);
mState = STATE_LOADED;
}
@Override
protected boolean onBind(GLCanvas canvas) {
if (!isLoaded()) {
uploadToCanvas(canvas);
}
return true;
}
@Override
public int getTarget() {
return mTarget;
}
@Override
public boolean isOpaque() {
return true;
}
@Override
public void yield() {
// we cannot free the texture because we have no backup.
}
}
@@ -1,52 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.RectF;
// FadeInTexture is a texture which begins with a color, then gradually animates
// into a given texture.
public class FadeInTexture extends FadeTexture implements Texture {
private final int mColor;
private final TiledTexture mTexture;
public FadeInTexture(int color, TiledTexture texture) {
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
mColor = color;
mTexture = texture;
}
@Override
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
if (isAnimating()) {
mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h);
} else {
mTexture.draw(canvas, x, y, w, h);
}
}
@Override
public void draw(GLCanvas canvas, RectF source, RectF target) {
if (isAnimating()) {
mTexture.drawMixed(canvas, mColor, getRatio(), source, target);
} else {
mTexture.draw(canvas, source, target);
}
}
}
@@ -1,52 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.RectF;
// FadeOutTexture is a texture which begins with a given texture, then gradually animates
// into fading out totally.
public class FadeOutTexture extends FadeTexture {
private final BasicTexture mTexture;
public FadeOutTexture(BasicTexture texture) {
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
mTexture = texture;
}
@Override
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
if (isAnimating()) {
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
canvas.setAlpha(getRatio());
mTexture.draw(canvas, x, y, w, h);
canvas.restore();
}
}
@Override
public void draw(GLCanvas canvas, RectF source, RectF target) {
if (isAnimating()) {
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
canvas.setAlpha(getRatio());
mTexture.draw(canvas, source, target);
canvas.restore();
}
}
}
@@ -1,79 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import com.hippo.glview.view.AnimationTime;
import com.hippo.yorozuya.MathUtils;
// FadeTexture is a texture which fades the given texture along the time.
public abstract class FadeTexture implements Texture {
// The duration of the fading animation in milliseconds
public static final int DURATION = 180;
private final long mStartTime;
private final int mWidth;
private final int mHeight;
private final boolean mIsOpaque;
private boolean mIsAnimating;
public FadeTexture(int width, int height, boolean opaque) {
mWidth = width;
mHeight = height;
mIsOpaque = opaque;
mStartTime = now();
mIsAnimating = true;
}
@Override
public void draw(GLCanvas canvas, int x, int y) {
draw(canvas, x, y, mWidth, mHeight);
}
@Override
public boolean isOpaque() {
return mIsOpaque;
}
@Override
public int getWidth() {
return mWidth;
}
@Override
public int getHeight() {
return mHeight;
}
public boolean isAnimating() {
if (mIsAnimating) {
if (now() - mStartTime >= DURATION) {
mIsAnimating = false;
}
}
return mIsAnimating;
}
protected float getRatio() {
float r = (float) (now() - mStartTime) / DURATION;
return MathUtils.clamp(1.0f - r, 0.0f, 1.0f);
}
private long now() {
return AnimationTime.get();
}
}
@@ -1,223 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
//
// GLCanvas gives a convenient interface to draw using OpenGL.
//
// When a rectangle is specified in this interface, it means the region
// [x, x+width) * [y, y+height)
//
public interface GLCanvas {
int SAVE_FLAG_ALL = 0xFFFFFFFF;
int SAVE_FLAG_ALPHA = 0x01;
int SAVE_FLAG_MATRIX = 0x02;
GLId getGLId();
// Tells GLCanvas the size of the underlying GL surface. This should be
// called before first drawing and when the size of GL surface is changed.
// This is called by GLRoot and should not be called by the clients
// who only want to draw on the GLCanvas. Both width and height must be
// nonnegative.
void setSize(int width, int height);
// Clear the drawing buffers. This should only be used by GLRoot.
void clearBuffer();
void clearBuffer(float[] argb);
float getAlpha();
// Sets and gets the current alpha, alpha must be in [0, 1].
void setAlpha(float alpha);
// (current alpha) = (current alpha) * alpha
void multiplyAlpha(float alpha);
// Change the current transform matrix.
void translate(float x, float y, float z);
void translate(float x, float y);
void scale(float sx, float sy, float sz);
void rotate(float angle, float x, float y, float z);
void multiplyMatrix(float[] mMatrix, int offset);
// Pushes the configuration state (matrix, and alpha) onto
// a private stack.
void save();
// Same as save(), but only save those specified in saveFlags.
void save(int saveFlags);
// Pops from the top of the stack as current configuration state (matrix,
// alpha, and clip). This call balances a previous call to save(), and is
// used to remove all modifications to the configuration state since the
// last save call.
void restore();
// Draws a line using the specified paint from (x1, y1) to (x2, y2).
// (Both end points are included).
void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
// Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
// (Both end points are included).
void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
// Draws a oval using the specified paint for (cx, cy, radiusX, radiusY)
// (Both end points are included).
void drawOval(float cx, float cy, float radiusX, float radiusY,
GLPaint paint);
// Draw a arc inside a rect
void drawArc(float cx, float cy, float radiusX, float radiusY,
float sweepAngle, GLPaint paint);
// Fills the specified rectangle with the specified color.
void fillRect(float x, float y, float width, float height, int color);
// Fills the specified oval with the specified color.
void fillOval(float cx, float cy, float radiusX, float radiusY, int color);
// Fills the specified sector with the specified color.
void fillSector(float cx, float cy, float radiusX, float radiusY, float sweepAngle, int color);
// Draws a texture to the specified rectangle.
void drawTexture(BasicTexture texture, int x, int y, int width, int height);
void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, int uvBuffer, int indexBuffer, int indexCount);
// Draws the source rectangle part of the texture to the target rectangle.
void drawTexture(BasicTexture texture, RectF source, RectF target);
// Draw a texture with a specified texture transform.
void drawTexture(BasicTexture texture, float[] mTextureTransform, int x, int y, int w, int h);
// Draw two textures to the specified rectangle. The actual texture used is
// from * (1 - ratio) + to * ratio
// The two textures must have the same size.
void drawMixed(BasicTexture from, int toColor, float ratio, int x, int y, int w, int h);
// Draw a region of a texture and a specified color to the specified
// rectangle. The actual color used is from * (1 - ratio) + to * ratio.
// The region of the texture is defined by parameter "src". The target
// rectangle is specified by parameter "target".
void drawMixed(BasicTexture from, int toColor, float ratio, RectF src, RectF target);
// Unloads the specified texture from the canvas. The resource allocated
// to draw the texture will be released. The specified texture will return
// to the unloaded state. This function should be called only from
// BasicTexture or its descendant
boolean unloadTexture(BasicTexture texture);
// Delete the specified buffer object, similar to unloadTexture.
void deleteBuffer(int bufferId);
// Delete the textures and buffers in GL side. This function should only be
// called in the GL thread.
void deleteRecycledResources();
// Dump statistics information and clear the counters. For debug only.
void dumpStatisticsAndClear();
void beginRenderTarget(RawTexture texture);
void endRenderTarget();
/**
* Sets texture parameters to use GL_CLAMP_TO_EDGE for both
* GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
* GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
* bindTexture() must be called prior to this.
*
* @param texture The texture to set parameters on.
*/
void setTextureParameters(BasicTexture texture);
/**
* Initializes the texture to a size by calling texImage2D on it.
*
* @param texture The texture to initialize the size.
* @param format The texture format (e.g. GL_RGBA)
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
*/
void initializeTextureSize(BasicTexture texture, int format, int type);
/**
* Initializes the texture to a size by calling texImage2D on it.
*
* @param texture The texture to initialize the size.
* @param bitmap The bitmap to initialize the bitmap with.
*/
void initializeTexture(BasicTexture texture, Bitmap bitmap);
/**
* Calls glTexSubImage2D to upload a bitmap to the texture.
*
* @param texture The target texture to write to.
* @param xOffset Specifies a texel offset in the x direction within the
* texture array.
* @param yOffset Specifies a texel offset in the y direction within the
* texture array.
* @param format The texture format (e.g. GL_RGBA)
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
*/
void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, int format, int type);
/**
* Generates buffers and uploads the buffer data.
*
* @param buffer The buffer to upload
* @return The buffer ID that was generated.
*/
int uploadBuffer(java.nio.FloatBuffer buffer);
/**
* Generates buffers and uploads the element array buffer data.
*
* @param buffer The buffer to upload
* @return The buffer ID that was generated.
*/
int uploadBuffer(java.nio.ByteBuffer buffer);
/**
* After LightCycle makes GL calls, this method is called to restore the GL
* configuration to the one expected by GLCanvas.
*/
void recoverFromLightCycle();
/**
* Gets the bounds given by x, y, width, and height as well as the internal
* matrix state. There is no special handling for non-90-degree rotations.
* It only considers the lower-left and upper-right corners as the bounds.
*
* @param bounds The output bounds to write to.
* @param x The left side of the input rectangle.
* @param y The bottom of the input rectangle.
* @param width The width of the input rectangle.
* @param height The height of the input rectangle.
*/
void getBounds(Rect bounds, int x, int y, int width, int height);
}
File diff suppressed because it is too large. Load diff
@@ -1,67 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11ExtensionPack;
/**
* Open GL ES 1.1 implementation for generating and destroying texture IDs and
* buffer IDs
*/
public class GLES11IdImpl implements GLId {
// Mutex for sNextId
private final static Object sLock = new Object();
private static int sNextId = 1;
@Override
public int generateTexture() {
synchronized (sLock) {
return sNextId++;
}
}
@Override
public void glGenBuffers(int n, int[] buffers, int offset) {
synchronized (sLock) {
while (n-- > 0) {
buffers[offset + n] = sNextId++;
}
}
}
@Override
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
synchronized (sLock) {
gl.glDeleteTextures(n, textures, offset);
}
}
@Override
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
synchronized (sLock) {
gl.glDeleteBuffers(n, buffers, offset);
}
}
@Override
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
synchronized (sLock) {
gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,60 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.glview.glrenderer;
import android.opengl.GLES20;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11ExtensionPack;
public class GLES20IdImpl implements GLId {
private final int[] mTempIntArray = new int[1];
@Override
public int generateTexture() {
GLES20.glGenTextures(1, mTempIntArray, 0);
GLES20Canvas.checkError();
return mTempIntArray[0];
}
@Override
public void glGenBuffers(int n, int[] buffers, int offset) {
GLES20.glGenBuffers(n, buffers, offset);
GLES20Canvas.checkError();
}
@Override
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
GLES20.glDeleteTextures(n, textures, offset);
GLES20Canvas.checkError();
}
@Override
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
GLES20.glDeleteBuffers(n, buffers, offset);
GLES20Canvas.checkError();
}
@Override
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
GLES20.glDeleteFramebuffers(n, buffers, offset);
GLES20Canvas.checkError();
}
}
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11ExtensionPack;
// This mimics corresponding GL functions.
public interface GLId {
int generateTexture();
void glGenBuffers(int n, int[] buffers, int offset);
void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
}
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
public class GLPaint {
private float mLineWidth = 1f;
private int mColor = 0;
private int mBackgroundColor = 0;
public int getColor() {
return mColor;
}
public void setColor(int color) {
mColor = color;
}
public int getBackgroundColor() {
return mBackgroundColor;
}
public void setBackgroundColor(int backgroundColor) {
mBackgroundColor = backgroundColor;
}
public float getLineWidth() {
return mLineWidth;
}
public void setLineWidth(float width) {
if (width < 0) {
throw new IllegalArgumentException("width < 0");
}
mLineWidth = width;
}
}
@@ -1,202 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import java.util.Arrays;
// TODO support multiline
/**
* Works like movable type.<br>
* <br>
* Only support single line now
*/
public final class MovableTextTexture extends SpriteTexture {
private final char[] mCharacters;
private final float[] mWidths;
private final float mHeight;
private final float mMaxWidth;
private MovableTextTexture(Bitmap bitmap, int count, int[] rects, char[] characters,
float[] widths, float height, float maxWidth) {
super(bitmap, false, count, rects);
mCharacters = characters;
mWidths = widths;
mHeight = height;
mMaxWidth = maxWidth;
}
/**
* Create a TextTexture to draw text
*
* @param typeface the typeface
* @param size text size
* @param characters all Characters
* @return the TextTexture
*/
public static MovableTextTexture create(Typeface typeface, int size, int color, char[] characters) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(size);
paint.setColor(color);
paint.setTypeface(typeface);
Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
int fixed = fmi.bottom;
int height = fmi.bottom - fmi.top;
int length = characters.length;
float[] widths = new float[length];
paint.getTextWidths(characters, 0, length, widths);
// Calculate bitmap size
float maxWidth = 0.0f;
for (float f : widths) {
maxWidth = Math.max(maxWidth, f);
}
int hCount = (int) Math.ceil(Math.sqrt(height / maxWidth * length));
int vCount = (int) Math.ceil(Math.sqrt(maxWidth / height * length));
if (hCount * (vCount - 1) > length) {
vCount--;
}
if ((hCount - 1) * vCount > length) {
hCount--;
}
Bitmap bitmap = Bitmap.createBitmap((int) Math.ceil(hCount * maxWidth),
(int) Math.ceil(vCount * height), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.translate(0, height - fixed);
// Draw
int[] rects = new int[length * 4];
int x = 0;
int y = 0;
for (int i = 0; i < length; i++) {
int offset = i * 4;
rects[offset] = x;
rects[offset + 1] = y;
rects[offset + 2] = (int) widths[i];
rects[offset + 3] = height;
canvas.drawText(characters, i, 1, x, y, paint);
if (i % hCount == hCount - 1) {
// The end of row
x = 0;
y += height;
} else {
x += maxWidth;
}
}
return new MovableTextTexture(bitmap, length, rects, characters, widths, height, maxWidth);
}
public int[] getTextIndexes(String text) {
char[] characters = mCharacters;
int length = text.length();
int[] indexes = new int[length];
for (int i = 0; i < length; i++) {
char ch = text.charAt(i);
indexes[i] = Arrays.binarySearch(characters, ch);
}
return indexes;
}
public float getTextWidth(String text) {
char[] characters = mCharacters;
float[] widths = mWidths;
float width = 0.0f;
for (int i = 0, n = text.length(); i < n; i++) {
char ch = text.charAt(i);
int index = Arrays.binarySearch(characters, ch);
if (index >= 0) {
width += widths[index];
} else {
width += mMaxWidth;
}
}
return width;
}
public float getTextWidth(int[] indexes) {
float[] widths = mWidths;
float width = 0.0f;
int length = indexes.length;
for (int i = 0; i < length; i++) {
int index = indexes[i];
if (index >= 0) {
width += widths[index];
} else {
width += mMaxWidth;
}
}
return width;
}
public float getMaxWidth() {
return mMaxWidth;
}
public float getTextHeight() {
return mHeight;
}
public void drawText(GLCanvas canvas, String text, int x, int y) {
char[] characters = mCharacters;
float[] widths = mWidths;
for (int i = 0, n = text.length(); i < n; i++) {
char ch = text.charAt(i);
int index = Arrays.binarySearch(characters, ch);
if (index >= 0) {
drawSprite(canvas, index, x, y);
x += widths[index];
} else {
x += mMaxWidth;
}
}
}
public void drawText(GLCanvas canvas, int[] indexes, int x, int y) {
float[] widths = mWidths;
for (int i = 0, n = indexes.length; i < n; i++) {
int index = indexes[i];
if (index >= 0) {
drawSprite(canvas, index, x, y);
x += widths[index];
} else {
x += mMaxWidth;
}
}
}
}
@@ -1,52 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
// MultiLineTexture is a texture shows the content of a specified String.
//
// To create a MultiLineTexture, use the newInstance() method and specify
// the String, the font size, and the color.
class MultiLineTexture extends CanvasTexture {
private final Layout mLayout;
private MultiLineTexture(Layout layout) {
super(layout.getWidth(), layout.getHeight());
mLayout = layout;
}
public static MultiLineTexture newInstance(
String text, int maxWidth, float textSize, int color,
Layout.Alignment alignment) {
TextPaint paint = StringTexture.getDefaultPaint(textSize, color);
Layout layout = new StaticLayout(text, 0, text.length(), paint,
maxWidth, alignment, 1, 0, true, null, 0);
return new MultiLineTexture(layout);
}
@Override
protected void onDraw(Canvas canvas, Bitmap backing) {
mLayout.draw(canvas);
}
}
@@ -1,103 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.opengl.GLES20;
import android.util.Log;
import javax.microedition.khronos.opengles.GL11;
public abstract class NativeTexture extends BasicTexture {
private static final String TAG = NativeTexture.class.getSimpleName();
private boolean mContentValid = true;
private boolean mOpaque = true;
public static void checkError() {
int error = GLES20.glGetError();
if (error != 0) {
Throwable t = new Throwable();
Log.e(TAG, "GL error: " + error, t);
}
}
public void invalidateContent() {
mContentValid = false;
}
protected abstract void texImage(boolean init);
private void uploadToCanvas(GLCanvas canvas) {
// Get id
mId = canvas.getGLId().generateTexture();
// Prepare
canvas.setTextureParameters(this);
// Call glTexImage2D
GLES20.glBindTexture(getTarget(), mId);
checkError();
texImage(true);
checkError();
setAssociatedCanvas(canvas);
mState = STATE_LOADED;
mContentValid = true;
}
public void updateContent(GLCanvas canvas) {
if (!isLoaded()) {
uploadToCanvas(canvas);
} else if (!mContentValid) {
// Call glTexSubImage2D
GLES20.glBindTexture(getTarget(), mId);
checkError();
texImage(false);
checkError();
mContentValid = true;
}
}
/**
* Whether the content on GPU is valid.
*/
public boolean isContentValid() {
return isLoaded() && mContentValid;
}
@Override
protected boolean onBind(GLCanvas canvas) {
updateContent(canvas);
return isContentValid();
}
@Override
protected int getTarget() {
return GL11.GL_TEXTURE_2D;
}
@Override
public boolean isOpaque() {
return mOpaque;
}
public void setOpaque(boolean isOpaque) {
mOpaque = isOpaque;
}
}
@@ -1,82 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Rect;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
// NinePatch chunk.
class NinePatchChunk {
public static final int NO_COLOR = 0x00000001;
public static final int TRANSPARENT_COLOR = 0x00000000;
public Rect mPaddings = new Rect();
public int[] mDivX;
public int[] mDivY;
public int[] mColor;
private static void readIntArray(int[] data, ByteBuffer buffer) {
for (int i = 0, n = data.length; i < n; ++i) {
data[i] = buffer.getInt();
}
}
private static void checkDivCount(int length) {
if (length == 0 || (length & 0x01) != 0) {
throw new RuntimeException("invalid nine-patch: " + length);
}
}
public static NinePatchChunk deserialize(byte[] data) {
ByteBuffer byteBuffer =
ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
byte wasSerialized = byteBuffer.get();
if (wasSerialized == 0) return null;
NinePatchChunk chunk = new NinePatchChunk();
chunk.mDivX = new int[byteBuffer.get()];
chunk.mDivY = new int[byteBuffer.get()];
chunk.mColor = new int[byteBuffer.get()];
checkDivCount(chunk.mDivX.length);
checkDivCount(chunk.mDivY.length);
// skip 8 bytes
byteBuffer.getInt();
byteBuffer.getInt();
chunk.mPaddings.left = byteBuffer.getInt();
chunk.mPaddings.right = byteBuffer.getInt();
chunk.mPaddings.top = byteBuffer.getInt();
chunk.mPaddings.bottom = byteBuffer.getInt();
// skip 4 bytes
byteBuffer.getInt();
readIntArray(chunk.mDivX, byteBuffer);
readIntArray(chunk.mDivY, byteBuffer);
readIntArray(chunk.mColor, byteBuffer);
return chunk;
}
}
@@ -1,424 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import com.hippo.yorozuya.MathUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
// NinePatchTexture is a texture backed by a NinePatch resource.
//
// getPaddings() returns paddings specified in the NinePatch.
// getNinePatchChunk() returns the layout data specified in the NinePatch.
//
public class NinePatchTexture extends ResourceTexture {
@SuppressWarnings("unused")
private static final String TAG = "NinePatchTexture";
private final SmallCache<NinePatchInstance> mInstanceCache
= new SmallCache<NinePatchInstance>();
private NinePatchChunk mChunk;
public NinePatchTexture(Context context, int resId) {
super(context, resId);
}
@Override
protected Bitmap onGetBitmap() {
if (mBitmap != null) return mBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeResource(
mContext.getResources(), mResId, options);
mBitmap = bitmap;
setSize(bitmap.getWidth(), bitmap.getHeight());
byte[] chunkData = bitmap.getNinePatchChunk();
mChunk = chunkData == null
? null
: NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
if (mChunk == null) {
throw new RuntimeException("invalid nine-patch image: " + mResId);
}
return bitmap;
}
public Rect getPaddings() {
// get the paddings from nine patch
if (mChunk == null) onGetBitmap();
return mChunk.mPaddings;
}
public NinePatchChunk getNinePatchChunk() {
if (mChunk == null) onGetBitmap();
return mChunk;
}
private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
int key = w;
key = (key << 16) | h;
NinePatchInstance instance = mInstanceCache.get(key);
if (instance == null) {
instance = new NinePatchInstance(this, w, h);
NinePatchInstance removed = mInstanceCache.put(key, instance);
if (removed != null) {
removed.recycle(canvas);
}
}
return instance;
}
@Override
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
if (!isLoaded()) {
mInstanceCache.clear();
}
if (w != 0 && h != 0) {
findInstance(canvas, w, h).draw(canvas, this, x, y);
}
}
@Override
public void recycle() {
super.recycle();
GLCanvas canvas = mCanvasRef;
if (canvas == null) return;
int n = mInstanceCache.size();
for (int i = 0; i < n; i++) {
NinePatchInstance instance = mInstanceCache.valueAt(i);
instance.recycle(canvas);
}
mInstanceCache.clear();
}
// This is a simple cache for a small number of things. Linear search
// is used because the cache is small. It also tries to remove less used
// item when the cache is full by moving the often-used items to the front.
private static class SmallCache<V> {
private static final int CACHE_SIZE = 16;
private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
private final int[] mKey = new int[CACHE_SIZE];
private final V[] mValue = (V[]) new Object[CACHE_SIZE];
private int mCount; // number of items in this cache
// Puts a value into the cache. If the cache is full, also returns
// a less used item, otherwise returns null.
public V put(int key, V value) {
if (mCount == CACHE_SIZE) {
V old = mValue[CACHE_SIZE - 1]; // remove the last item
mKey[CACHE_SIZE - 1] = key;
mValue[CACHE_SIZE - 1] = value;
return old;
} else {
mKey[mCount] = key;
mValue[mCount] = value;
mCount++;
return null;
}
}
public V get(int key) {
for (int i = 0; i < mCount; i++) {
if (mKey[i] == key) {
// Move the accessed item one position to the front, so it
// will less likely to be removed when cache is full. Only
// do this if the cache is starting to get full.
if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
int tmpKey = mKey[i];
mKey[i] = mKey[i - 1];
mKey[i - 1] = tmpKey;
V tmpValue = mValue[i];
mValue[i] = mValue[i - 1];
mValue[i - 1] = tmpValue;
}
return mValue[i];
}
}
return null;
}
public void clear() {
for (int i = 0; i < mCount; i++) {
mValue[i] = null; // make sure it's can be garbage-collected.
}
mCount = 0;
}
public int size() {
return mCount;
}
public V valueAt(int i) {
return mValue[i];
}
}
}
// This keeps data for a specialization of NinePatchTexture with the size
// (width, height). We pre-compute the coordinates for efficiency.
class NinePatchInstance {
@SuppressWarnings("unused")
private static final String TAG = "NinePatchInstance";
// We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
private static final int VERTEX_BUFFER_SIZE = 16 * 2;
// We need 22 indices for a normal nine-patch image, plus 2 for each
// transparent region. Current there are at most 1 transparent region.
private static final int INDEX_BUFFER_SIZE = 22 + 2;
private FloatBuffer mXyBuffer;
private FloatBuffer mUvBuffer;
private ByteBuffer mIndexBuffer;
// Names for buffer names: xy, uv, index.
private int mXyBufferName = -1;
private int mUvBufferName;
private int mIndexBufferName;
private int mIdxCount;
public NinePatchInstance(NinePatchTexture tex, int width, int height) {
NinePatchChunk chunk = tex.getNinePatchChunk();
if (width <= 0 || height <= 0) {
throw new RuntimeException("invalid dimension");
}
// The code should be easily extended to handle the general cases by
// allocating more space for buffers. But let's just handle the only
// use case.
if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
throw new RuntimeException("unsupported nine patch");
}
float[] divX = new float[4];
float[] divY = new float[4];
float[] divU = new float[4];
float[] divV = new float[4];
int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
}
/**
* Stretches the texture according to the nine-patch rules. It will
* linearly distribute the strechy parts defined in the nine-patch chunk to
* the target area.
*
* <pre>
* source
* /--------------^---------------\
* u0 u1 u2 u3 u4 u5
* div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
* | div0 div1 div2 div3 |
* | | / / / /
* | | / / / /
* | | / / / /
* |fffff|ssss|fff|sss|ffff| ---> x
* x0 x1 x2 x3 x4 x5
* \----------v------------/
* target
*
* f: fixed segment
* s: stretchy segment
* </pre>
*
* @param div the stretch parts defined in nine-patch chunk
* @param source the length of the texture
* @param target the length on the drawing plan
* @param u output, the positions of these dividers in the texture
* coordinate
* @param x output, the corresponding position of these dividers on the
* drawing plan
* @return the number of these dividers.
*/
private static int stretch(
float[] x, float[] u, int[] div, int source, int target) {
int textureSize = MathUtils.nextPowerOf2(source);
float textureBound = (float) source / textureSize;
float stretch = 0;
for (int i = 0, n = div.length; i < n; i += 2) {
stretch += div[i + 1] - div[i];
}
float remaining = target - source + stretch;
float lastX = 0;
float lastU = 0;
x[0] = 0;
u[0] = 0;
for (int i = 0, n = div.length; i < n; i += 2) {
// Make the stretchy segment a little smaller to prevent sampling
// on neighboring fixed segments.
// fixed segment
x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
// stretchy segment
float partU = div[i + 1] - div[i];
float partX = remaining * partU / stretch;
remaining -= partX;
stretch -= partU;
lastX = x[i + 1] + partX;
lastU = div[i + 1];
x[i + 2] = lastX - 0.5f;
u[i + 2] = Math.min((lastU - 0.5f) / textureSize, textureBound);
}
// the last fixed segment
x[div.length + 1] = target;
u[div.length + 1] = textureBound;
// remove segments with length 0.
int last = 0;
for (int i = 1, n = div.length + 2; i < n; ++i) {
if ((x[i] - x[last]) < 1f) continue;
x[++last] = x[i];
u[last] = u[i];
}
return last + 1;
}
private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
}
private void prepareVertexData(float[] x, float[] y, float[] u, float[] v,
int nx, int ny, int[] color) {
/*
* Given a 3x3 nine-patch image, the vertex order is defined as the
* following graph:
*
* (0) (1) (2) (3)
* | /| /| /|
* | / | / | / |
* (4) (5) (6) (7)
* | \ | \ | \ |
* | \| \| \|
* (8) (9) (A) (B)
* | /| /| /|
* | / | / | / |
* (C) (D) (E) (F)
*
* And we draw the triangle strip in the following index order:
*
* index: 04152637B6A5948C9DAEBF
*/
int pntCount = 0;
float[] xy = new float[VERTEX_BUFFER_SIZE];
float[] uv = new float[VERTEX_BUFFER_SIZE];
for (int j = 0; j < ny; ++j) {
for (int i = 0; i < nx; ++i) {
int xIndex = (pntCount++) << 1;
int yIndex = xIndex + 1;
xy[xIndex] = x[i];
xy[yIndex] = y[j];
uv[xIndex] = u[i];
uv[yIndex] = v[j];
}
}
int idxCount = 1;
boolean isForward = false;
byte[] index = new byte[INDEX_BUFFER_SIZE];
for (int row = 0; row < ny - 1; row++) {
--idxCount;
isForward = !isForward;
int start, end, inc;
if (isForward) {
start = 0;
end = nx;
inc = 1;
} else {
start = nx - 1;
end = -1;
inc = -1;
}
for (int col = start; col != end; col += inc) {
int k = row * nx + col;
if (col != start) {
int colorIdx = row * (nx - 1) + col;
if (isForward) colorIdx--;
if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
index[idxCount] = index[idxCount - 1];
++idxCount;
index[idxCount++] = (byte) k;
}
}
index[idxCount++] = (byte) k;
index[idxCount++] = (byte) (k + nx);
}
}
mIdxCount = idxCount;
int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
mXyBuffer.put(xy, 0, pntCount * 2).position(0);
mUvBuffer.put(uv, 0, pntCount * 2).position(0);
mIndexBuffer.put(index, 0, idxCount).position(0);
}
private void prepareBuffers(GLCanvas canvas) {
mXyBufferName = canvas.uploadBuffer(mXyBuffer);
mUvBufferName = canvas.uploadBuffer(mUvBuffer);
mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
// These buffers are never used again.
mXyBuffer = null;
mUvBuffer = null;
mIndexBuffer = null;
}
public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
if (mXyBufferName == -1) {
prepareBuffers(canvas);
}
canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
}
public void recycle(GLCanvas canvas) {
if (mXyBuffer == null) {
canvas.deleteBuffer(mXyBufferName);
canvas.deleteBuffer(mUvBufferName);
canvas.deleteBuffer(mIndexBufferName);
mXyBufferName = -1;
}
}
}
@@ -1,73 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.util.Log;
import javax.microedition.khronos.opengles.GL11;
public class RawTexture extends BasicTexture {
private static final String TAG = "RawTexture";
private final boolean mOpaque;
private boolean mIsFlipped;
public RawTexture(int width, int height, boolean opaque) {
mOpaque = opaque;
setSize(width, height);
}
@Override
public boolean isOpaque() {
return mOpaque;
}
@Override
public boolean isFlippedVertically() {
return mIsFlipped;
}
public void setIsFlippedVertically(boolean isFlipped) {
mIsFlipped = isFlipped;
}
protected void prepare(GLCanvas canvas) {
GLId glId = canvas.getGLId();
mId = glId.generateTexture();
canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
canvas.setTextureParameters(this);
mState = STATE_LOADED;
setAssociatedCanvas(canvas);
}
@Override
protected boolean onBind(GLCanvas canvas) {
if (isLoaded()) return true;
Log.w(TAG, "lost the content due to context change");
return false;
}
@Override
public void yield() {
// we cannot free the texture because we have no backup.
}
@Override
protected int getTarget() {
return GL11.GL_TEXTURE_2D;
}
}
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
// ResourceTexture is a texture whose Bitmap is decoded from a resource.
// By default ResourceTexture is not opaque.
public class ResourceTexture extends UploadedTexture {
protected final Context mContext;
protected final int mResId;
public ResourceTexture(Context context, int resId) {
if (context == null) {
throw new IllegalArgumentException("context == null");
}
mContext = context;
mResId = resId;
setOpaque(false);
}
@Override
protected Bitmap onGetBitmap() {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeResource(
mContext.getResources(), mResId, options);
}
@Override
protected void onFreeBitmap(Bitmap bitmap) {
if (!inFinalizer()) {
bitmap.recycle();
}
}
}
@@ -1,91 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import android.graphics.RectF;
import com.hippo.yorozuya.AssertUtils;
public class SpriteTexture extends TiledTexture {
private final int mCount;
private final int[] mRects;
private final RectF mTempSource = new RectF();
private final RectF mTempTarget = new RectF();
public SpriteTexture(Bitmap bitmap, boolean isOpaque, int count, int[] rects) {
super(bitmap, isOpaque);
AssertUtils.assertEquals("rects.length must be count * 4", count * 4, rects.length);
mCount = count;
mRects = rects;
}
public int getCount() {
return mCount;
}
public void drawSprite(GLCanvas canvas, int index, int x, int y) {
int[] rects = mRects;
int offset = index * 4;
int sourceX = rects[offset];
int sourceY = rects[offset + 1];
int sourceWidth = rects[offset + 2];
int sourceHeight = rects[offset + 3];
mTempSource.set(sourceX, sourceY, sourceX + sourceWidth, sourceY + sourceHeight);
mTempTarget.set(x, y, x + sourceWidth, y + sourceHeight);
draw(canvas, mTempSource, mTempTarget);
}
public void drawSprite(GLCanvas canvas, int index, int x, int y, int width, int height) {
int[] rects = mRects;
int offset = index * 4;
int sourceX = rects[offset];
int sourceY = rects[offset + 1];
mTempSource.set(sourceX, sourceY, sourceX + rects[offset + 2], sourceY + rects[offset + 3]);
mTempTarget.set(x, y, x + width, y + height);
draw(canvas, mTempSource, mTempTarget);
}
public void drawSpriteMixed(GLCanvas canvas, int index, int color, float ratio, int x, int y) {
int[] rects = mRects;
int offset = index * 4;
int sourceX = rects[offset];
int sourceY = rects[offset + 1];
int sourceWidth = rects[offset + 2];
int sourceHeight = rects[offset + 3];
mTempSource.set(sourceX, sourceY, sourceX + sourceWidth, sourceY + sourceHeight);
mTempTarget.set(x, y, x + sourceWidth, y + sourceHeight);
drawMixed(canvas, color, ratio, mTempSource, mTempTarget);
}
public void drawSpriteMixed(GLCanvas canvas, int index, int color, float ratio,
int x, int y, int width, int height) {
int[] rects = mRects;
int offset = index * 4;
int sourceX = rects[offset];
int sourceY = rects[offset + 1];
mTempSource.set(sourceX, sourceY, sourceX + rects[offset + 2],
sourceY + rects[offset + 3]);
mTempTarget.set(x, y, x + width, y + height);
drawMixed(canvas, color, ratio, mTempSource, mTempTarget);
}
}
@@ -1,87 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.TextUtils;
// StringTexture is a texture shows the content of a specified String.
//
// To create a StringTexture, use the newInstance() method and specify
// the String, the font size, and the color.
public class StringTexture extends CanvasTexture {
private final String mText;
private final TextPaint mPaint;
private final FontMetricsInt mMetrics;
private StringTexture(String text, TextPaint paint,
FontMetricsInt metrics, int width, int height) {
super(width, height);
mText = text;
mPaint = paint;
mMetrics = metrics;
}
public static TextPaint getDefaultPaint(float textSize, int color) {
TextPaint paint = new TextPaint();
paint.setTextSize(textSize);
paint.setAntiAlias(true);
paint.setColor(color);
paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
return paint;
}
public static StringTexture newInstance(
String text, float textSize, int color) {
return newInstance(text, getDefaultPaint(textSize, color));
}
public static StringTexture newInstance(
String text, float textSize, int color,
float lengthLimit, boolean isBold) {
TextPaint paint = getDefaultPaint(textSize, color);
if (isBold) {
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
}
if (lengthLimit > 0) {
text = TextUtils.ellipsize(
text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
}
return newInstance(text, paint);
}
private static StringTexture newInstance(String text, TextPaint paint) {
FontMetricsInt metrics = paint.getFontMetricsInt();
int width = (int) Math.ceil(paint.measureText(text));
int height = metrics.bottom - metrics.top;
// The texture size needs to be at least 1x1.
if (width <= 0) width = 1;
if (height <= 0) height = 1;
return new StringTexture(text, paint, metrics, width, height);
}
@Override
protected void onDraw(Canvas canvas, Bitmap backing) {
canvas.translate(0, -mMetrics.ascent);
canvas.drawText(mText, 0, 0, mPaint);
}
}
@@ -1,52 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.RectF;
// Texture is a rectangular image which can be drawn on GLCanvas.
// The isOpaque() function gives a hint about whether the texture is opaque,
// so the drawing can be done faster.
//
// This is the current texture hierarchy:
//
// Texture
// -- ColorTexture
// -- FadeInTexture
// -- BasicTexture
// -- UploadedTexture
// -- BitmapTexture
// -- Tile
// -- ResourceTexture
// -- NinePatchTexture
// -- CanvasTexture
// -- StringTexture
//
public interface Texture {
int getWidth();
int getHeight();
void draw(GLCanvas canvas, int x, int y);
void draw(GLCanvas canvas, int x, int y, int w, int h);
void draw(GLCanvas canvas, RectF source, RectF target);
boolean isOpaque();
}
@@ -1,104 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import com.hippo.glview.view.GLRoot;
import java.util.ArrayDeque;
public class TextureUploader implements GLRoot.OnGLIdleListener {
private static final int INIT_CAPACITY = 64;
private static final int QUOTA_PER_FRAME = 1;
private final ArrayDeque<UploadedTexture> mFgTextures =
new ArrayDeque<>(INIT_CAPACITY);
private final ArrayDeque<UploadedTexture> mBgTextures =
new ArrayDeque<>(INIT_CAPACITY);
private final GLRoot mGLRoot;
private volatile boolean mIsQueued = false;
public TextureUploader(GLRoot root) {
mGLRoot = root;
}
public synchronized void clear() {
while (!mFgTextures.isEmpty()) {
mFgTextures.pop().setIsUploading(false);
}
while (!mBgTextures.isEmpty()) {
mBgTextures.pop().setIsUploading(false);
}
}
// caller should hold synchronized on "this"
private void queueSelfIfNeed() {
if (mIsQueued) return;
mIsQueued = true;
mGLRoot.addOnGLIdleListener(this);
}
public synchronized void addBgTexture(UploadedTexture t) {
if (t.isContentValid()) return;
mBgTextures.addLast(t);
t.setIsUploading(true);
queueSelfIfNeed();
}
public synchronized void addFgTexture(UploadedTexture t) {
if (t.isContentValid()) return;
mFgTextures.addLast(t);
t.setIsUploading(true);
queueSelfIfNeed();
}
private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
int uploadQuota, boolean isBackground) {
while (uploadQuota > 0) {
UploadedTexture t;
synchronized (this) {
if (deque.isEmpty()) break;
t = deque.removeFirst();
t.setIsUploading(false);
if (t.isContentValid()) continue;
// this has to be protected by the synchronized block
// to prevent the inner bitmap get recycled
t.updateContent(canvas);
}
// It will took some more time for a texture to be drawn for
// the first time.
// Thus, when scrolling, if a new column appears on screen,
// it may cause a UI jank even these textures are uploaded.
if (isBackground) t.draw(canvas, 0, 0);
--uploadQuota;
}
return uploadQuota;
}
@Override
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
int uploadQuota = QUOTA_PER_FRAME;
uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
upload(canvas, mBgTextures, uploadQuota, true);
synchronized (this) {
mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
return mIsQueued;
}
}
}
@@ -1,370 +0,0 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.os.SystemClock;
import com.hippo.glview.view.GLRoot;
import java.util.ArrayDeque;
import java.util.ArrayList;
// This class is similar to BitmapTexture, except the bitmap is
// split into tiles. By doing so, we may increase the time required to
// upload the whole bitmap but we reduce the time of uploading each tile
// so it make the animation more smooth and prevents jank.
public class TiledTexture implements Texture {
private static final int CONTENT_SIZE = 254;
private static final int BORDER_SIZE = 1;
private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
private static final int INIT_CAPACITY = 8;
// We are targeting at 60fps, so we have 16ms for each frame.
// In this 16ms, we use about 4~8 ms to upload tiles.
private static final long UPLOAD_TILE_LIMIT = 4; // ms
private static final Object sFreeTileLock = new Object();
private static final Bitmap sUploadBitmap;
private static final Canvas sCanvas;
private static final Paint sBitmapPaint;
private static final Paint sPaint;
private static Tile sFreeTileHead = null;
static {
sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888);
sCanvas = new Canvas(sUploadBitmap);
sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
sBitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
sPaint = new Paint();
sPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
sPaint.setColor(Color.TRANSPARENT);
}
private final Tile[] mTiles; // Can be modified in different threads.
// Should be protected by "synchronized."
private final int mWidth;
private final int mHeight;
private final RectF mSrcRect = new RectF();
private final RectF mDestRect = new RectF();
private int mUploadIndex = 0;
public TiledTexture(Bitmap bitmap, boolean isOpaque) {
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
ArrayList<Tile> list = new ArrayList<>();
for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
Tile tile = obtainTile();
tile.offsetX = x;
tile.offsetY = y;
tile.bitmap = bitmap;
tile.setSize(
Math.min(CONTENT_SIZE, mWidth - x),
Math.min(CONTENT_SIZE, mHeight - y));
tile.setOpaque(isOpaque);
list.add(tile);
}
}
mTiles = list.toArray(new Tile[list.size()]);
}
private static void freeTile(Tile tile) {
tile.invalidateContent();
tile.bitmap = null;
synchronized (sFreeTileLock) {
tile.nextFreeTile = sFreeTileHead;
sFreeTileHead = tile;
}
}
private static Tile obtainTile() {
synchronized (sFreeTileLock) {
Tile result = sFreeTileHead;
if (result == null) return new Tile();
sFreeTileHead = result.nextFreeTile;
result.nextFreeTile = null;
return result;
}
}
// We want to draw the "source" on the "target".
// This method is to find the "output" rectangle which is
// the corresponding area of the "src".
// (x,y) target
// (x0,y0) source +---------------+
// +----------+ | |
// | src | | output |
// | +--+ | linear map | +----+ |
// | +--+ | ----------> | | | |
// | | by (scaleX, scaleY) | +----+ |
// +----------+ | |
// Texture +---------------+
// Canvas
private static void mapRect(RectF output,
RectF src, float x0, float y0, float x, float y, float scaleX,
float scaleY) {
output.set(x + (src.left - x0) * scaleX,
y + (src.top - y0) * scaleY,
x + (src.right - x0) * scaleX,
y + (src.bottom - y0) * scaleY);
}
private boolean uploadNextTile(GLCanvas canvas) {
if (mUploadIndex == mTiles.length) return true;
synchronized (mTiles) {
Tile next = mTiles[mUploadIndex++];
// Make sure tile has not already been recycled by the time
// this is called (race condition in onGLIdle)
if (next.bitmap != null) {
boolean hasBeenLoad = next.isLoaded();
next.updateContent(canvas);
// It will take some time for a texture to be drawn for the first
// time. When scrolling, we need to draw several tiles on the screen
// at the same time. It may cause a UI jank even these textures has
// been uploaded.
if (!hasBeenLoad) next.draw(canvas, 0, 0);
}
}
return mUploadIndex == mTiles.length;
}
public boolean isReady() {
return mUploadIndex == mTiles.length;
}
// Can be called in UI thread.
public void recycle() {
synchronized (mTiles) {
for (int i = 0, n = mTiles.length; i < n; ++i) {
freeTile(mTiles[i]);
}
}
}
// Draws a mixed color of this texture and a specified color onto the
// a rectangle. The used color is: from * (1 - ratio) + to * ratio.
public void drawMixed(GLCanvas canvas, int color, float ratio,
int x, int y, int width, int height) {
RectF src = mSrcRect;
RectF dest = mDestRect;
float scaleX = (float) width / mWidth;
float scaleY = (float) height / mHeight;
synchronized (mTiles) {
for (int i = 0, n = mTiles.length; i < n; ++i) {
Tile t = mTiles[i];
src.set(0, 0, t.contentWidth, t.contentHeight);
src.offset(t.offsetX, t.offsetY);
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
}
}
}
public void drawMixed(GLCanvas canvas, int color, float ratio,
RectF source, RectF target) {
RectF src = mSrcRect;
RectF dest = mDestRect;
float x0 = source.left;
float y0 = source.top;
float x = target.left;
float y = target.top;
float scaleX = target.width() / source.width();
float scaleY = target.height() / source.height();
synchronized (mTiles) {
for (int i = 0, n = mTiles.length; i < n; ++i) {
Tile t = mTiles[i];
src.set(0, 0, t.contentWidth, t.contentHeight);
src.offset(t.offsetX, t.offsetY);
if (!src.intersect(source)) continue;
mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
}
}
}
// Draws the texture on to the specified rectangle.
@Override
public void draw(GLCanvas canvas, int x, int y, int width, int height) {
RectF src = mSrcRect;
RectF dest = mDestRect;
float scaleX = (float) width / mWidth;
float scaleY = (float) height / mHeight;
synchronized (mTiles) {
for (int i = 0, n = mTiles.length; i < n; ++i) {
Tile t = mTiles[i];
src.set(0, 0, t.contentWidth, t.contentHeight);
src.offset(t.offsetX, t.offsetY);
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
canvas.drawTexture(t, mSrcRect, mDestRect);
}
}
}
// Draws a sub region of this texture on to the specified rectangle.
@Override
public void draw(GLCanvas canvas, RectF source, RectF target) {
RectF src = mSrcRect;
RectF dest = mDestRect;
float x0 = source.left;
float y0 = source.top;
float x = target.left;
float y = target.top;
float scaleX = target.width() / source.width();
float scaleY = target.height() / source.height();
synchronized (mTiles) {
for (int i = 0, n = mTiles.length; i < n; ++i) {
Tile t = mTiles[i];
src.set(0, 0, t.contentWidth, t.contentHeight);
src.offset(t.offsetX, t.offsetY);
if (!src.intersect(source)) continue;
mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
canvas.drawTexture(t, src, dest);
}
}
}
@Override
public int getWidth() {
return mWidth;
}
@Override
public int getHeight() {
return mHeight;
}
@Override
public void draw(GLCanvas canvas, int x, int y) {
draw(canvas, x, y, mWidth, mHeight);
}
@Override
public boolean isOpaque() {
return false;
}
public static class Uploader implements GLRoot.OnGLIdleListener {
private final ArrayDeque<TiledTexture> mTextures =
new ArrayDeque<>(INIT_CAPACITY);
private final GLRoot mGlRoot;
private boolean mIsQueued = false;
public Uploader(GLRoot glRoot) {
mGlRoot = glRoot;
}
public synchronized void clear() {
mTextures.clear();
}
public synchronized void addTexture(TiledTexture t) {
if (t.isReady()) return;
mTextures.addLast(t);
if (mIsQueued) return;
mIsQueued = true;
mGlRoot.addOnGLIdleListener(this);
}
@Override
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
ArrayDeque<TiledTexture> deque = mTextures;
synchronized (this) {
long now = SystemClock.uptimeMillis();
long dueTime = now + UPLOAD_TILE_LIMIT;
while (now < dueTime && !deque.isEmpty()) {
TiledTexture t = deque.peekFirst();
if (t.uploadNextTile(canvas)) {
deque.removeFirst();
mGlRoot.requestRender();
}
now = SystemClock.uptimeMillis();
}
mIsQueued = !mTextures.isEmpty();
// return true to keep this listener in the queue
return mIsQueued;
}
}
}
private static class Tile extends UploadedTexture {
public int offsetX;
public int offsetY;
public Bitmap bitmap;
public Tile nextFreeTile;
public int contentWidth;
public int contentHeight;
@Override
public void setSize(int width, int height) {
contentWidth = width;
contentHeight = height;
mWidth = width + 2 * BORDER_SIZE;
mHeight = height + 2 * BORDER_SIZE;
mTextureWidth = TILE_SIZE;
mTextureHeight = TILE_SIZE;
}
@Override
protected Bitmap onGetBitmap() {
// make a local copy of the reference to the bitmap,
// since it might be null'd in a different thread. b/8694871
Bitmap localBitmapRef = bitmap;
bitmap = null;
if (localBitmapRef != null) {
int x = BORDER_SIZE - offsetX;
int y = BORDER_SIZE - offsetY;
int r = localBitmapRef.getWidth() + x;
int b = localBitmapRef.getHeight() + y;
sCanvas.drawBitmap(localBitmapRef, x, y, sBitmapPaint);
localBitmapRef = null;
// draw borders if need
if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
}
return sUploadBitmap;
}
@Override
protected void onFreeBitmap(Bitmap bitmap) {
// do nothing
}
}
}
@@ -1,296 +0,0 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.opengl.GLUtils;
import java.util.HashMap;
import javax.microedition.khronos.opengles.GL11;
// UploadedTextures use a Bitmap for the content of the texture.
//
// Subclasses should implement onGetBitmap() to provide the Bitmap and
// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
// is not needed anymore.
//
// isContentValid() is meaningful only when the isLoaded() returns true.
// It means whether the content needs to be updated.
//
// The user of this class should call recycle() when the texture is not
// needed anymore.
//
// By default an UploadedTexture is opaque (so it can be drawn faster without
// blending). The user or subclass can override it using setOpaque().
public abstract class UploadedTexture extends BasicTexture {
// To prevent keeping allocation the borders, we store those used borders here.
// Since the length will be power of two, it won't use too much memory.
private static final HashMap<BorderKey, Bitmap> sBorderLines =
new HashMap<>();
private static final BorderKey sBorderKey = new BorderKey();
@SuppressWarnings("unused")
private static final String TAG = "Texture";
private static final int UPLOAD_LIMIT = 100;
private static int sUploadedCount;
protected Bitmap mBitmap;
private boolean mContentValid = true;
// indicate this textures is being uploaded in background
private boolean mIsUploading = false;
private boolean mOpaque = true;
private boolean mThrottled = false;
private int mBorder;
protected UploadedTexture() {
this(false);
}
protected UploadedTexture(boolean hasBorder) {
super(null, 0, STATE_UNLOADED);
if (hasBorder) {
setBorder(true);
mBorder = 1;
}
}
private static Bitmap getBorderLine(
boolean vertical, Config config, int length) {
BorderKey key = sBorderKey;
key.vertical = vertical;
key.config = config;
key.length = length;
Bitmap bitmap = sBorderLines.get(key);
if (bitmap == null) {
bitmap = vertical
? Bitmap.createBitmap(1, length, config)
: Bitmap.createBitmap(length, 1, config);
sBorderLines.put(key.clone(), bitmap);
}
return bitmap;
}
public static void resetUploadLimit() {
sUploadedCount = 0;
}
public static boolean uploadLimitReached() {
return sUploadedCount > UPLOAD_LIMIT;
}
protected void setIsUploading(boolean uploading) {
mIsUploading = uploading;
}
public boolean isUploading() {
return mIsUploading;
}
protected void setThrottled(boolean throttled) {
mThrottled = throttled;
}
private Bitmap getBitmap() {
if (mBitmap == null) {
mBitmap = onGetBitmap();
if (mWidth == UNSPECIFIED) {
int w = mBitmap.getWidth() + mBorder * 2;
int h = mBitmap.getHeight() + mBorder * 2;
setSize(w, h);
}
}
return mBitmap;
}
private void freeBitmap() {
if (mBitmap == null) {
throw new IllegalStateException("mBitmap == null");
}
onFreeBitmap(mBitmap);
mBitmap = null;
}
@Override
public int getWidth() {
if (mWidth == UNSPECIFIED) getBitmap();
return mWidth;
}
@Override
public int getHeight() {
if (mWidth == UNSPECIFIED) getBitmap();
return mHeight;
}
protected abstract Bitmap onGetBitmap();
protected abstract void onFreeBitmap(Bitmap bitmap);
public void invalidateContent() {
if (mBitmap != null) freeBitmap();
mContentValid = false;
mWidth = UNSPECIFIED;
mHeight = UNSPECIFIED;
}
/**
* Whether the content on GPU is valid.
*/
public boolean isContentValid() {
return isLoaded() && mContentValid;
}
/**
* Updates the content on GPU's memory.
*
* @param canvas
*/
public void updateContent(GLCanvas canvas) {
if (!isLoaded()) {
if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
return;
}
uploadToCanvas(canvas);
} else if (!mContentValid) {
Bitmap bitmap = getBitmap();
int format = GLUtils.getInternalFormat(bitmap);
int type = GLUtils.getType(bitmap);
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
freeBitmap();
mContentValid = true;
}
}
private void uploadToCanvas(GLCanvas canvas) {
Bitmap bitmap = getBitmap();
if (bitmap != null) {
try {
int bWidth = bitmap.getWidth();
int bHeight = bitmap.getHeight();
int texWidth = getTextureWidth();
int texHeight = getTextureHeight();
if (bWidth > texWidth || bHeight > texHeight) {
throw new IllegalStateException("bWidth > texWidth || bHeight > texHeight");
}
// Upload the bitmap to a new texture.
mId = canvas.getGLId().generateTexture();
canvas.setTextureParameters(this);
if (bWidth == texWidth && bHeight == texHeight) {
canvas.initializeTexture(this, bitmap);
} else {
int format = GLUtils.getInternalFormat(bitmap);
int type = GLUtils.getType(bitmap);
Config config = bitmap.getConfig();
canvas.initializeTextureSize(this, format, type);
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
if (mBorder > 0) {
// Left border
Bitmap line = getBorderLine(true, config, texHeight);
canvas.texSubImage2D(this, 0, 0, line, format, type);
// Top border
line = getBorderLine(false, config, texWidth);
canvas.texSubImage2D(this, 0, 0, line, format, type);
}
// Right border
if (mBorder + bWidth < texWidth) {
Bitmap line = getBorderLine(true, config, texHeight);
canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
}
// Bottom border
if (mBorder + bHeight < texHeight) {
Bitmap line = getBorderLine(false, config, texWidth);
canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
}
}
} finally {
freeBitmap();
}
// Update texture state.
setAssociatedCanvas(canvas);
mState = STATE_LOADED;
mContentValid = true;
} else {
mState = STATE_ERROR;
throw new RuntimeException("Texture load fail, no bitmap");
}
}
@Override
protected boolean onBind(GLCanvas canvas) {
updateContent(canvas);
return isContentValid();
}
@Override
protected int getTarget() {
return GL11.GL_TEXTURE_2D;
}
@Override
public boolean isOpaque() {
return mOpaque;
}
public void setOpaque(boolean isOpaque) {
mOpaque = isOpaque;
}
@Override
public void recycle() {
super.recycle();
if (mBitmap != null) freeBitmap();
}
private static class BorderKey implements Cloneable {
public boolean vertical;
public Config config;
public int length;
@Override
public int hashCode() {
int x = config.hashCode() ^ length;
return vertical ? x : -x;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof BorderKey o)) return false;
return vertical == o.vertical
&& config == o.config && length == o.length;
}
@Override
public BorderKey clone() {
try {
return (BorderKey) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
}
@@ -1,80 +0,0 @@
/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.glrenderer;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import androidx.annotation.DrawableRes;
import androidx.core.content.ContextCompat;
public class XmlResourceTexture extends UploadedTexture {
private final Context mContext;
private final int mResId;
private int mWidth;
private int mHeight;
public XmlResourceTexture(Context context, @DrawableRes int resId) {
mContext = context;
mResId = resId;
setOpaque(false);
}
public void setBound(int width, int height) {
mWidth = width;
mHeight = height;
invalidateContent();
}
@Override
protected Bitmap onGetBitmap() {
Drawable drawable = ContextCompat.getDrawable(mContext, mResId);
int width = mWidth;
int height = mHeight;
if (width <= 0) {
width = drawable.getIntrinsicWidth();
}
if (height <= 0) {
height = drawable.getIntrinsicHeight();
}
if (width <= 0) {
width = 1;
}
if (height <= 0) {
height = 1;
}
drawable.setBounds(0, 0, width, height);
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.draw(canvas);
return bitmap;
}
@Override
protected void onFreeBitmap(Bitmap bitmap) {
if (!inFinalizer()) {
bitmap.recycle();
}
}
}
@@ -1,107 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.glview.image;
import android.graphics.Rect;
import android.text.TextUtils;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.view.GLView;
import com.hippo.glview.view.Gravity;
import com.hippo.yorozuya.collect.CollectionUtils;
public class GLImageMovableTextView extends GLView {
ImageMovableTextTexture mTextTexture;
private String mText = "";
private int[] mIndexes = CollectionUtils.EMPTY_INT_ARRAY;
private int mGravity = Gravity.NO_GRAVITY;
private void generateIndexes() {
if (mTextTexture == null || TextUtils.isEmpty(mText)) {
mIndexes = CollectionUtils.EMPTY_INT_ARRAY;
} else {
mIndexes = mTextTexture.getTextIndexes(mText);
}
}
public void setTextTexture(ImageMovableTextTexture textTexture) {
if (mTextTexture == textTexture) {
return;
}
mTextTexture = textTexture;
generateIndexes();
requestLayout();
}
public void setText(String text) {
if (text == null) {
text = "";
}
if (text.equals(mText)) {
return;
}
mText = text;
generateIndexes();
requestLayout();
}
public void setGravity(int gravity) {
if (mGravity != gravity) {
mGravity = gravity;
invalidate();
}
}
@Override
protected int getSuggestedMinimumWidth() {
if (mTextTexture == null) {
return super.getSuggestedMinimumWidth();
} else {
return Math.max((int) mTextTexture.getTextWidth(mIndexes) + mPaddings.left + mPaddings.right,
super.getSuggestedMinimumWidth());
}
}
@Override
protected int getSuggestedMinimumHeight() {
if (mTextTexture == null) {
return super.getSuggestedMinimumHeight();
} else {
return Math.max((int) mTextTexture.getTextHeight() + mPaddings.top + mPaddings.bottom,
super.getSuggestedMinimumHeight());
}
}
@Override
public void onRender(GLCanvas canvas) {
if (mTextTexture == null || mIndexes == null) {
return;
}
Rect paddings = getPaddings();
int x = getDefaultBegin(getWidth(), (int) mTextTexture.getTextWidth(mIndexes),
paddings.left, paddings.right, Gravity.getPosition(mGravity, Gravity.HORIZONTAL));
int y = getDefaultBegin(getHeight(), (int) mTextTexture.getTextHeight(),
paddings.top, paddings.bottom, Gravity.getPosition(mGravity, Gravity.VERTICAL));
mTextTexture.drawText(canvas, mIndexes, x, y);
}
}
Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff. Show more