Compare commits

..
74 Commits
Author SHA1 Message Date
tarsin aea75d44e3 Happy New Year 2023!
Releases / Build (push) Has been cancelled
2023-01-01 08:38:50 +08:00
tarsin 80481f1778 Update copyleft year 2023-01-01 08:37:32 +08:00
FooIbar 3ea7ff8202 Resolve #598 2022-12-31 19:37:33 +08:00
小白-白 b5979c0528 Fix comments covered by fab 2022-12-31 16:47:02 +08:00
小白-白 f0f79a52b8 Support filter comments
Resolve #516
2022-12-31 16:47:02 +08:00
小白-白 b549acc1de Add page number limit 2022-12-31 16:47:02 +08:00
小白-白 2505cc5d00 Don't show progress bar on last page 2022-12-31 16:47:02 +08:00
小白-白 d5af66b3fc Fix search bar set selection 2022-12-31 16:47:02 +08:00
小白-白 f680dc7e46 Fix copy trans 2022-12-31 16:47:02 +08:00
tarsin ffd8bbb1ce Refine missing comments 2022-12-31 12:38:50 +08:00
tarsin 216a043094 Set bar style correctly 2022-12-31 12:35:20 +08:00
tarsin 021eb5f828 Fix 3-button navigation 2022-12-31 12:27:47 +08:00
tarsin f3bf8849e9 Cleanup unused res 2022-12-31 11:48:58 +08:00
tarsin 128f7100d7 Minor cleanup 2022-12-31 11:19:34 +08:00
FooIbar 2cf53dd00f Disable landscape zoom by default 2022-12-30 17:27:01 +08:00
tarsin 4a674e46e6 Fixup! Handle drawer on back-pressed 2022-12-30 16:40:46 +08:00
tarsin f6b0134fdb Remove redundant comment 2022-12-30 14:33:20 +08:00
tarsin 675a6b863e Handle searchview backpress in separated callback 2022-12-30 13:43:39 +08:00
tarsin b570eeef93 Don't allow open drawer in search mode 2022-12-30 13:12:45 +08:00
tarsin 71a8b4e607 Handle drawer on back-pressed 2022-12-30 12:35:19 +08:00
renovate[bot] 89b866b0fc Update dependency gradle to v8.0-rc-1 2022-12-29 18:34:17 +00:00
tarsin bcce20d9ac Remove unnecessary okhttpclient rebuild 2022-12-29 21:18:47 +08:00
tarsin 420e760fb3 fixup! Convert spiderInfo cache to Coil DiskCache 2022-12-29 20:38:48 +08:00
FooIbar a168c95d79 fixup! Refactor getPageData 2022-12-29 20:28:16 +08:00
tarsin d8ccec45f9 Convert spiderInfo cache to Coil DiskCache 2022-12-29 20:27:33 +08:00
renovate[bot] 1316875602 Update plugin com.google.devtools.ksp to v1.8.0-1.0.8 2022-12-29 09:05:19 +08:00
FooIbar a85387c7c8 Add edit button for download label 2022-12-28 22:20:53 +08:00
renovate[bot] d357e83d06 Update plugin org.jetbrains.kotlin.android to v1.8.0 2022-12-28 14:17:07 +00:00
FooIbar 51546f6fea Add option for saving progress in quick search 2022-12-28 16:32:05 +08:00
tarsin b4aa5ec593 Revert "Get download dir in download mode only"
This reverts commit 241d9daae9.
2022-12-28 12:20:49 +08:00
tarsin 909380f592 Remove unused dirloader 2022-12-28 12:09:13 +08:00
tarsin d5f26643bf Support wide-color-gamut as config 2022-12-28 11:34:17 +08:00
tarsin a15dc4afdf Minor cleanup image decode codes 2022-12-28 09:57:50 +08:00
FooIbar b317bc334e Store GID in quick search name
Resolve #447
2022-12-27 15:44:02 +08:00
FooIbar d0a8d08b26 fixup! Support jump to last page 2022-12-27 15:43:01 +08:00
FooIbar f73a922a8e Cancel current task before saving instance state
Resolve #210
2022-12-26 20:33:19 +08:00
tarsin c6682ce718 Fix SwipeRefreshLayout theme 2022-12-26 19:01:35 +08:00
tarsin 764d1137ae Fixup sync with tachiyomi res 2022-12-26 17:29:02 +08:00
tarsin 02fc972a79 Remove confusing hint 2022-12-26 17:11:55 +08:00
tarsin 7fc727cf2e Resolve #579 2022-12-26 17:05:17 +08:00
FooIbar d77e1e6820 Remove redundant context parameter 2022-12-26 16:38:22 +08:00
FooIbar 241d9daae9 Get download dir in download mode only
Resolve? #381
2022-12-26 16:38:22 +08:00
FooIbar 462e0257a9 Save images to download location by default 2022-12-26 16:37:38 +08:00
renovate[bot] 66fbc7e821 Update dependency com.squareup.okhttp3:okhttp-bom to v5.0.0-alpha.11 2022-12-24 22:23:31 +00:00
FooIbar 3c37f22027 Use getBindingAdapterPosition() to get item position
Better solution for #201
2022-12-24 16:52:37 +08:00
tarsin 0d24bc6602 Thou shalt not neutralize nullability check 2022-12-24 12:20:55 +08:00
tarsin 5a50df8179 Upgrade dependencies 2022-12-24 11:53:58 +08:00
tarsin f1251fd150 Remove gradle properties enabled by default 2022-12-24 11:52:05 +08:00
tarsin 16202efc7b Allow obfuscate settings 2022-12-24 10:35:37 +08:00
tarsin 26c6052286 Rename .java to .kt 2022-12-24 10:35:37 +08:00
tarsin 7ce386a7b8 Cleanup OpenGL usage
Our bitmap/hwui may use vulkan ctx
2022-12-24 09:42:16 +08:00
tarsin e43cd0a039 Minor refactor 2022-12-23 19:12:39 +08:00
tarsin 70be405032 Free native source in advance if image is not Animated 2022-12-23 18:58:03 +08:00
tarsin 9f3ad8142a Remove largeHeap flag
Now that pixels are stored in GraphicBuffer, we no longer need large dalvik or native heap
2022-12-23 18:52:48 +08:00
FooIbar 51d9ece30c Init readingModeSettings before showing
Resolve #539
2022-12-22 21:45:07 +08:00
FooIbar d8e79f879f Fix missing space in copyright 2022-12-22 21:45:07 +08:00
FooIbar 76d50b56ee Show progress on goTo 2022-12-22 13:37:34 +08:00
renovate[bot] c8d1e957a1 Update plugin com.android.application to v8.0.0-alpha10 2022-12-22 03:14:33 +00:00
FooIbar b287af4824 Refactor getPageData 2022-12-21 18:28:52 +08:00
FooIbar 416ef8d6d7 fixup! Remove unused fab 2022-12-21 18:28:52 +08:00
renovate[bot] 7c71abb985 Update plugin com.google.devtools.ksp to v1.8.0-RC2-1.0.8 2022-12-21 11:37:05 +08:00
renovate[bot] 900f2462a7 Update plugin org.jetbrains.kotlin.android to v1.8.0-RC2 2022-12-20 18:56:50 +00:00
txyyh dd982c6cb9 Release 1.8.5.1
Releases / Build (push) Has been cancelled
2022-12-20 14:01:10 +00:00
tarsin 927a011c9d Upload mapping for release as well 2022-12-20 21:58:50 +08:00
tarsin ad0ad47a40 Support RTL Slider
But Idk whether we really support RTL :(
2022-12-20 21:30:20 +08:00
tarsin 71a424c15d Remove unused fab 2022-12-20 21:20:42 +08:00
FooIbar 145b7049a8 Support jump to last page
Resolve #466
2022-12-20 21:03:43 +08:00
tarsin 323d258bc2 Fix RTL load 2022-12-20 20:57:45 +08:00
tarsin 56ba52d7b9 Do not recycle bitmap in pager, add back lrucache
TODO: use coil instead
2022-12-20 20:43:15 +08:00
tarsin 54bd6f6ed8 Fix memleak for archive 2022-12-20 19:58:40 +08:00
tarsin f06167ba4b Sync with tachiyomi, support load progress 2022-12-20 19:03:57 +08:00
renovate[bot] 60438ae1ed Update dependency gradle to v8.0-milestone-6 (#562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-20 17:23:02 +08:00
FooIbar 1b87750b87 fixup! Fix isTranslatable (#555)
* Use appcompat to auto store locales

* fixup! Fix isTranslatable #553

* Code cleanup
2022-12-20 08:36:55 +08:00
FooIbar 40cdc566c8 Upload mapping files in ci 2022-12-20 08:33:56 +08:00
131 changed files with 1373 additions and 1849 deletions

No files matched your search

+7
View File
@@ -56,3 +56,10 @@ jobs:
with:
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
- name: Update mapping
uses: actions/upload-artifact@v3
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/release/mapping.txt
retention-days: 5
+1
View File
@@ -49,6 +49,7 @@ jobs:
app/build/outputs/apk/release/EhViewer-x86_64.apk
app/build/outputs/apk/release/EhViewer-x86.apk
app/build/outputs/apk/release/EhViewer-armeabi-v7a.apk
app/build/outputs/mapping/release/mapping.txt
- name: Collect Apks
run: |
+1 -1
View File
@@ -94,7 +94,7 @@ Here is the libraries
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
Copyright 2022 Tarsin Norbin
Copyright 2022-2023 Tarsin Norbin
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.
+4 -8
View File
@@ -31,8 +31,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 28
targetSdkVersion 33
versionCode 180028
versionName "1.8.5.0"
versionCode 180030
versionName "1.8.5.2"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -52,10 +52,6 @@ android {
freeCompilerArgs += [
// https://kotlinlang.org/docs/compiler-reference.html#progressive
"-progressive",
// Generate smaller bytecode by not generating runtime not-null assertions.
"-Xno-call-assertions",
"-Xno-param-assertions",
"-Xno-receiver-assertions",
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
]
@@ -112,7 +108,7 @@ dependencies {
implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.annotation:annotation-experimental:1.3.0"
implementation "androidx.appcompat:appcompat:1.6.0-rc01"
implementation "androidx.appcompat:appcompat:1.7.0-alpha01"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
@@ -185,7 +181,7 @@ dependencies {
implementation 'com.drakeet.drawer:drawer:1.0.3'
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-bom:5.0.0-alpha.11'
implementation 'com.squareup.okhttp3:okhttp'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
+3 -10
View File
@@ -2,22 +2,15 @@
public static final ** CREATOR;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
public static void check*(...);
public static void throw*(...);
}
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-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
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
-repackageclasses
-allowaccessmodification
-mergeinterfacesaggressively
-overloadaggressively
+14 -4
View File
@@ -14,7 +14,8 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
@@ -41,12 +42,13 @@
android:fullBackupContent="@xml/backup_scheme"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:label="@string/app_name"
android:largeHeap="true"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:targetApi="tiramisu">
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
@@ -101,7 +103,6 @@
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:exported="true"
android:colorMode="wideColorGamut"
android:launchMode="singleTask">
<intent-filter>
@@ -157,6 +158,15 @@
android:taskAffinity=""
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
<service
android:name="com.hippo.ehviewer.download.DownloadService"
android:foregroundServiceType="dataSync"
@@ -0,0 +1,25 @@
package androidx.swiperefreshlayout.widget
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.ShapeDrawable
import android.util.AttributeSet
import androidx.core.graphics.ColorUtils
import rikka.core.res.resolveColor
@SuppressLint("PrivateResource")
class ThemedSwipeRefreshLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : SwipeRefreshLayout(context, attrs) {
init {
val surfaceColor =
context.theme.resolveColor(com.google.android.material.R.attr.colorSurface)
val overlayColor =
context.getColor(com.google.android.material.R.color.m3_popupmenu_overlay_color)
val backgroundColor = ColorUtils.compositeColors(overlayColor, surfaceColor)
(mCircleView.background as ShapeDrawable).paint.color = backgroundColor
setColorSchemeColors(context.theme.resolveColor(com.google.android.material.R.attr.colorAccent))
}
}
@@ -29,8 +29,6 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
private external fun openArchive(fd: Int, size: Long): Int
external fun extractToByteBuffer(index: Int): ByteBuffer?
external fun releaseByteBuffer(buffer: ByteBuffer)
external fun extractToFd(index: Int, fd: Int)
external fun getFilename(index: Int): String
external fun needPassword(): Boolean
@@ -40,4 +38,9 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
closeArchive()
pfd.close()
}
companion object {
@JvmStatic
external fun releaseByteBuffer(buffer: ByteBuffer)
}
}
@@ -20,7 +20,6 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.content.ComponentCallbacks2
import android.os.Build
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.View
@@ -28,15 +27,14 @@ 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.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import com.hippo.Native
import com.hippo.app.BaseDialogBuilder
import com.hippo.beerbelly.SimpleDiskCache
import com.hippo.ehviewer.client.EhClient
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhDns
@@ -58,6 +56,8 @@ import com.hippo.util.ExceptionUtils
import com.hippo.util.ReadableTime
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IntIdGenerator
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -107,7 +107,7 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
super<SceneApplication>.onCreate()
Native.initialize()
GetText.initialize(this)
Settings.initialize(this)
Settings.initialize()
ReadableTime.initialize(this)
AppConfig.initialize(this)
SpiderDen.initialize(this)
@@ -116,9 +116,6 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
BitmapUtils.initialize(this)
EhTagDatabase.update()
// 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())
GlobalScope.launch {
@@ -335,19 +332,20 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
@JvmStatic
val spiderInfoCache by lazy {
SimpleDiskCache(
File(application.cacheDir, "spider_info"),
20 * 1024 * 1024
)
DiskCache.Builder().directory(File(application.cacheDir, "spider_info"))
.maxSizeBytes(20 * 1024 * 1024).build()
}
@JvmStatic
val downloadManager by lazy { DownloadManager(application) }
val downloadManager by lazy { DownloadManager() }
@JvmStatic
val hosts by lazy { Hosts(application, "hosts.db") }
@JvmStatic
val favouriteStatusRouter by lazy { FavouriteStatusRouter() }
@JvmStatic
val readerPreferences by lazy { ReaderPreferences(AndroidPreferenceStore(application)) }
}
}
@@ -16,7 +16,6 @@
package com.hippo.ehviewer;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.util.Log;
@@ -27,7 +26,6 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.ehviewer.client.EhTagDatabase;
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.ehviewer.ui.scene.GalleryListScene;
@@ -87,7 +85,6 @@ public class Settings {
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;
@@ -125,34 +122,6 @@ 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;
/********************
****** 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 String KEY_PAGE_SCALING = "page_scaling";
private static final String KEY_START_POSITION = "start_position";
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";
private static final boolean DEFAULT_SHOW_CLOCK = true;
private static final String KEY_SHOW_PROGRESS = "gallery_show_progress";
private static final boolean DEFAULT_SHOW_PROGRESS = true;
private static final String KEY_SHOW_BATTERY = "gallery_show_battery";
private static final boolean DEFAULT_SHOW_BATTERY = true;
private static final String KEY_SHOW_PAGE_INTERVAL = "gallery_show_page_interval";
private static final boolean DEFAULT_SHOW_PAGE_INTERVAL = true;
private static final String KEY_VOLUME_PAGE = "volume_page";
private static final boolean DEFAULT_VOLUME_PAGE = false;
private static final String KEY_REVERSE_VOLUME_PAGE = "reserve_volume_page";
private static final boolean DEFAULT_REVERSE_VOLUME_PAGE = false;
private static final String KEY_READING_FULLSCREEN = "reading_fullscreen";
private static final boolean VALUE_READING_FULLSCREEN = true;
private static final String KEY_CUSTOM_SCREEN_LIGHTNESS = "custom_screen_lightness";
private static final boolean DEFAULT_CUSTOM_SCREEN_LIGHTNESS = false;
private static final String KEY_SCREEN_LIGHTNESS = "screen_lightness";
private static final int DEFAULT_SCREEN_LIGHTNESS = 50;
private static final boolean DEFAULT_MEDIA_SCAN = false;
private static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label";
private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null;
@@ -166,8 +135,6 @@ public class Settings {
private static final int DEFAULT_PRELOAD_IMAGE = 5;
private static final String KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image";
private static final boolean DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false;
private static final String KEY_READ_THEME = "read_theme";
private static final int DEFAULT_READ_THEME = 1;
/********************
****** Favorites
********************/
@@ -214,15 +181,12 @@ public class Settings {
private static final boolean DEFAULT_SAVE_CRASH_LOG = true;
private static final boolean DEFAULT_BUILT_IN_HOSTS = false;
private static final boolean DEFAULT_FRONTING = false;
private static final String DEFAULT_APP_LANGUAGE = "system";
private static final String KEY_PROXY_TYPE = "proxy_type";
private static final int DEFAULT_PROXY_TYPE = EhProxySelector.TYPE_SYSTEM;
private static final String KEY_PROXY_IP = "proxy_ip";
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;
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";
private static final int DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0;
private static final String KEY_DOWNLOAD_DELAY = "download_delay";
@@ -230,15 +194,15 @@ public class Settings {
private static final String KEY_REQUEST_NEWS = "request_news";
private static final boolean DEFAULT_REQUEST_NEWS = true;
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
private static final String KEY_QS_SAVE_PROGRESS = "qs_save_progress";
private static final boolean DEFAULT_QS_SAVE_PROGRESS = true;
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 EhConfig sEhConfig;
public static void initialize(Context context) {
sContext = context.getApplicationContext();
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
public static void initialize() {
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(EhApplication.getApplication());
sEhConfig = loadEhConfig();
fixDefaultValue();
}
@@ -260,15 +224,6 @@ public class Settings {
}
}
public static Locale getLocale() {
String language = Settings.getAppLanguage();
if (language != null && !language.equals("system")) {
return Locale.forLanguageTag(language);
} else {
return Locale.getDefault();
}
}
private static EhConfig loadEhConfig() {
EhConfig ehConfig = new EhConfig();
ehConfig.imageSize = getImageResolution();
@@ -432,7 +387,7 @@ public class Settings {
}
public static int dip2px(int dpValue) {
final float scale = sContext.getResources().getDisplayMetrics().density;
final float scale = EhApplication.getApplication().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@@ -461,7 +416,7 @@ public class Settings {
}
public static boolean getShowTagTranslations() {
return getBoolean(KEY_SHOW_TAG_TRANSLATIONS, DEFAULT_SHOW_TAG_TRANSLATIONS) && EhTagDatabase.INSTANCE.isTranslatable();
return getBoolean(KEY_SHOW_TAG_TRANSLATIONS, DEFAULT_SHOW_TAG_TRANSLATIONS);
}
public static void putShowTagTranslations(boolean value) {
@@ -496,114 +451,6 @@ public class Settings {
putBoolean(KEY_APP_LINK_VERIFY_TIP, value);
}
public static int getScreenRotation() {
return getIntFromStr(KEY_SCREEN_ROTATION, DEFAULT_SCREEN_ROTATION);
}
public static void putScreenRotation(int value) {
putIntToStr(KEY_SCREEN_ROTATION, value);
}
public static void putReadingDirection(int value) {
putIntToStr(KEY_READING_DIRECTION, value);
}
public static void putPageScaling(int value) {
putIntToStr(KEY_PAGE_SCALING, value);
}
public static void putStartPosition(int value) {
putIntToStr(KEY_START_POSITION, value);
}
public static boolean getKeepScreenOn() {
return getBoolean(KEY_KEEP_SCREEN_ON, DEFAULT_KEEP_SCREEN_ON);
}
public static void putKeepScreenOn(boolean value) {
putBoolean(KEY_KEEP_SCREEN_ON, value);
}
public static boolean getShowClock() {
return getBoolean(KEY_SHOW_CLOCK, DEFAULT_SHOW_CLOCK);
}
public static void putShowClock(boolean value) {
putBoolean(KEY_SHOW_CLOCK, value);
}
public static boolean getShowProgress() {
return getBoolean(KEY_SHOW_PROGRESS, DEFAULT_SHOW_PROGRESS);
}
public static void putShowProgress(boolean value) {
putBoolean(KEY_SHOW_PROGRESS, value);
}
public static boolean getShowBattery() {
return getBoolean(KEY_SHOW_BATTERY, DEFAULT_SHOW_BATTERY);
}
public static void putShowBattery(boolean value) {
putBoolean(KEY_SHOW_BATTERY, value);
}
public static boolean getShowPageInterval() {
return getBoolean(KEY_SHOW_PAGE_INTERVAL, DEFAULT_SHOW_PAGE_INTERVAL);
}
public static void putShowPageInterval(boolean value) {
putBoolean(KEY_SHOW_PAGE_INTERVAL, value);
}
public static boolean getVolumePage() {
return getBoolean(KEY_VOLUME_PAGE, DEFAULT_VOLUME_PAGE);
}
public static void putVolumePage(boolean value) {
putBoolean(KEY_VOLUME_PAGE, value);
}
public static boolean getReverseVolumePage() {
return getBoolean(KEY_REVERSE_VOLUME_PAGE, DEFAULT_REVERSE_VOLUME_PAGE);
}
public static void putReverseVolumePage(boolean value) {
putBoolean(KEY_REVERSE_VOLUME_PAGE, value);
}
public static boolean getReadingFullscreen() {
return getBoolean(KEY_READING_FULLSCREEN, VALUE_READING_FULLSCREEN);
}
public static void putReadingFullscreen(boolean value) {
putBoolean(KEY_READING_FULLSCREEN, value);
}
public static boolean getCustomScreenLightness() {
return getBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, DEFAULT_CUSTOM_SCREEN_LIGHTNESS);
}
public static void putCustomScreenLightness(boolean value) {
putBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, value);
}
public static int getScreenLightness() {
return getInt(KEY_SCREEN_LIGHTNESS, DEFAULT_SCREEN_LIGHTNESS);
}
public static void putScreenLightness(int value) {
putInt(KEY_SCREEN_LIGHTNESS, value);
}
public static int getReadTheme() {
return getIntFromStr(KEY_READ_THEME, DEFAULT_READ_THEME);
}
public static void putReadTheme(int value) {
putIntToStr(KEY_READ_THEME, value);
}
public static boolean getEnabledSecurity() {
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
}
@@ -617,7 +464,7 @@ public class Settings {
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());
dir = UniFile.fromUri(EhApplication.getApplication(), builder.build());
return dir != null ? dir : UniFile.fromFile(AppConfig.getDefaultDownloadDir());
}
@@ -784,6 +631,14 @@ public class Settings {
putInt(KEY_DEFAULT_FAV_SLOT, value);
}
public static boolean getQSSaveProgress() {
return getBoolean(KEY_QS_SAVE_PROGRESS, DEFAULT_QS_SAVE_PROGRESS);
}
public static void putQSSaveProgress(boolean value) {
putBoolean(KEY_QS_SAVE_PROGRESS, value);
}
public static boolean getSaveParseErrorBody() {
return getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY);
}
@@ -824,10 +679,6 @@ public class Settings {
putBoolean(KEY_DOMAIN_FRONTING, value);
}
public static String getAppLanguage() {
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
}
public static int getProxyType() {
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
}
@@ -852,14 +703,6 @@ public class Settings {
putInt(KEY_PROXY_PORT, value);
}
public static boolean getGuideGallery() {
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
}
public static void putGuideGallery(boolean value) {
putBoolean(KEY_GUIDE_GALLERY, value);
}
public static int getClipboardTextHashCode() {
return getInt(KEY_CLIPBOARD_TEXT_HASH_CODE, DEFAULT_CLIPBOARD_TEXT_HASH_CODE);
}
@@ -24,6 +24,8 @@ import com.hippo.ehviewer.dao.Filter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class EhFilter {
@@ -31,36 +33,38 @@ public final class EhFilter {
public static final int MODE_UPLOADER = 1;
public static final int MODE_TAG = 2;
public static final int MODE_TAG_NAMESPACE = 3;
public static final int MODE_COMMENTER = 4;
public static final int MODE_COMMENT = 5;
private static final String TAG = EhFilter.class.getSimpleName();
private static EhFilter sInstance;
private final List<Filter> mTitleFilterList = new ArrayList<>();
private final List<Filter> mUploaderFilterList = new ArrayList<>();
private final List<Filter> mTagFilterList = new ArrayList<>();
private final List<Filter> mTagNamespaceFilterList = new ArrayList<>();
private final List<Filter> mCommenterFilterList = new ArrayList<>();
private final List<Filter> mCommentFilterList = new ArrayList<>();
private EhFilter() {
List<Filter> list = EhDB.getAllFilter();
for (int i = 0, n = list.size(); i < n; i++) {
Filter filter = list.get(i);
switch (filter.mode) {
case MODE_TITLE:
case MODE_TITLE -> {
filter.text = filter.text.toLowerCase();
mTitleFilterList.add(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.add(filter);
break;
case MODE_TAG:
}
case MODE_TAG -> {
filter.text = filter.text.toLowerCase();
mTagFilterList.add(filter);
break;
case MODE_TAG_NAMESPACE:
}
case MODE_TAG_NAMESPACE -> {
filter.text = filter.text.toLowerCase();
mTagNamespaceFilterList.add(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
}
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
case MODE_COMMENT -> mCommentFilterList.add(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
}
}
@@ -88,30 +92,36 @@ public final class EhFilter {
return mTagNamespaceFilterList;
}
public List<Filter> getCommenterFilterList() {
return mCommenterFilterList;
}
public List<Filter> getCommentFilterList() {
return mCommentFilterList;
}
public synchronized boolean addFilter(Filter filter) {
// enable filter by default before it is added to database
filter.enable = true;
if (!EhDB.addFilter(filter)) return false;
switch (filter.mode) {
case MODE_TITLE:
case MODE_TITLE -> {
filter.text = filter.text.toLowerCase();
mTitleFilterList.add(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.add(filter);
break;
case MODE_TAG:
}
case MODE_TAG -> {
filter.text = filter.text.toLowerCase();
mTagFilterList.add(filter);
break;
case MODE_TAG_NAMESPACE:
}
case MODE_TAG_NAMESPACE -> {
filter.text = filter.text.toLowerCase();
mTagNamespaceFilterList.add(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
}
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
case MODE_COMMENT -> mCommentFilterList.add(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
return true;
}
@@ -124,21 +134,13 @@ public final class EhFilter {
EhDB.deleteFilter(filter);
switch (filter.mode) {
case MODE_TITLE:
mTitleFilterList.remove(filter);
break;
case MODE_UPLOADER:
mUploaderFilterList.remove(filter);
break;
case MODE_TAG:
mTagFilterList.remove(filter);
break;
case MODE_TAG_NAMESPACE:
mTagNamespaceFilterList.remove(filter);
break;
default:
Log.d(TAG, "Unknown mode: " + filter.mode);
break;
case MODE_TITLE -> mTitleFilterList.remove(filter);
case MODE_TAG -> mTagFilterList.remove(filter);
case MODE_TAG_NAMESPACE -> mTagNamespaceFilterList.remove(filter);
case MODE_UPLOADER -> mUploaderFilterList.remove(filter);
case MODE_COMMENTER -> mCommenterFilterList.remove(filter);
case MODE_COMMENT -> mCommentFilterList.remove(filter);
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
}
}
@@ -272,4 +274,40 @@ public final class EhFilter {
return true;
}
public synchronized boolean filterCommenter(String commenter) {
if (null == commenter) {
return false;
}
List<Filter> filters = mCommenterFilterList;
if (filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && commenter.equals(filters.get(i).text)) {
return false;
}
}
}
return true;
}
public synchronized boolean filterComment(String comment) {
if (null == comment) {
return false;
}
List<Filter> filters = mCommentFilterList;
if (filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable) {
Pattern p = Pattern.compile(filters.get(i).text);
Matcher m = p.matcher(comment);
if (m.find()) return false;
}
}
}
return true;
}
}
@@ -133,8 +133,8 @@ object EhTagDatabase {
.takeIf { it.size == 4 }
}
fun isTranslatable(): Boolean {
return EhApplication.application.resources.getBoolean(R.bool.tag_translatable)
fun isTranslatable(context: Context): Boolean {
return context.resources.getBoolean(R.bool.tag_translatable)
}
private fun getFileContent(file: File): String? {
@@ -32,7 +32,7 @@ public class FavListUrlBuilder implements Parcelable {
public static final int FAV_CAT_ALL = -1;
public static final int FAV_CAT_LOCAL = -2;
public static final Parcelable.Creator<FavListUrlBuilder> CREATOR = new Parcelable.Creator<FavListUrlBuilder>() {
public static final Parcelable.Creator<FavListUrlBuilder> CREATOR = new Parcelable.Creator<>() {
@Override
public FavListUrlBuilder createFromParcel(Parcel source) {
@@ -66,12 +66,14 @@ public class FavListUrlBuilder implements Parcelable {
return favCat >= 0 && favCat <= 9;
}
public void setPrev(String prev) {
mPrev = prev;
}
public void setNext(String next) {
mNext = next;
public void setIndex(String index, boolean isNext) {
if (isNext) {
mNext = index;
mPrev = null;
} else {
mPrev = index;
mNext = null;
}
}
public void setJumpTo(String jumpTo) {
@@ -115,12 +117,6 @@ public class FavListUrlBuilder implements Parcelable {
e.printStackTrace();
}
}
// Name
ub.addQuery("sn", "on");
// Tags
ub.addQuery("st", "on");
// Note
ub.addQuery("sf", "on");
}
if (mPrev != null && !mPrev.isEmpty()) {
ub.addQuery("prev", mPrev);
@@ -36,8 +36,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
@get:Mode
@Mode
var mode = MODE_NORMAL
private var mPrevGid = 0
private var mNextGid = 0
private var mPrev: String? = null
private var mNext: String? = null
private var mJumpTo: String? = null
var category = EhUtils.NONE
private var mKeyword: String? = null
@@ -54,8 +54,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
constructor()
protected constructor(parcel: Parcel) {
mode = parcel.readInt()
mPrevGid = parcel.readInt()
mNextGid = parcel.readInt()
mPrev = parcel.readString()
mNext = parcel.readString()
mJumpTo = parcel.readString()
this.category = parcel.readInt()
mKeyword = parcel.readString()
@@ -72,8 +72,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
fun reset() {
mode = MODE_NORMAL
mPrevGid = 0
mNextGid = 0
mPrev = null
mNext = null
mJumpTo = null
this.category = EhUtils.NONE
mKeyword = null
@@ -96,12 +96,14 @@ open class ListUrlBuilder : Cloneable, Parcelable {
}
}
fun setPrevGid(prevGid: Int) {
mPrevGid = prevGid
}
fun setNextGid(nextGid: Int) {
mNextGid = nextGid
fun setIndex(index: String?, isNext: Boolean = true) {
if (isNext) {
mNext = index
mPrev = null
} else {
mPrev = index
mNext = null
}
}
fun setJumpTo(jumpTo: String?) {
@@ -121,8 +123,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
*/
fun set(lub: ListUrlBuilder) {
mode = lub.mode
mPrevGid = lub.mPrevGid
mNextGid = lub.mNextGid
mPrev = lub.mPrev
mNext = lub.mNext
mJumpTo = lub.mJumpTo
this.category = lub.category
mKeyword = lub.mKeyword
@@ -196,7 +198,7 @@ open class ListUrlBuilder : Cloneable, Parcelable {
if (TextUtils.isEmpty(query)) {
return
}
val querys = StringUtils.split(query, '&')
val queries = StringUtils.split(query, '&')
var category = 0
var keyword: String? = null
var enableAdvanceSearch = false
@@ -206,7 +208,7 @@ open class ListUrlBuilder : Cloneable, Parcelable {
var enablePage = false
var pageFrom = -1
var pageTo = -1
for (str in querys) {
for (str in queries) {
val index = str.indexOf('=')
if (index < 0) {
continue
@@ -348,19 +350,17 @@ open class ListUrlBuilder : Cloneable, Parcelable {
}
}
}
if (mSHash != null) {
ub.addQuery("f_shash", mSHash!!)
mSHash?.let {
ub.addQuery("f_shash", it)
}
mJumpTo?.let {
ub.addQuery("seek", mJumpTo!!)
mPrevGid = 0
mNextGid = 0
ub.addQuery("seek", it)
}
if (mPrevGid != 0) {
ub.addQuery("prev", mPrevGid)
mPrev?.let {
ub.addQuery("prev", it)
}
if (mNextGid != 0) {
ub.addQuery("next", mNextGid)
mNext?.let {
ub.addQuery("next", it)
}
// Advance search
if (advanceSearch != -1) {
@@ -393,14 +393,14 @@ open class ListUrlBuilder : Cloneable, Parcelable {
} catch (e: UnsupportedEncodingException) {
// Empty
}
if (mPrevGid != 0) {
sb.append("?prev=").append(mPrevGid)
mPrev?.let {
sb.append("?prev=").append(it)
}
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid)
mNext?.let {
sb.append("?next=").append(it)
}
if (mJumpTo != null) {
sb.append("&seek=").append(mJumpTo)
mJumpTo?.let {
sb.append("&seek=").append(it)
}
sb.toString()
}
@@ -413,14 +413,14 @@ open class ListUrlBuilder : Cloneable, Parcelable {
} catch (e: UnsupportedEncodingException) {
// Empty
}
if (mPrevGid != 0) {
sb.append("?prev=").append(mPrevGid)
mPrev?.let {
sb.append("?prev=").append(it)
}
if (mNextGid != 0) {
sb.append("?next=").append(mNextGid)
mNext?.let {
sb.append("?next=").append(it)
}
if (mJumpTo != null) {
sb.append("&seek=").append(mJumpTo)
mJumpTo?.let {
sb.append("&seek=").append(it)
}
sb.toString()
}
@@ -433,61 +433,15 @@ open class ListUrlBuilder : Cloneable, Parcelable {
try {
sb.append(URLEncoder.encode(mKeyword, "UTF-8"))
} catch (e: UnsupportedEncodingException) {
// Empty
e.printStackTrace()
}
if (mNextGid != 0) {
sb.append("&p=").append(mNextGid)
mJumpTo?.let {
sb.append("&p=").append(it)
}
sb.toString()
}
else -> {
val url: String = if (mode == MODE_NORMAL) {
EhUrl.getHost()
} else {
EhUrl.getWatchedUrl()
}
val ub = UrlBuilder(url)
if (this.category != EhUtils.NONE) {
ub.addQuery("f_cats", category.inv() and EhConfig.ALL_CATEGORY)
}
if (mKeyword != null) {
val keyword = mKeyword!!.trim { it <= ' ' }
if (keyword.isNotEmpty()) {
try {
ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8"))
} catch (_: UnsupportedEncodingException) {
}
}
}
if (mSHash != null) {
ub.addQuery("f_shash", mSHash!!)
}
if (mPrevGid != 0) {
ub.addQuery("prev", mPrevGid)
}
if (mNextGid != 0) {
ub.addQuery("next", mNextGid)
}
if (advanceSearch != -1) {
ub.addQuery("advsearch", "1")
if (advanceSearch and AdvanceSearchTable.SH != 0) ub.addQuery("f_sh", "on")
if (advanceSearch and AdvanceSearchTable.STO != 0) ub.addQuery("f_sto", "on")
if (advanceSearch and AdvanceSearchTable.SFL != 0) ub.addQuery("f_sfl", "on")
if (advanceSearch and AdvanceSearchTable.SFU != 0) ub.addQuery("f_sfu", "on")
if (advanceSearch and AdvanceSearchTable.SFT != 0) ub.addQuery("f_sft", "on")
if (minRating != -1) {
ub.addQuery("f_sr", "on")
ub.addQuery("f_srdd", minRating)
}
if (pageFrom != -1 || pageTo != -1) {
ub.addQuery("f_sp", "on")
ub.addQuery("f_spf", if (pageFrom != -1) pageFrom.toString() else "")
ub.addQuery("f_spt", if (pageTo != -1) pageTo.toString() else "")
}
}
ub.build()
}
else -> throw java.lang.IllegalStateException("Unexpected value: $mode")
}
}
@@ -497,8 +451,8 @@ open class ListUrlBuilder : Cloneable, Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(mode)
dest.writeInt(mPrevGid)
dest.writeInt(mNextGid)
dest.writeString(mPrev)
dest.writeString(mNext)
dest.writeString(mJumpTo)
dest.writeInt(this.category)
dest.writeString(mKeyword)
@@ -31,22 +31,17 @@ import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FavoritesParser {
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+(-\\d+)?)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+(-\\d+)?)");
public static Result parse(String body) throws Exception {
if (body.contains("This page requires you to log on.</p>")) {
throw new EhException(GetText.getString(R.string.need_sign_in));
}
Document d = Jsoup.parse(body);
String[] catArray = new String[10];
int[] countArray = new int[10];
try {
Document d = Jsoup.parse(body);
Element ido = JsoupUtils.getElementByClass(d, "ido");
//noinspection ConstantConditions
Elements fps = ido.getElementsByClass("fp");
@@ -64,24 +59,13 @@ public class FavoritesParser {
throw new ParseException("Parse favorites error", body);
}
Result re = new Result();
try {
Element prev = d.getElementById("uprev");
Element next = d.getElementById("unext");
assert prev != null;
assert next != null;
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
if (matcherPrev.find()) re.prevPage = matcherPrev.group(1);
if (matcherNext.find()) re.nextPage = matcherNext.group(1);
} catch (Throwable e) {
e.printStackTrace();
}
GalleryListParser.Result result = GalleryListParser.parse(body);
Result re = new Result();
re.catArray = catArray;
re.countArray = countArray;
re.prev = result.prev;
re.next = result.next;
re.galleryInfoList = result.galleryInfoList;
return re;
@@ -90,8 +74,8 @@ public class FavoritesParser {
public static class Result {
public String[] catArray; // Size 10
public int[] countArray; // Size 10
public String prevPage;
public String nextPage;
public String prev;
public String next;
public List<GalleryInfo> galleryInfoList;
}
}
@@ -20,6 +20,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryCommentList;
@@ -473,6 +474,13 @@ public class GalleryDetailParser {
comment.user = c3.child(0).text();
// comment
comment.comment = JsoupUtils.getElementByClass(element, "c6").html();
// filter comment
if (!comment.uploader) {
EhFilter sEhFilter = EhFilter.getInstance();
if (!sEhFilter.filterCommenter(comment.user) || !sEhFilter.filterComment(comment.comment)) {
return null;
}
}
// last edited
Element c8 = JsoupUtils.getElementByClass(element, "c8");
if (c8 != null) {
@@ -50,8 +50,9 @@ public class GalleryListParser {
private static final Pattern PATTERN_THUMB_SIZE = Pattern.compile("height:(\\d+)px;width:(\\d+)px");
private static final Pattern PATTERN_FAVORITE_SLOT = Pattern.compile("background-color:rgba\\((\\d+),(\\d+),(\\d+),");
private static final Pattern PATTERN_PAGES = Pattern.compile("(\\d+) page");
private static final Pattern PATTERN_PREV_PAGE = Pattern.compile("prev=(\\d+)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("next=(\\d+)");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("p=(\\d+)");
private static final Pattern PATTERN_PREV = Pattern.compile("prev=(\\d+(-\\d+)?)");
private static final Pattern PATTERN_NEXT = Pattern.compile("next=(\\d+(-\\d+)?)");
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
new String[]{"0", "0", "0"},
@@ -256,11 +257,7 @@ public class GalleryListParser {
if (div != null) {
gi.disowned = div.attr("style").contains("opacity:0.5");
Element a = div.children().first();
if (a != null) {
gi.uploader = a.text().trim();
} else {
gi.uploader = div.text().trim();
}
gi.uploader = a == null ? div.text().trim() : a.text().trim();
}
}
if (children.size() > pagesIndex) {
@@ -296,28 +293,44 @@ public class GalleryListParser {
Element next = d.getElementById("unext");
assert prev != null;
assert next != null;
Matcher matcherPrev = PATTERN_PREV_PAGE.matcher(prev.attr("href"));
Matcher matcherNext = PATTERN_NEXT_PAGE.matcher(next.attr("href"));
Matcher matcherPrev = PATTERN_PREV.matcher(prev.attr("href"));
Matcher matcherNext = PATTERN_NEXT.matcher(next.attr("href"));
if (matcherPrev.find())
result.prevGid = NumberUtils.parseIntSafely(matcherPrev.group(1), 0);
result.prev = matcherPrev.group(1);
if (matcherNext.find())
result.nextGid = NumberUtils.parseIntSafely(matcherNext.group(1), 0);
result.founds = Integer.MAX_VALUE;
result.next = matcherNext.group(1);
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
result.noWatchedTags = body.contains("<p>You do not have any watched tags");
if (body.contains("No hits found</p>")) {
result.founds = 0;
//noinspection unchecked
result.galleryInfoList = Collections.EMPTY_LIST;
return result;
} else if (d.getElementsByClass("ptt").isEmpty()) {
result.founds = 1;
} else {
result.founds = Integer.MAX_VALUE;
}
}
try { // For toplists
Element ptt = d.getElementsByClass("ptt").first();
if (ptt != null) {
Elements es = ptt.child(0).child(0).children();
result.pages = Integer.parseInt(es.get(es.size() - 2).text().trim());
Element e = es.get(es.size() - 1);
if (e != null) {
e = e.children().first();
if (e != null) {
String href = e.attr("href");
Matcher matcher = PATTERN_NEXT_PAGE.matcher(href);
if (matcher.find()) {
result.nextPage = NumberUtils.parseIntSafely(matcher.group(1), 0);
}
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
try {
Element itg = d.getElementsByClass("itg").first();
Elements es;
@@ -350,9 +363,10 @@ public class GalleryListParser {
}
public static class Result {
public int founds;
public int prevGid;
public int nextGid;
public int pages;
public int nextPage;
public String prev;
public String next;
public boolean noWatchedTags;
public List<GalleryInfo> galleryInfoList;
}
@@ -17,7 +17,6 @@
package com.hippo.ehviewer.download;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
@@ -44,7 +43,6 @@ import com.hippo.yorozuya.collect.SparseJLArray;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@@ -55,8 +53,7 @@ import java.util.Map;
public class DownloadManager implements SpiderQueen.OnSpiderListener {
private static final String TAG = DownloadManager.class.getSimpleName();
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = (lhs, rhs) -> lhs.time - rhs.time > 0 ? -1 : 1;
private final Context mContext;
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = (lhs, rhs) -> (int) (rhs.time - lhs.time);
// All download info list
private final LinkedList<DownloadInfo> mAllInfoList;
// All download info map
@@ -79,8 +76,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
@Nullable
private SpiderQueen mCurrentSpider;
public DownloadManager(Context context) {
mContext = context;
public DownloadManager() {
// Get all labels
List<DownloadLabel> labels = EhDB.getAllDownloadLabelList();
@@ -206,7 +202,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Get download from wait list
if (!mWaitList.isEmpty()) {
DownloadInfo info = mWaitList.removeFirst();
SpiderQueen spider = SpiderQueen.obtainSpiderQueen(mContext, info, SpiderQueen.MODE_DOWNLOAD);
SpiderQueen spider = SpiderQueen.obtainSpiderQueen(info, SpiderQueen.MODE_DOWNLOAD);
mCurrentTask = info;
mCurrentSpider = spider;
spider.addOnSpiderListener(this);
@@ -387,7 +383,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
list.add(info);
// Sort
Collections.sort(list, DATE_DESC_COMPARATOR);
list.sort(DATE_DESC_COMPARATOR);
// Add to all download list and map
mAllInfoList.add(info);
@@ -398,7 +394,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Sort all download list
Collections.sort(mAllInfoList, DATE_DESC_COMPARATOR);
mAllInfoList.sort(DATE_DESC_COMPARATOR);
// Notify
if (notify) {
@@ -730,7 +726,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
srcList.remove(info);
dstList.add(info);
info.label = label;
Collections.sort(dstList, DATE_DESC_COMPARATOR);
dstList.sort(DATE_DESC_COMPARATOR);
// Save to DB
EhDB.putDownloadInfo(info);
@@ -830,7 +826,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Sort
Collections.sort(mDefaultInfoList, DATE_DESC_COMPARATOR);
mDefaultInfoList.sort(DATE_DESC_COMPARATOR);
// Notify listener
for (DownloadInfoListener l : mDownloadInfoListeners) {
@@ -1052,7 +1048,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
@Override
public void run() {
switch (mType) {
case TYPE_ON_GET_PAGES: {
case TYPE_ON_GET_PAGES -> {
DownloadInfo info = mCurrentTask;
if (info == null) {
Log.e(TAG, "Current task is null, but it should not be");
@@ -1065,19 +1061,15 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
}
}
break;
}
case TYPE_ON_GET_509: {
case TYPE_ON_GET_509 -> {
if (mDownloadListener != null) {
mDownloadListener.onGet509();
}
break;
}
case TYPE_ON_PAGE_DOWNLOAD: {
mSpeedReminder.onDownload(mIndex, mContentLength, mReceivedSize, mBytesRead);
break;
}
case TYPE_ON_PAGE_SUCCESS: {
case TYPE_ON_PAGE_DOWNLOAD ->
mSpeedReminder.onDownload(mIndex, mContentLength, mReceivedSize, mBytesRead);
case TYPE_ON_PAGE_SUCCESS -> {
mSpeedReminder.onDone(mIndex);
DownloadInfo info = mCurrentTask;
if (info == null) {
@@ -1096,9 +1088,8 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
}
}
break;
}
case TYPE_ON_PAGE_FAILURE: {
case TYPE_ON_PAGE_FAILURE -> {
mSpeedReminder.onDone(mIndex);
DownloadInfo info = mCurrentTask;
if (info == null) {
@@ -1114,9 +1105,8 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
}
}
break;
}
case TYPE_ON_FINISH: {
case TYPE_ON_FINISH -> {
mSpeedReminder.onFinish();
// Download done
DownloadInfo info = mCurrentTask;
@@ -1159,7 +1149,6 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Start next download
ensureDownload();
break;
}
}
@@ -91,6 +91,7 @@ public class ArchivePageLoader extends PageLoader2 {
@Override
public void stop() {
super.stop();
if (archiveThread != null) {
archiveThread.interrupt();
archiveThread = null;
@@ -334,12 +335,9 @@ public class ArchivePageLoader extends PageLoader2 {
Image image = null;
if (buffer != null) {
try {
image = Image.decode(buffer, () -> {
archiveAccessor.releaseByteBuffer(buffer);
return null;
});
image = Image.decode(buffer.asReadOnlyBuffer());
} catch (ImageDecoder.DecodeException e) {
archiveAccessor.releaseByteBuffer(buffer);
UriArchiveAccessor.releaseByteBuffer(buffer);
e.printStackTrace();
}
}
@@ -1,276 +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.gallery;
import android.os.Process;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.image.Image;
import com.hippo.unifile.FilenameFilter;
import com.hippo.unifile.UniFile;
import com.hippo.util.NaturalComparator;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.StringUtils;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
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);
private static final Comparator<UniFile> naturalComparator = new Comparator<UniFile>() {
private final NaturalComparator comparator = new NaturalComparator();
@Override
public int compare(UniFile o1, UniFile o2) {
return comparator.compare(o1.getName(), o2.getName());
}
};
private final UniFile mDir;
private final Stack<Integer> mRequests = new Stack<>();
private final AtomicInteger mDecodingIndex = new AtomicInteger(-1);
private final AtomicReference<UniFile[]> mFileList = new AtomicReference<>();
@Nullable
private Thread mBgThread;
private volatile int mSize = PageLoader.STATE_WAIT;
private String mError;
public DirPageLoader(@NonNull UniFile dir) {
mDir = dir;
}
@Override
public void start() {
mBgThread = new PriorityThread(this, TAG + '-' + sIdGenerator.incrementAndGet(),
Process.THREAD_PRIORITY_BACKGROUND);
mBgThread.start();
}
@Override
public void stop() {
if (mBgThread != null) {
mBgThread.interrupt();
mBgThread = null;
}
}
@Override
public int size() {
return mSize;
}
@Override
protected void onRequest(int index) {
synchronized (mRequests) {
if (!mRequests.contains(index) && index != mDecodingIndex.get()) {
mRequests.add(index);
mRequests.notify();
}
}
notifyPageWait(index);
}
@Override
protected void onForceRequest(int index) {
onRequest(index);
}
@Override
public void onCancelRequest(int index) {
synchronized (mRequests) {
mRequests.remove(Integer.valueOf(index));
}
}
@Override
public String getError() {
return mError;
}
@NonNull
@Override
public String getImageFilename(int index) {
UniFile[] fileList = mFileList.get();
if (null == fileList || index < 0 || index >= fileList.length) {
return Integer.toString(index);
}
UniFile src = fileList[index];
String name = src.getName();
if (name == null) {
return Integer.toString(index);
}
return name;
}
@NonNull
@Override
public String getImageFilenameWithExtension(int index) {
UniFile[] fileList = mFileList.get();
if (null == fileList || index < 0 || index >= fileList.length) {
return Integer.toString(index);
}
UniFile src = fileList[index];
String extension = FileUtils.getExtensionFromFilename(src.getName());
String name = src.getName();
if (name == null) {
return Integer.toString(index);
}
name = null != extension ? name + "." + extension : name;
return name;
}
@Override
public boolean save(int index, @NonNull UniFile file) {
UniFile[] fileList = mFileList.get();
if (null == fileList || index < 0 || index >= fileList.length) {
return false;
}
InputStream is = null;
OutputStream os = null;
try {
is = fileList[index].openInputStream();
os = file.openOutputStream();
IOUtils.copy(is, os);
return true;
} catch (IOException e) {
return false;
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
}
@Nullable
@Override
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
UniFile[] fileList = mFileList.get();
if (null == fileList || index < 0 || index >= fileList.length) {
return null;
}
UniFile src = fileList[index];
String extension = FileUtils.getExtensionFromFilename(src.getName());
UniFile dst = dir.subFile(null != extension ? filename + "." + extension : filename);
if (null == dst) {
return null;
}
InputStream is = null;
OutputStream os = null;
try {
is = src.openInputStream();
os = dst.openOutputStream();
IOUtils.copy(is, os);
return dst;
} catch (IOException e) {
return null;
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
}
@Override
public void run() {
// It may take a long time, so run it in new thread
UniFile[] files = mDir.listFiles(imageFilter);
if (files == null) {
mSize = PageLoader.STATE_ERROR;
mError = GetText.getString(R.string.error_not_folder_path);
// Notify to to show error
notifyDataChanged();
Log.i(TAG, "ImageDecoder end with error");
return;
}
// Sort it
Arrays.sort(files, naturalComparator);
// Put file list
mFileList.lazySet(files);
// Set state normal and notify
mSize = files.length;
notifyDataChanged();
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (mRequests) {
if (mRequests.isEmpty()) {
try {
mRequests.wait();
} catch (InterruptedException e) {
// Interrupted
break;
}
continue;
}
index = mRequests.pop();
mDecodingIndex.lazySet(index);
}
// Check index valid
if (index < 0 || index >= files.length) {
mDecodingIndex.lazySet(-1);
notifyPageFailed(index);
continue;
}
InputStream is = null;
try {
is = files[index].openInputStream();
Image image = Image.decode((FileInputStream) is);
mDecodingIndex.lazySet(-1);
notifyPageSucceed(index, image);
} catch (IOException e) {
mDecodingIndex.lazySet(-1);
notifyPageFailed(index);
} finally {
IOUtils.closeQuietly(is);
}
mDecodingIndex.lazySet(-1);
}
// Clear file list
mFileList.lazySet(null);
Log.i(TAG, "ImageDecoder end");
}
}
@@ -16,8 +16,6 @@
package com.hippo.ehviewer.gallery;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -33,19 +31,17 @@ 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 EhPageLoader(Context context, GalleryInfo galleryInfo) {
mContext = context;
public EhPageLoader(GalleryInfo galleryInfo) {
mGalleryInfo = galleryInfo;
}
@Override
public void start() {
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mContext, mGalleryInfo, SpiderQueen.MODE_READ);
mSpiderQueen = SpiderQueen.obtainSpiderQueen(mGalleryInfo, SpiderQueen.MODE_READ);
mSpiderQueen.addOnSpiderListener(this);
if (mSpiderQueen.size() > 0)
notifyDataChanged();
@@ -53,6 +49,7 @@ public class EhPageLoader extends PageLoader2 implements SpiderQueen.OnSpiderLis
@Override
public void stop() {
super.stop();
if (mSpiderQueen != null) {
mSpiderQueen.removeOnSpiderListener(this);
// Activity recreate may called, so wait 3000s
@@ -141,14 +141,11 @@ public final class SpiderDen {
}
public boolean isReady() {
switch (mMode) {
case SpiderQueen.MODE_READ:
return sCache != null;
case SpiderQueen.MODE_DOWNLOAD:
return mDownloadDir != null && mDownloadDir.isDirectory();
default:
return false;
}
return switch (mMode) {
case SpiderQueen.MODE_READ -> sCache != null;
case SpiderQueen.MODE_DOWNLOAD -> mDownloadDir != null && mDownloadDir.isDirectory();
default -> false;
};
}
@Nullable
@@ -16,7 +16,6 @@
package com.hippo.ehviewer.spider;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
import android.os.AsyncTask;
@@ -31,7 +30,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
@@ -66,6 +64,7 @@ import com.hippo.yorozuya.thread.PriorityThreadFactory;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -83,6 +82,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import coil.disk.DiskCache;
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader;
import okhttp3.Call;
import okhttp3.MediaType;
@@ -113,7 +113,7 @@ public final class SpiderQueen implements Runnable {
@NonNull
private final OkHttpClient mHttpClient;
@NonNull
private final SimpleDiskCache mSpiderInfoCache;
private final DiskCache mSpiderInfoCache;
@NonNull
private final GalleryInfo mGalleryInfo;
@NonNull
@@ -176,13 +176,11 @@ public final class SpiderQueen implements Runnable {
}
@UiThread
public static SpiderQueen obtainSpiderQueen(@NonNull Context context,
@NonNull GalleryInfo galleryInfo, @Mode int mode) {
public static SpiderQueen obtainSpiderQueen(@NonNull GalleryInfo galleryInfo, @Mode int mode) {
OSUtils.checkMainLoop();
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
if (queen == null) {
context.getApplicationContext();
queen = new SpiderQueen(galleryInfo);
sQueenMap.put(galleryInfo.gid, queen);
// Set mode
@@ -692,21 +690,16 @@ public final class SpiderQueen implements Runnable {
}
// Read from cache
InputStreamPipe pipe = mSpiderInfoCache.getInputStreamPipe(Long.toString(mGalleryInfo.gid));
if (null != pipe) {
try {
pipe.obtain();
spiderInfo = SpiderInfo.read(pipe.open());
if (spiderInfo != null && spiderInfo.gid == mGalleryInfo.gid &&
spiderInfo.token.equals(mGalleryInfo.token)) {
return spiderInfo;
}
} catch (IOException e) {
// Ignore
} finally {
pipe.close();
pipe.release();
var entry = mSpiderInfoCache.get(Long.toString(mGalleryInfo.gid));
if (entry == null) return null;
try (entry; InputStream is = new FileInputStream(entry.getData().toFile())) {
spiderInfo = SpiderInfo.read(is);
if (spiderInfo != null && spiderInfo.gid == mGalleryInfo.gid &&
spiderInfo.token.equals(mGalleryInfo.token)) {
return spiderInfo;
}
} catch (IOException e) {
// Ignore
}
return null;
@@ -846,16 +839,14 @@ public final class SpiderQueen implements Runnable {
}
}
// Read from cache
OutputStreamPipe pipe = mSpiderInfoCache.getOutputStreamPipe(Long.toString(mGalleryInfo.gid));
try {
pipe.obtain();
spiderInfo.write(pipe.open());
// Write to cache
var editor = mSpiderInfoCache.edit(Long.toString(mGalleryInfo.gid));
if (editor == null) return;
try (OutputStream os = new FileOutputStream(editor.getData().toFile())) {
spiderInfo.write(os);
editor.commit();
} catch (IOException e) {
// Ignore
} finally {
pipe.close();
pipe.release();
editor.abort();
}
}
@@ -1090,12 +1081,7 @@ public final class SpiderQueen implements Runnable {
private String getPageUrl(long gid, int index, String pToken,
String oldPageUrl, String skipHathKey) {
String pageUrl;
if (oldPageUrl != null) {
pageUrl = oldPageUrl;
} else {
pageUrl = EhUrl.getPageUrl(gid, index, pToken);
}
String pageUrl = oldPageUrl != null ? oldPageUrl : EhUrl.getPageUrl(gid, index, pToken);
// Add skipHathKey
if (skipHathKey != null) {
if (pageUrl.contains("?")) {
@@ -1251,10 +1237,7 @@ public final class SpiderQueen implements Runnable {
Log.d(TAG, "Start download image " + index);
}
// disable Call Timeout for image-downloading requests
Call call = mHttpClient.newBuilder()
.callTimeout(0, TimeUnit.SECONDS).build()
.newCall(new EhRequestBuilder(targetImageUrl, referer).build());
Call call = mHttpClient.newCall(new EhRequestBuilder(targetImageUrl, referer).build());
Response response = call.execute();
ResponseBody responseBody = response.body();
@@ -16,73 +16,43 @@
package com.hippo.ehviewer.ui
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import rikka.core.res.resolveColor
import eu.kanade.tachiyomi.util.system.isNightMode
import eu.kanade.tachiyomi.util.view.setSecureScreen
import rikka.insets.WindowInsetsHelper
import rikka.layoutinflater.view.LayoutInflaterFactory
abstract class EhActivity : AppCompatActivity() {
@StyleRes
fun getThemeStyleRes(context: Context): Int {
return if (THEME_BLACK == getTheme(context)) {
R.style.ThemeOverlay_Black
} else R.style.ThemeOverlay
fun getThemeStyleRes(): Int {
val isBlackDarkTheme = Settings.getBoolean("black_dark_theme", false)
return if (isBlackDarkTheme && isNightMode()) R.style.ThemeOverlay_Black else R.style.ThemeOverlay
}
override fun onApplyThemeResource(theme: Theme, resid: Int, first: Boolean) {
if (parent == null) {
theme.applyStyle(resid, true)
} else {
try {
theme.setTo(parent.theme)
} catch (ignored: Exception) {
}
theme.applyStyle(resid, false)
}
theme.applyStyle(getThemeStyleRes(this), true)
super.onApplyThemeResource(theme, R.style.ThemeOverlay, first)
theme.applyStyle(resid, true)
theme.applyStyle(getThemeStyleRes(), true)
}
override fun onNightModeChanged(mode: Int) {
theme.applyStyle(getThemeStyleRes(this), true)
theme.applyStyle(getThemeStyleRes(), true)
}
override fun onCreate(savedInstanceState: Bundle?) {
layoutInflater.factory2 =
LayoutInflaterFactory(delegate).addOnViewCreatedListener(
WindowInsetsHelper.LISTENER
)
layoutInflater.factory2 = LayoutInflaterFactory(delegate).addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
super.onCreate(savedInstanceState)
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars =
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES) <= 0
window.decorView.post {
val rootWindowInsets = window.decorView.rootWindowInsets
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
window.navigationBarColor =
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
}
}
(application as EhApplication).registerActivity(this)
}
@@ -92,56 +62,24 @@ abstract class EhActivity : AppCompatActivity() {
}
override fun onResume() {
val locked_resume_time = System.currentTimeMillis() / 1000
val locked_delay_time = locked_resume_time - EhApplication.locked_last_leave_time
if (locked_delay_time < Settings.getSecurityDelay() * 60) {
val lockedResumeTime = System.currentTimeMillis() / 1000
val lockedDelayTime = lockedResumeTime - EhApplication.locked_last_leave_time
if (lockedDelayTime < Settings.getSecurityDelay() * 60) {
EhApplication.locked = false
} else if (Settings.getSecurity() && isAuthenticationSupported() && EhApplication.locked) {
startActivity(Intent(this, SecurityActivity::class.java))
}
super.onResume()
if (Settings.getEnabledSecurity()) {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
window.setSecureScreen(Settings.getEnabledSecurity())
}
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) {
Settings.putNotificationRequired()
}
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { Settings.putNotificationRequired() }
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun checkAndRequestNotificationPermission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
return
}
if (Settings.getNotificationRequired())
return
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) return
if (Settings.getNotificationRequired()) return
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
companion object {
private const val THEME_DEFAULT = "DEFAULT"
private const val THEME_BLACK = "BLACK"
private val isBlackNightTheme: Boolean
get() = Settings.getBoolean("black_dark_theme", false)
fun getTheme(context: Context): String {
return if (isBlackNightTheme && isNightMode(context.resources.configuration)) THEME_BLACK else THEME_DEFAULT
}
private fun isNightMode(configuration: Configuration): Boolean {
return configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0
}
}
}
}
@@ -30,6 +30,7 @@ import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.util.addTextToClipboard
import com.hippo.util.tellClipboard
class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment() {
private var mKeys: ArrayList<String> = arrayListOf()
@@ -117,10 +118,7 @@ class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment(
if (position == INDEX_PARENT) {
UrlOpener.openUrl(context, mValues[position], true)
} else {
activity?.addTextToClipboard(
mValues[position],
false,
)
requireActivity() tellClipboard mValues[position]
if (position == INDEX_URL) {
// Save it to avoid detect the gallery
Settings.putClipboardTextHashCode(mValues[position].hashCode())
@@ -28,11 +28,11 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.IdRes;
@@ -40,6 +40,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.navigation.NavigationView;
@@ -49,7 +50,6 @@ import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhTagDatabase;
import com.hippo.ehviewer.client.EhUrlOpener;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
@@ -287,6 +287,9 @@ public final class MainActivity extends StageActivity
mDrawerLayout = (DrawerLayout) ViewUtils.$$(this, R.id.draw_view);
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
var callback = new DrawerOnBackPressedCallback(false, mDrawerLayout);
mDrawerLayout.addDrawerListener(callback);
getOnBackPressedDispatcher().addCallback(callback);
if (mNavView != null) {
mNavView.setNavigationItemSelectedListener(this);
@@ -481,9 +484,9 @@ public final class MainActivity extends StageActivity
baseScene.getLayoutInflater(), mRightDrawer, null);
if (drawerView != null) {
mRightDrawer.addView(drawerView);
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
} else {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
}
}
@@ -515,6 +518,13 @@ public final class MainActivity extends StageActivity
}
}
public Integer getDrawerLockMode(int edgeGravity) {
if (mDrawerLayout != null) {
return mDrawerLayout.getDrawerLockMode(edgeGravity);
}
return null;
}
public void openDrawer(int drawerGravity) {
if (mDrawerLayout != null) {
mDrawerLayout.openDrawer(drawerGravity);
@@ -566,17 +576,6 @@ public final class MainActivity extends StageActivity
}
}
@SuppressLint("RtlHardcoded")
@Override
public void onBackPressed() {
if (mDrawerLayout != null && (mDrawerLayout.isDrawerOpen(Gravity.LEFT) ||
mDrawerLayout.isDrawerOpen(Gravity.RIGHT))) {
mDrawerLayout.closeDrawers();
} else {
super.onBackPressed();
}
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Don't select twice
@@ -623,4 +622,40 @@ public final class MainActivity extends StageActivity
return true;
}
static class DrawerOnBackPressedCallback extends OnBackPressedCallback implements DrawerLayout.DrawerListener {
private final DrawerLayout mLayout;
public DrawerOnBackPressedCallback(boolean enabled, DrawerLayout drawerLayout) {
super(enabled);
mLayout = drawerLayout;
}
@Override
public void handleOnBackPressed() {
mLayout.closeDrawers();
}
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
setEnabled(true);
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {}
@Override
public void onDrawerStateChanged(int newState) {
switch (newState) {
case DrawerLayout.STATE_DRAGGING, DrawerLayout.STATE_SETTLING -> setEnabled(true);
case DrawerLayout.STATE_IDLE -> {
if (!mLayout.isDrawerVisible(GravityCompat.START) && !mLayout.isDrawerVisible(GravityCompat.END))
setEnabled(false);
}
}
}
}
}
@@ -19,7 +19,6 @@ package com.hippo.ehviewer.ui.fragment;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -333,14 +332,14 @@ public class AdvancedFragment extends BasePreferenceFragment {
Log.d("LocalFavorites", "now backup page " + status);
EhDB.putLocalFavorites(result.galleryInfoList);
if (result.nextPage != null) {
if (result.next != null) {
try {
Thread.sleep(Settings.getDownloadDelay());
} catch (InterruptedException e) {
e.printStackTrace();
}
favIndex++;
favListUrlBuilder.setNext(result.nextPage);
favListUrlBuilder.setIndex(result.next, true);
request.setArgs(favListUrlBuilder.build());
mClient.execute(request);
} else {
@@ -372,7 +371,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
String key = preference.getKey();
if (KEY_APP_LANGUAGE.equals(key) && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
if ("system".equals(newValue)) {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.wrap(Resources.getSystem().getConfiguration().getLocales()));
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList());
} else {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags((String) newValue));
}
@@ -1,90 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.ui.fragment;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.SettingsActivity;
public class BasePreferenceFragment extends PreferenceFragmentCompat
implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
@Override
public void onStart() {
super.onStart();
setTitle(getFragmentTitle());
}
@StringRes
public int getFragmentTitle() {
return -1;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
return false;
}
@Override
public boolean onPreferenceClick(Preference preference) {
return false;
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
String key = preference.getKey();
try {
Fragment classObj = (Fragment) Class.forName(key).newInstance();
requireActivity().getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.fragment, classObj)
.addToBackStack(null)
.commitAllowingStateLoss();
} catch (ClassNotFoundException | IllegalAccessException |
java.lang.InstantiationException ignored) {
}
return true;
}
private void setTitle(@StringRes int string) {
requireActivity().setTitle(string);
}
public void showTip(@StringRes int id, int length) {
((SettingsActivity) requireActivity()).showTip(getString(id), length);
}
public void showTip(CharSequence message, int length) {
((SettingsActivity) requireActivity()).showTip(message, length);
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.ui.fragment
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.fragment.app.FragmentTransaction
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.SettingsActivity
open class BasePreferenceFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
override fun onStart() {
super.onStart()
setTitle(fragmentTitle)
}
@get:StringRes
open val fragmentTitle: Int
get() = -1
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
return false
}
override fun onPreferenceClick(preference: Preference): Boolean {
return false
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
val fragment = when (preference.key) {
"eh" -> EhFragment()
"download" -> DownloadFragment()
"privacy" -> PrivacyFragment()
"advance" -> AdvancedFragment()
"about" -> AboutFragment()
"filter" -> FilterFragment()
"mytags" -> MyTagsFragment()
"uconfig" -> UConfigFragment()
"hosts" -> HostsFragment()
else -> null
}
fragment?.let {
requireActivity().supportFragmentManager
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.fragment, it)
.addToBackStack(null)
.commitAllowingStateLoss()
}
return true
}
private fun setTitle(@StringRes string: Int) {
requireActivity().setTitle(string)
}
fun showTip(@StringRes id: Int, length: Int) {
(requireActivity() as SettingsActivity).showTip(getString(id), length)
}
fun showTip(message: CharSequence?, length: Int) {
(requireActivity() as SettingsActivity).showTip(message, length)
}
}
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.ui.fragment
import android.app.Activity
import android.content.res.Configuration
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.ListPreference
import androidx.preference.Preference
@@ -52,7 +53,7 @@ class EhFragment : BasePreferenceFragment() {
thumbResolution!!.setSummaryProvider {
getString(R.string.settings_eh_thumb_resolution_summary, (it as ListPreference).entry)
}
if (!EhTagDatabase.isTranslatable()) {
if (!EhTagDatabase.isTranslatable(requireActivity())) {
preferenceScreen.removePreference(showTagTranslations)
preferenceScreen.removePreference(tagTranslationsSource!!)
}
@@ -91,7 +92,7 @@ class EhFragment : BasePreferenceFragment() {
return true
}
override fun getFragmentTitle(): Int {
return R.string.settings_eh
}
@get:StringRes
override val fragmentTitle: Int
get() = R.string.settings_eh
}
@@ -264,10 +264,14 @@ class FilterFragment : BaseFragment() {
private val mUploaderFilterList: List<Filter> = mEhFilter.uploaderFilterList
private val mTagFilterList: List<Filter> = mEhFilter.tagFilterList
private val mTagNamespaceFilterList: List<Filter> = mEhFilter.tagNamespaceFilterList
private val mCommenterFilterList: List<Filter> = mEhFilter.commenterFilterList
private val mCommentFilterList: List<Filter> = mEhFilter.commentFilterList
private var mTitleHeader: Filter? = null
private var mUploaderHeader: Filter? = null
private var mTagHeader: Filter? = null
private var mTagNamespaceHeader: Filter? = null
private var mCommenterHeader: Filter? = null
private var mCommentHeader: Filter? = null
fun size(): Int {
var count = 0
var size = mTitleFilterList.size
@@ -278,6 +282,10 @@ class FilterFragment : BaseFragment() {
count += if (0 == size) 0 else size + 1
size = mTagNamespaceFilterList.size
count += if (0 == size) 0 else size + 1
size = mCommenterFilterList.size;
count += if (0 == size) 0 else size + 1
size = mCommentFilterList.size;
count += if (0 == size) 0 else size + 1
return count
}
@@ -317,6 +325,24 @@ class FilterFragment : BaseFragment() {
}
return mTagNamespaceHeader!!
}
private val commenterHeader: Filter
get() {
if (null == mCommenterHeader) {
mCommenterHeader = Filter()
mCommenterHeader!!.mode = MODE_HEADER
mCommenterHeader!!.text = getString(R.string.filter_commenter)
}
return mCommenterHeader!!
}
private val commentHeader: Filter
get() {
if (null == mCommentHeader) {
mCommentHeader = Filter()
mCommentHeader!!.mode = MODE_HEADER
mCommentHeader!!.text = getString(R.string.filter_comment)
}
return mCommentHeader!!
}
operator fun get(index: Int): Filter {
var index1 = index
@@ -356,6 +382,26 @@ class FilterFragment : BaseFragment() {
return tagNamespaceHeader
} else if (index1 <= size) {
return mTagNamespaceFilterList[index1 - 1]
} else {
size + 1
}
}
size = mCommenterFilterList.size
if (0 != size) {
index1 -= if (index1 == 0) {
return commenterHeader
} else if (index1 <= size) {
return mCommenterFilterList[index1 - 1]
} else {
size + 1
}
}
size = mCommentFilterList.size
if (0 != size) {
if (index1 == 0) {
return commentHeader
} else if (index1 <= size) {
return mCommentFilterList[index1 - 1]
}
}
throw IndexOutOfBoundsException()
@@ -19,6 +19,7 @@
package com.hippo.ehviewer.ui.fragment
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
import com.hippo.ehviewer.R
@@ -59,9 +60,9 @@ class PrivacyFragment : BasePreferenceFragment() {
}
}
override fun getFragmentTitle(): Int {
return R.string.settings_privacy
}
@get:StringRes
override val fragmentTitle: Int
get() = R.string.settings_privacy
private fun setUnlockDelaySummary(value: Int) {
unlockDelay.summary = if (value == 0) {
@@ -33,6 +33,7 @@ import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
@@ -78,6 +79,14 @@ public abstract class BaseScene extends SceneFragment {
}
}
public Integer getDrawerLockMode(int edgeGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
return ((MainActivity) activity).getDrawerLockMode(edgeGravity);
}
return null;
}
public void openDrawer(int drawerGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
@@ -190,9 +199,9 @@ public abstract class BaseScene extends SceneFragment {
// Update left drawer locked state
if (needShowLeftDrawer()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
} else {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
}
// Update nav checked item
@@ -43,6 +43,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.ItemTouchHelper;
@@ -406,7 +407,7 @@ public class DownloadsScene extends ToolbarScene
@Override
public void onNavigationClick() {
toggleDrawer(Gravity.LEFT);
toggleDrawer(GravityCompat.START);
}
@Override
@@ -444,10 +445,10 @@ public class DownloadsScene extends ToolbarScene
}
return true;
} else if (id == R.id.action_open_download_labels) {
openDrawer(Gravity.RIGHT);
openDrawer(GravityCompat.END);
return true;
} else if (id == R.id.action_reset_reading_progress) {
new BaseDialogBuilder(getContext())
new BaseDialogBuilder(requireContext())
.setMessage(R.string.reset_reading_progress_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
@@ -566,14 +567,14 @@ public class DownloadsScene extends ToolbarScene
@Override
public void onStartDragHandler() {
// Lock right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
public void onEndDragHandler() {
// Restore right drawer
if (null != mRecyclerView && !mRecyclerView.isInCustomChoice()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
}
}
@@ -875,11 +876,13 @@ public class DownloadsScene extends ToolbarScene
private static class DownloadLabelHolder extends RecyclerView.ViewHolder {
private final TextView label;
private final ImageView edit;
private final ImageView option;
private DownloadLabelHolder(View itemView) {
super(itemView);
label = (TextView) ViewUtils.$$(itemView, R.id.tv_key);
edit = (ImageView) ViewUtils.$$(itemView, R.id.iv_edit);
option = (ImageView) ViewUtils.$$(itemView, R.id.iv_option);
}
}
@@ -933,12 +936,12 @@ public class DownloadsScene extends ToolbarScene
mLabel = label1;
updateForLabel();
updateView();
closeDrawer(Gravity.RIGHT);
closeDrawer(GravityCompat.END);
}
});
if (position > 0) {
holder.option.setVisibility(View.VISIBLE);
holder.option.setOnClickListener(v -> {
holder.edit.setVisibility(View.VISIBLE);
holder.edit.setOnClickListener(v -> {
if (context != null) {
EditTextDialogBuilder builder = new EditTextDialogBuilder(
context, label, getString(R.string.download_labels));
@@ -1195,8 +1198,8 @@ public class DownloadsScene extends ToolbarScene
mFabLayout.setExpanded(true);
}
// Lock drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
@@ -1205,8 +1208,8 @@ public class DownloadsScene extends ToolbarScene
mFabLayout.setExpanded(false);
}
// Unlock drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
}
@Override
@@ -22,7 +22,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
import android.view.Gravity;
@@ -34,6 +33,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -136,15 +136,13 @@ public class FavoritesScene extends SearchBarScene implements
@Override
public void run() {
if (mFabLayout != null) {
mFabLayout.setSecondaryFabVisibilityAt(0, true);
updateJumpFab(); // index: 0, 2
mFabLayout.setSecondaryFabVisibilityAt(1, true);
mFabLayout.setSecondaryFabVisibilityAt(2, true);
mFabLayout.setSecondaryFabVisibilityAt(3, false);
mFabLayout.setSecondaryFabVisibilityAt(4, false);
mFabLayout.setSecondaryFabVisibilityAt(5, false);
mFabLayout.setSecondaryFabVisibilityAt(6, false);
}
updateJumpFab();
}
};
private int mFavLocalCount = 0;
@@ -346,7 +344,8 @@ public class FavoritesScene extends SearchBarScene implements
// Hide jump fab on local fav cat
private void updateJumpFab() {
if (mFabLayout != null && mUrlBuilder != null) {
mFabLayout.setSecondaryFabVisibilityAt(1, mUrlBuilder.getFavCat() != FavListUrlBuilder.FAV_CAT_LOCAL);
mFabLayout.setSecondaryFabVisibilityAt(0, mUrlBuilder.getFavCat() != FavListUrlBuilder.FAV_CAT_LOCAL);
mFabLayout.setSecondaryFabVisibilityAt(2, mUrlBuilder.getFavCat() != FavListUrlBuilder.FAV_CAT_LOCAL);
}
}
@@ -427,8 +426,6 @@ public class FavoritesScene extends SearchBarScene implements
mFabLayout.toggle();
} else if (mSearchMode) {
exitSearchMode();
} else if (isSearchViewShown()) {
hideSearchView();
} else {
finish();
}
@@ -437,21 +434,21 @@ public class FavoritesScene extends SearchBarScene implements
@Override
public void onStartDragHandler() {
// Lock right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
public void onEndDragHandler() {
// Restore right drawer
if (null != mRecyclerView && !mRecyclerView.isInCustomChoice()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
}
showSearchBar();
}
public boolean onItemClick(int position) {
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) {
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(GravityCompat.END)) {
// Skip if in search mode
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
return true;
@@ -477,7 +474,7 @@ public class FavoritesScene extends SearchBarScene implements
updateJumpFab();
mHelper.refresh();
closeDrawer(Gravity.RIGHT);
closeDrawer(GravityCompat.END);
} else {
if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) {
@@ -570,7 +567,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, true));
}
@Override
@@ -582,17 +579,14 @@ public class FavoritesScene extends SearchBarScene implements
if (!mRecyclerView.isInCustomChoice()) {
switch (position) {
case 0: // Open right
openDrawer(Gravity.RIGHT);
break;
case 1: // Go to
if (mHelper.canGoTo()) {
showGoToDialog();
}
break;
case 2: // Refresh
mHelper.refresh();
break;
// Go to
case 0 -> {
if (mHelper.canGoTo()) showGoToDialog();
}
// Refresh
case 1 -> mHelper.refresh();
// Last page
case 2 -> mHelper.goTo("1-0", false);
}
view.setExpanded(false);
return;
@@ -678,8 +672,8 @@ public class FavoritesScene extends SearchBarScene implements
mHelper.setRefreshLayoutEnable(false);
}
// Lock drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
@@ -693,8 +687,8 @@ public class FavoritesScene extends SearchBarScene implements
mHelper.setRefreshLayoutEnable(true);
}
// Unlock drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
}
@Override
@@ -738,28 +732,7 @@ public class FavoritesScene extends SearchBarScene implements
}
updateSearchBar();
int pages;
if (result.nextPage == null)
pages = mHelper.pgCounter + 1;
else
pages = Integer.MAX_VALUE;
String prev = result.prevPage, next = result.nextPage;
switch (mHelper.operation) {
case -1:
if (prev != null) mHelper.upperPage = prev;
break;
case 1:
if (next != null) mHelper.lowerPage = next;
break;
default:
mHelper.upperPage = prev;
mHelper.lowerPage = next;
break;
}
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
mHelper.onGetPageData(taskId, 0, 0, result.prev, result.next, result.galleryInfoList);
if (mDrawerAdapter != null) {
mDrawerAdapter.notifyDataSetChanged();
@@ -784,9 +757,9 @@ public class FavoritesScene extends SearchBarScene implements
}
if (list.size() == 0) {
mHelper.onGetPageData(taskId, 0, 0, Collections.EMPTY_LIST);
mHelper.onGetPageData(taskId, 0, 0, null, null, Collections.EMPTY_LIST);
} else {
mHelper.onGetPageData(taskId, 1, 0, list);
mHelper.onGetPageData(taskId, 1, 0, null, null, list);
}
if (TextUtils.isEmpty(keyword)) {
@@ -1077,18 +1050,13 @@ public class FavoritesScene extends SearchBarScene implements
}
private class FavoritesHelper extends GalleryInfoContentHelper {
public String upperPage = null;
public String lowerPage = null;
public int operation = 0;
public int pgCounter = 0;
@Override
protected void getPageData(final int taskId, int type, int page) {
protected void getPageData(final int taskId, int type, int page, String index, boolean isNext) {
MainActivity activity = getMainActivity();
if (null == activity || null == mUrlBuilder || null == mClient) {
return;
}
pgCounter = page;
if (mEnableModify) {
mEnableModify = false;
@@ -1127,7 +1095,7 @@ public class FavoritesScene extends SearchBarScene implements
url = mUrlBuilder.build();
}
mUrlBuilder.setNext(lowerPage);
mUrlBuilder.setIndex(index, true);
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_MODIFY_FAVORITES);
request.setCallback(new GetFavoritesListener(getContext(),
@@ -1140,25 +1108,8 @@ public class FavoritesScene extends SearchBarScene implements
final String keyword = mUrlBuilder.getKeyword();
SimpleHandler.getInstance().post(() -> onGetFavoritesLocal(keyword, taskId));
} else {
String prev = null, next = null;
if (jumpTo != null) {
next = Integer.toString(minGid);
operation = 0;
} else if (page != 0 && mHelper != null) {
if (page >= mHelper.getPageForTop()) {
next = lowerPage;
operation = 1;
} else {
prev = upperPage;
operation = -1;
}
}
mUrlBuilder.setPrev(prev);
mUrlBuilder.setNext(next);
mUrlBuilder.setIndex(index, isNext);
mUrlBuilder.setJumpTo(jumpTo);
jumpTo = null;
String url = mUrlBuilder.build();
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
@@ -1187,13 +1138,6 @@ public class FavoritesScene extends SearchBarScene implements
}
}
@Override
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (mAdapter != null) {
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@Override
protected void notifyItemRangeInserted(int positionStart, int itemCount) {
if (mAdapter != null) {
@@ -1217,36 +1161,5 @@ public class FavoritesScene extends SearchBarScene implements
showSearchBar();
}
}
@Override
protected void onClearData() {
super.onClearData();
}
@Override
protected void beforeRefresh() {
super.beforeRefresh();
upperPage = null;
lowerPage = null;
operation = 0;
}
@NonNull
@Override
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(@NonNull Parcelable state) {
Bundle bundle = (Bundle) state;
upperPage = bundle.getString(KEY_UPPER_PAGE);
lowerPage = bundle.getString(KEY_LOWER_PAGE);
return super.restoreInstanceState(state);
}
}
}
@@ -106,8 +106,7 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
RecyclerView recyclerView = mRecyclerView;
switch (type) {
default:
case GalleryAdapter.TYPE_LIST: {
case GalleryAdapter.TYPE_LIST -> {
int columnWidth = mResources.getDimensionPixelOffset(Settings.getDetailSizeResId());
mLayoutManager.setColumnSize(columnWidth);
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE);
@@ -123,9 +122,8 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
recyclerView.addItemDecoration(mListDecoration);
adjustPaddings();
notifyDataSetChanged();
break;
}
case GalleryAdapter.TYPE_GRID: {
case GalleryAdapter.TYPE_GRID -> {
int columnWidth = Settings.getThumbSize();
mLayoutManager.setColumnSize(columnWidth);
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_SUITABLE_SIZE);
@@ -141,7 +139,6 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
recyclerView.addItemDecoration(mGirdDecoration);
adjustPaddings();
notifyDataSetChanged();
break;
}
}
}
@@ -149,16 +146,11 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
@NonNull
@Override
public GalleryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
int layoutId;
switch (viewType) {
default:
case TYPE_LIST:
layoutId = R.layout.item_gallery_list;
break;
case TYPE_GRID:
layoutId = R.layout.item_gallery_grid;
break;
}
int layoutId = switch (viewType) {
case TYPE_LIST -> R.layout.item_gallery_list;
case TYPE_GRID -> R.layout.item_gallery_grid;
default -> throw new IllegalStateException("Unexpected value: " + viewType);
};
GalleryHolder holder = new GalleryHolder(mInflater.inflate(layoutId, parent, false));
@@ -169,6 +161,9 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
holder.thumb.setLayoutParams(lp);
}
holder.card.setOnClickListener(v -> onItemClick(holder.itemView, holder.getBindingAdapterPosition()));
holder.card.setOnLongClickListener(v -> onItemLongClick(holder.itemView, holder.getBindingAdapterPosition()));
return holder;
}
@@ -193,8 +188,7 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
}
switch (mType) {
default:
case TYPE_LIST: {
case TYPE_LIST -> {
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
holder.title.setText(EhUtils.getSuitableTitle(gi));
holder.uploader.setText(gi.uploader);
@@ -223,9 +217,8 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
}
holder.favourited.setVisibility((mShowFavourited && gi.favoriteSlot >= -1 && gi.favoriteSlot <= 10) ? View.VISIBLE : View.GONE);
holder.downloaded.setVisibility(mDownloadManager.containDownloadInfo(gi.gid) ? View.VISIBLE : View.GONE);
break;
}
case TYPE_GRID: {
case TYPE_GRID -> {
((TileThumb) holder.thumb).setThumbSize(gi.thumbWidth, gi.thumbHeight);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
View category = holder.category;
@@ -238,15 +231,11 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
((TriangleDrawable) drawable).setColor(color);
}
holder.simpleLanguage.setText(gi.simpleLanguage);
break;
}
}
// Update transition name
ViewCompat.setTransitionName(holder.thumb, TransitionNameFactory.getThumbTransitionName(gi.gid));
holder.card.setOnClickListener(v -> onItemClick(holder.itemView, position));
holder.card.setOnLongClickListener(v -> onItemLongClick(holder.itemView, position));
}
@IntDef({TYPE_LIST, TYPE_GRID})
@@ -62,6 +62,7 @@ import com.hippo.ehviewer.R;
import com.hippo.ehviewer.UrlOpener;
import com.hippo.ehviewer.WindowInsetsAnimationHelper;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.data.GalleryComment;
@@ -69,6 +70,7 @@ import com.hippo.ehviewer.client.data.GalleryCommentList;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.VoteCommentParser;
import com.hippo.ehviewer.dao.Filter;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.text.URLImageGetter;
@@ -194,7 +196,6 @@ public final class GalleryCommentsScene extends ToolbarScene
mEditPanel,
mFabLayout
));
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(getTheme(), androidx.appcompat.R.attr.colorPrimary));
mRefreshLayout.setOnRefreshListener(this);
Context context = requireContext();
@@ -266,6 +267,45 @@ public final class GalleryCommentsScene extends ToolbarScene
onBackPressed();
}
private void showFilterCommenterDialog(String commenter, int position) {
Context context = getContext();
if (context == null || commenter == null) {
return;
}
new BaseDialogBuilder(context)
.setMessage(getString(R.string.filter_the_commenter, commenter))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Filter filter = new Filter();
filter.mode = EhFilter.MODE_COMMENTER;
filter.text = commenter;
EhFilter.getInstance().addFilter(filter);
hideComment(position);
showTip(R.string.filter_added, LENGTH_SHORT);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
private void hideComment(int position) {
if (mGalleryDetail == null || mGalleryDetail.comments == null || mGalleryDetail.comments.comments == null) {
return;
}
GalleryComment[] oldCommentsList = mGalleryDetail.comments.comments;
GalleryComment[] newCommentsList = new GalleryComment[oldCommentsList.length - 1];
for (int i = 0, j = 0; i < oldCommentsList.length; i++) {
if (i != position) {
newCommentsList[j] = oldCommentsList[i];
j++;
}
}
mGalleryDetail.comments.comments = newCommentsList;
mAdapter.notifyDataSetChanged();
updateView(true);
}
private void voteComment(long id, int vote) {
Context context = getContext();
MainActivity activity = getMainActivity();
@@ -340,6 +380,10 @@ public final class GalleryCommentsScene extends ToolbarScene
menu.add(resources.getString(R.string.copy_comment_text));
menuId.add(R.id.copy);
if (!comment.uploader && !comment.editable) {
menu.add(resources.getString(R.string.block_commenter));
menuId.add(R.id.block_commenter);
}
if (comment.editable) {
menu.add(resources.getString(R.string.edit_comment));
menuId.add(R.id.edit_comment);
@@ -365,6 +409,8 @@ public final class GalleryCommentsScene extends ToolbarScene
int id = menuId.get(which);
if (id == R.id.copy) {
ClipboardUtilKt.addTextToClipboard(requireActivity(), comment.comment, false);
} else if (id == R.id.block_commenter) {
showFilterCommenterDialog(comment.user, position);
} else if (id == R.id.vote_up) {
voteComment(comment.id, 1);
} else if (id == R.id.vote_down) {
@@ -57,7 +57,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.widget.NestedScrollView;
import com.google.android.material.progressindicator.CircularProgressIndicator;
@@ -261,7 +260,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private Pair<String, String>[] mArchiveList;
@State
private int mState = STATE_INIT;
private boolean mModifingFavorites;
private boolean mModifyingFavorites;
@Nullable
private static String getArtist(GalleryTagGroup[] tagGroups) {
@@ -293,47 +292,21 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
@StringRes
private int getRatingText(float rating) {
int resId;
switch (Math.round(rating * 2)) {
case 0:
resId = R.string.rating0;
break;
case 1:
resId = R.string.rating1;
break;
case 2:
resId = R.string.rating2;
break;
case 3:
resId = R.string.rating3;
break;
case 4:
resId = R.string.rating4;
break;
case 5:
resId = R.string.rating5;
break;
case 6:
resId = R.string.rating6;
break;
case 7:
resId = R.string.rating7;
break;
case 8:
resId = R.string.rating8;
break;
case 9:
resId = R.string.rating9;
break;
case 10:
resId = R.string.rating10;
break;
default:
resId = R.string.rating_none;
break;
}
return resId;
return switch (Math.round(rating * 2)) {
case 0 -> R.string.rating0;
case 1 -> R.string.rating1;
case 2 -> R.string.rating2;
case 3 -> R.string.rating3;
case 4 -> R.string.rating4;
case 5 -> R.string.rating5;
case 6 -> R.string.rating6;
case 7 -> R.string.rating7;
case 8 -> R.string.rating8;
case 9 -> R.string.rating9;
case 10 -> R.string.rating10;
default -> R.string.rating_none;
};
}
private void handleArgs(Bundle args) {
@@ -357,7 +330,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
@Nullable
private String getGalleryDetailUrl(boolean allComment) {
private String getGalleryDetailUrl() {
long gid;
String token;
if (mGalleryDetail != null) {
@@ -372,7 +345,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
} else {
return null;
}
return EhUrl.getGalleryDetailUrl(gid, token, 0, allComment);
return EhUrl.getGalleryDetailUrl(gid, token, 0, false);
}
// -1 for error
@@ -388,18 +361,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
}
private String getToken() {
if (mGalleryDetail != null) {
return mGalleryDetail.token;
} else if (mGalleryInfo != null) {
return mGalleryInfo.token;
} else if (ACTION_GID_TOKEN.equals(mAction)) {
return mToken;
} else {
return null;
}
}
private String getUploader() {
if (mGalleryDetail != null) {
return mGalleryDetail.uploader;
@@ -452,7 +413,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
super.onResume();
if (mRead != null) {
try {
PageLoader2 galleryProvider = new EhPageLoader(requireContext(), mGalleryInfo);
PageLoader2 galleryProvider = new EhPageLoader(mGalleryInfo);
galleryProvider.start();
int startPage = galleryProvider.getStartPage();
if (startPage != 0) {
@@ -519,14 +480,14 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
public boolean onMenuItemClick(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_open_in_other_app) {
String url = getGalleryDetailUrl(false);
String url = getGalleryDetailUrl();
Activity activity = getMainActivity();
if (null != url && null != activity) {
UrlOpener.openUrl(activity, url, false);
}
} else if (itemId == R.id.action_refresh) {
if (mState != STATE_REFRESH && mState != STATE_REFRESH_HEADER) {
adjustViewVisibility(STATE_REFRESH, true);
adjustViewVisibility(STATE_REFRESH);
request();
}
} else if (itemId == R.id.action_add_tag) {
@@ -550,7 +511,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return true;
}
@Nullable
@NonNull
@Override
public View onCreateViewWithToolbar(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
@@ -655,16 +616,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (prepareData()) {
if (mGalleryDetail != null) {
bindViewSecond();
adjustViewVisibility(STATE_NORMAL, false);
adjustViewVisibility(STATE_NORMAL);
} else if (mGalleryInfo != null) {
bindViewFirst();
adjustViewVisibility(STATE_REFRESH_HEADER, false);
adjustViewVisibility(STATE_REFRESH_HEADER);
} else {
adjustViewVisibility(STATE_REFRESH, false);
adjustViewVisibility(STATE_REFRESH);
}
} else {
mTip.setText(R.string.error_cannot_find_gallery);
adjustViewVisibility(STATE_FAILED, false);
adjustViewVisibility(STATE_FAILED);
}
EhApplication.getDownloadManager().addDownloadInfoListener(this);
@@ -759,12 +720,12 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private boolean request() {
Context context = getContext();
MainActivity activity = getMainActivity();
String url = getGalleryDetailUrl(false);
String url = getGalleryDetailUrl();
if (null == context || null == activity || null == url) {
return false;
}
EhClient.Callback callback = new GetGalleryDetailListener(context,
EhClient.Callback<?> callback = new GetGalleryDetailListener(context,
activity.getStageId(), getTag());
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
@@ -776,7 +737,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return true;
}
private void adjustViewVisibility(int state, boolean animation) {
private void adjustViewVisibility(int state) {
if (state == mState) {
return;
}
@@ -784,34 +745,29 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return;
}
int oldState = mState;
mState = state;
animation = !TRANSITION_ANIMATION_DISABLED && animation;
boolean animation = !TRANSITION_ANIMATION_DISABLED && animation;
switch (state) {
case STATE_NORMAL:
case STATE_NORMAL -> {
// Show mMainView
mViewTransition.showView(0, animation);
// Show mBelowHeader
mViewTransition2.showView(0, animation);
break;
case STATE_REFRESH:
}
case STATE_REFRESH ->
// Show mProgressView
mViewTransition.showView(1, animation);
break;
case STATE_REFRESH_HEADER:
mViewTransition.showView(1, animation);
case STATE_REFRESH_HEADER -> {
// Show mMainView
mViewTransition.showView(0, animation);
// Show mProgress
mViewTransition2.showView(1, animation);
break;
default:
case STATE_INIT:
case STATE_FAILED:
}
case STATE_INIT, STATE_FAILED ->
// Show mFailedView
mViewTransition.showView(2, animation);
break;
mViewTransition.showView(2, animation);
}
}
@@ -915,7 +871,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private void bindTags(GalleryTagGroup[] tagGroups) {
Context context = getContext();
LayoutInflater inflater = getLayoutInflater();
if (null == context || null == inflater || null == mTags || null == mNoTags) {
if (null == context || null == mTags || null == mNoTags) {
return;
}
@@ -927,7 +883,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mNoTags.setVisibility(View.GONE);
}
EhTagDatabase ehTags = Settings.getShowTagTranslations() ? EhTagDatabase.INSTANCE : null;
EhTagDatabase ehTags = Settings.getShowTagTranslations() && EhTagDatabase.INSTANCE.isTranslatable(context) ? EhTagDatabase.INSTANCE : null;
int colorTag = ResourcesKt.resolveColor(getTheme(), R.attr.tagBackgroundColor);
int colorName = ResourcesKt.resolveColor(getTheme(), R.attr.tagGroupBackgroundColor);
for (GalleryTagGroup tg : tagGroups) {
@@ -971,7 +927,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private void bindComments(GalleryComment[] comments) {
Context context = getContext();
LayoutInflater inflater = getLayoutInflater();
if (null == context || null == inflater || null == mComments || null == mCommentsText) {
if (null == context || null == mComments || null == mCommentsText) {
return;
}
@@ -1010,7 +966,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
LayoutInflater inflater = getLayoutInflater();
Resources resources = getResourcesOrNull();
int previewNum = Settings.getPreviewNum();
if (null == inflater || null == resources || null == mGridLayout || null == mPreviewText) {
if (null == resources || null == mGridLayout || null == mPreviewText) {
return;
}
@@ -1046,16 +1002,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return getString(R.string.rating_text, getString(getRatingText(rating)), rating, ratingCount);
}
private void setTransitionName() {
long gid = getGid();
if (gid != -1 && mThumb != null && mUploader != null && mCategory != null) {
ViewCompat.setTransitionName(mThumb, TransitionNameFactory.getThumbTransitionName(gid));
ViewCompat.setTransitionName(mUploader, TransitionNameFactory.getUploaderTransitionName(gid));
ViewCompat.setTransitionName(mCategory, TransitionNameFactory.getCategoryTransitionName(gid));
}
}
private void showSimilarGalleryList() {
GalleryDetail gd = mGalleryDetail;
if (null == gd) {
@@ -1117,7 +1063,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (mTip == v) {
if (request()) {
adjustViewVisibility(STATE_REFRESH, true);
adjustViewVisibility(STATE_REFRESH);
}
} else if (mUploader == v) {
String uploader = getUploader();
@@ -1188,17 +1134,17 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
var galleryInfoBottomSheet = new GalleryInfoBottomSheet(mGalleryDetail);
galleryInfoBottomSheet.show(requireActivity().getSupportFragmentManager(), GalleryInfoBottomSheet.TAG);
} else if (mHeart == v || mHeartOutline == v) {
if (mGalleryDetail != null && !mModifingFavorites) {
if (mGalleryDetail != null && !mModifyingFavorites) {
boolean remove = false;
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
mModifingFavorites = true;
mModifyingFavorites = true;
CommonOperations.removeFromFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(context,
activity.getStageId(), getTag(), true));
remove = true;
}
if (!remove) {
mModifingFavorites = true;
mModifyingFavorites = true;
CommonOperations.addToFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(context,
activity.getStageId(), getTag(), false));
@@ -1207,7 +1153,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
updateFavoriteDrawable();
}
} else if (mShare == v) {
String url = getGalleryDetailUrl(false);
String url = getGalleryDetailUrl();
if (url != null) {
AppHelper.share(activity, url);
}
@@ -1365,7 +1311,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
menu.add(resources.getString(android.R.string.copy));
menuId.add(R.id.copy);
if (!tag.equals(tv.getText().toString())) {
if (!tag2.equals(tv.getText().toString())) {
menu.add(resources.getString(R.string.copy_trans));
menuId.add(R.id.copy_trans);
}
@@ -1437,17 +1383,17 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
return true;
} else if (mHeart == v || mHeartOutline == v) {
if (mGalleryDetail != null && !mModifingFavorites) {
if (mGalleryDetail != null && !mModifyingFavorites) {
boolean remove = false;
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
mModifingFavorites = true;
mModifyingFavorites = true;
CommonOperations.removeFromFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(activity,
activity.getStageId(), getTag(), true));
remove = true;
}
if (!remove) {
mModifingFavorites = true;
mModifyingFavorites = true;
CommonOperations.addToFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(activity,
activity.getStageId(), getTag(), false), true);
@@ -1492,25 +1438,13 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return;
}
switch (mDownloadState) {
default:
case DownloadInfo.STATE_INVALID:
mDownload.setText(R.string.download);
break;
case DownloadInfo.STATE_NONE:
mDownload.setText(R.string.download_state_none);
break;
case DownloadInfo.STATE_WAIT:
mDownload.setText(R.string.download_state_wait);
break;
case DownloadInfo.STATE_DOWNLOAD:
mDownload.setText(R.string.download_state_downloading);
break;
case DownloadInfo.STATE_FINISH:
mDownload.setText(R.string.download_state_downloaded);
break;
case DownloadInfo.STATE_FAILED:
mDownload.setText(R.string.download_state_failed);
break;
case DownloadInfo.STATE_INVALID -> mDownload.setText(R.string.download);
case DownloadInfo.STATE_NONE -> mDownload.setText(R.string.download_state_none);
case DownloadInfo.STATE_WAIT -> mDownload.setText(R.string.download_state_wait);
case DownloadInfo.STATE_DOWNLOAD ->
mDownload.setText(R.string.download_state_downloading);
case DownloadInfo.STATE_FINISH -> mDownload.setText(R.string.download_state_downloaded);
case DownloadInfo.STATE_FAILED -> mDownload.setText(R.string.download_state_failed);
}
}
@@ -1570,7 +1504,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private void onGetGalleryDetailSuccess(GalleryDetail result) {
mGalleryDetail = result;
updateDownloadState();
adjustViewVisibility(STATE_NORMAL, true);
adjustViewVisibility(STATE_NORMAL);
bindViewSecond();
}
@@ -1580,7 +1514,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (null != context && null != mTip) {
String error = ExceptionUtils.getReadableString(e);
mTip.setText(error);
adjustViewVisibility(STATE_FAILED, true);
adjustViewVisibility(STATE_FAILED);
}
}
@@ -1598,26 +1532,26 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
private void onModifyFavoritesSuccess(boolean addOrRemove) {
mModifingFavorites = false;
mModifyingFavorites = false;
if (mGalleryDetail != null) {
mGalleryDetail.isFavorited = !addOrRemove && mGalleryDetail.favoriteName != null;
updateFavoriteDrawable();
}
}
private void onModifyFavoritesFailure(boolean addOrRemove) {
mModifingFavorites = false;
private void onModifyFavoritesFailure() {
mModifyingFavorites = false;
}
private void onModifyFavoritesCancel(boolean addOrRemove) {
mModifingFavorites = false;
private void onModifyFavoritesCancel() {
mModifyingFavorites = false;
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
String url = getGalleryDetailUrl(false);
String url = getGalleryDetailUrl();
if (url != null) {
outContent.setWebUri(Uri.parse(url));
}
@@ -1771,7 +1705,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
R.string.add_to_favorite_failure, LENGTH_LONG);
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onModifyFavoritesFailure(mAddOrRemove);
scene.onModifyFavoritesFailure();
}
}
@@ -1779,7 +1713,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
public void onCancel() {
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onModifyFavoritesCancel(mAddOrRemove);
scene.onModifyFavoritesCancel();
}
}
@@ -2105,7 +2039,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
}
private class DeleteDialogHelper implements DialogInterface.OnClickListener {
private static class DeleteDialogHelper implements DialogInterface.OnClickListener {
private final com.hippo.ehviewer.download.DownloadManager mDownloadManager;
private final GalleryInfo mGalleryInfo;
private final CheckBoxDialogBuilder mBuilder;
@@ -48,6 +48,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@@ -150,7 +151,7 @@ public final class GalleryListScene extends SearchBarScene
private EasyRecyclerView mRecyclerView;
@Nullable
private SearchLayout mSearchLayout;
ActivityResultLauncher<PickVisualMediaRequest> selectImageLauncher = registerForActivityResult(
final ActivityResultLauncher<PickVisualMediaRequest> selectImageLauncher = registerForActivityResult(
new ActivityResultContracts.PickVisualMedia(),
result -> mSearchLayout.setImageUri(result));
@Nullable
@@ -467,30 +468,33 @@ public final class GalleryListScene extends SearchBarScene
private void onUpdateUrlBuilder() {
ListUrlBuilder builder = mUrlBuilder;
Resources resources = getResourcesOrNull();
if (resources == null || builder == null || mSearchLayout == null) {
if (resources == null || builder == null || mSearchLayout == null || mFabLayout == null) {
return;
}
String keyword = builder.getKeyword();
int category = builder.getCategory();
boolean isTopList = builder.getMode() == ListUrlBuilder.MODE_TOPLIST;
var mode = builder.getMode();
boolean isPopular = mode == ListUrlBuilder.MODE_WHATS_HOT;
boolean isTopList = mode == ListUrlBuilder.MODE_TOPLIST;
if (isTopList != mIsTopList) {
mIsTopList = isTopList;
recreateDrawerView();
mFabLayout.getSecondaryFabAt(0).setImageResource(isTopList ? R.drawable.ic_baseline_format_list_numbered_24 : R.drawable.v_magnify_x24);
}
mFabLayout.setSecondaryFabVisibilityAt(1, ListUrlBuilder.MODE_WHATS_HOT != builder.getMode());
// Update fab visibility
mFabLayout.setSecondaryFabVisibilityAt(0, !isPopular);
mFabLayout.setSecondaryFabVisibilityAt(2, !isPopular);
// Update normal search mode
mSearchLayout.setNormalSearchMode(builder.getMode() == ListUrlBuilder.MODE_SUBSCRIPTION
mSearchLayout.setNormalSearchMode(mode == ListUrlBuilder.MODE_SUBSCRIPTION
? R.id.search_subscription_search
: R.id.search_normal_search);
// Update search edit text
if (!TextUtils.isEmpty(keyword) && !mIsTopList) {
if (builder.getMode() == ListUrlBuilder.MODE_TAG) {
if (mode == ListUrlBuilder.MODE_TAG) {
keyword = wrapTagKeyword(keyword);
}
setSearchBarText(keyword);
@@ -505,19 +509,16 @@ public final class GalleryListScene extends SearchBarScene
// Update nav checked item
int checkedItemId;
if (ListUrlBuilder.MODE_NORMAL == builder.getMode() &&
EhUtils.NONE == category &&
TextUtils.isEmpty(keyword)) {
checkedItemId = R.id.nav_homepage;
} else if (ListUrlBuilder.MODE_SUBSCRIPTION == builder.getMode()) {
checkedItemId = R.id.nav_subscription;
} else if (ListUrlBuilder.MODE_WHATS_HOT == builder.getMode()) {
checkedItemId = R.id.nav_whats_hot;
} else if (ListUrlBuilder.MODE_TOPLIST == builder.getMode()) {
checkedItemId = R.id.nav_toplist;
} else {
checkedItemId = 0;
}
checkedItemId = switch (mode) {
case ListUrlBuilder.MODE_NORMAL ->
EhUtils.NONE == category && TextUtils.isEmpty(keyword) ? R.id.nav_homepage : 0;
case ListUrlBuilder.MODE_SUBSCRIPTION -> R.id.nav_subscription;
case ListUrlBuilder.MODE_WHATS_HOT -> R.id.nav_whats_hot;
case ListUrlBuilder.MODE_TOPLIST -> R.id.nav_toplist;
case ListUrlBuilder.MODE_TAG, ListUrlBuilder.MODE_UPLOADER, ListUrlBuilder.MODE_IMAGE_SEARCH ->
0;
default -> throw new IllegalStateException("Unexpected value: " + mode);
};
setNavCheckedItem(checkedItemId);
mNavCheckedId = checkedItemId;
}
@@ -541,6 +542,7 @@ public final class GalleryListScene extends SearchBarScene
FastScroller fastScroller = mContentLayout.getFastScroller();
mSearchLayout = (SearchLayout) ViewUtils.$$(mainLayout, R.id.search_layout);
mFabLayout = (FabLayout) ViewUtils.$$(mainLayout, R.id.fab_layout);
AssertUtils.assertNotNull(container);
mSearchFab = ViewUtils.$$(container, R.id.search_fab);
ViewCompat.setWindowInsetsAnimationCallback(view, new WindowInsetsAnimationHelper(
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
@@ -656,7 +658,7 @@ public final class GalleryListScene extends SearchBarScene
final EasyRecyclerView recyclerView, final TextView tip) {
Context context = getContext();
final ListUrlBuilder urlBuilder = mUrlBuilder;
if (null == context || null == urlBuilder) {
if (null == context || null == urlBuilder || null == mHelper) {
return;
}
@@ -666,11 +668,17 @@ public final class GalleryListScene extends SearchBarScene
return;
}
var gi = mHelper.getFirstVisibleItem();
var next = gi != null ? "@" + (gi.gid + 1) : null;
// Check duplicate
for (QuickSearch q : mQuickSearchList) {
if (urlBuilder.equalsQuickSearch(q)) {
showTip(getString(R.string.duplicate_quick_search, q.name), LENGTH_LONG);
return;
var i = q.name.lastIndexOf("@");
if (i != -1 && q.name.substring(i).equals(next)) {
showTip(getString(R.string.duplicate_quick_search, q.name), LENGTH_LONG);
return;
}
}
}
@@ -678,6 +686,10 @@ public final class GalleryListScene extends SearchBarScene
getSuitableTitleForUrlBuilder(context.getResources(), urlBuilder, false), getString(R.string.quick_search));
builder.setTitle(R.string.add_quick_search_dialog_title);
builder.setPositiveButton(android.R.string.ok, null);
// TODO: It's ugly
final boolean[] checked = {Settings.getQSSaveProgress()};
final String[] hint = {getString(R.string.save_progress)};
builder.setMultiChoiceItems(hint, checked, (dialog, which, isChecked) -> checked[which] = isChecked);
final AlertDialog dialog = builder.show();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
String text = builder.getText().trim();
@@ -688,6 +700,13 @@ public final class GalleryListScene extends SearchBarScene
return;
}
if (checked[0] && next != null) {
text += next;
Settings.putQSSaveProgress(true);
} else {
Settings.putQSSaveProgress(false);
}
// Check name duplicate
for (QuickSearch q : mQuickSearchList) {
if (text.equals(q.name)) {
@@ -764,17 +783,11 @@ public final class GalleryListScene extends SearchBarScene
if (null != mFabLayout && mFabLayout.isExpanded()) {
return false;
}
if (isSearchViewShown())
return false;
return mState == STATE_NORMAL && getStackIndex() == 0;
}
@Override
public void onBackPressed() {
if (isSearchViewShown()) {
hideSearchView();
return;
}
if (null != mFabLayout && mFabLayout.isExpanded()) {
mFabLayout.setExpanded(false);
return;
@@ -797,14 +810,14 @@ public final class GalleryListScene extends SearchBarScene
}
}
public boolean onItemClick(int position) {
public void onItemClick(int position) {
if (null == mHelper || null == mRecyclerView) {
return false;
return;
}
GalleryInfo gi = mHelper.getDataAtEx(position);
if (gi == null) {
return true;
return;
}
Bundle args = new Bundle();
@@ -812,7 +825,6 @@ public final class GalleryListScene extends SearchBarScene
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, gi);
Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args);
startScene(announcer);
return true;
}
@Override
@@ -824,13 +836,13 @@ public final class GalleryListScene extends SearchBarScene
private void showGoToDialog() {
Context context = getContext();
if (null == context || null == mHelper) {
if (null == context || null == mHelper || null == mUrlBuilder) {
return;
}
if (ListUrlBuilder.MODE_TOPLIST == mUrlBuilder.getMode()) {
if (mIsTopList) {
final int page = mHelper.getPageForTop() + 1;
final int pages = 200;
final int pages = mHelper.getPages();
String hint = getString(R.string.go_to_hint, page, pages);
final EditTextDialogBuilder builder = new EditTextDialogBuilder(context, null, hint);
builder.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
@@ -856,7 +868,7 @@ public final class GalleryListScene extends SearchBarScene
return;
}
builder.setError(null);
mHelper.goToPage(goTo);
mHelper.goTo(goTo);
dialog.dismiss();
});
} else {
@@ -879,7 +891,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, true));
}
}
@@ -890,17 +902,20 @@ public final class GalleryListScene extends SearchBarScene
}
switch (position) {
case 0: // Open right
openDrawer(Gravity.RIGHT);
break;
case 1: // Go to
if (mHelper.canGoTo()) {
showGoToDialog();
// Go to
case 0 -> {
if (mHelper.canGoTo()) showGoToDialog();
}
// Refresh
case 1 -> mHelper.refresh();
// Last page
case 2 -> {
if (mIsTopList) {
mHelper.goTo(mHelper.getPages() - 1);
} else {
mHelper.goTo("1", false);
}
break;
case 2: // Refresh
mHelper.refresh();
break;
}
}
view.setExpanded(false);
@@ -913,12 +928,12 @@ public final class GalleryListScene extends SearchBarScene
}
if (expanded) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
mActionFabDrawable.setDelete(ANIMATE_TIME);
} else {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
mActionFabDrawable.setAdd(ANIMATE_TIME);
}
}
@@ -1217,23 +1232,23 @@ public final class GalleryListScene extends SearchBarScene
@Override
public void onStartDragHandler() {
// Lock right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
@Override
public void onEndDragHandler() {
// Restore right drawer
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
showSearchBar();
}
public void onStateChange(int newState) {
if (newState == STATE_NORMAL || newState == STATE_SIMPLE_SEARCH) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END);
} else {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END);
}
}
@@ -1256,21 +1271,16 @@ public final class GalleryListScene extends SearchBarScene
private void onGetGalleryListSuccess(GalleryListParser.Result result, int taskId) {
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
if (mHelper != null && mHelper.isCurrentTask(taskId) && mUrlBuilder != null) {
String emptyString = getString(mUrlBuilder.getMode() == ListUrlBuilder.MODE_SUBSCRIPTION && result.noWatchedTags
? R.string.gallery_list_empty_hit_subscription
: R.string.gallery_list_empty_hit);
mHelper.setEmptyString(emptyString);
int pages;
if (mIsTopList)
pages = 200;
else if (result.nextGid == 0)
pages = mHelper.pgCounter + 1;
else
pages = result.founds;
mHelper.onGetPageData(taskId, pages, mHelper.pgCounter + 1, result.galleryInfoList);
if (mIsTopList) {
mHelper.onGetPageData(taskId, result.pages, result.nextPage, null, null, result.galleryInfoList);
} else {
mHelper.onGetPageData(taskId, 0, 0, result.prev, result.next, result.galleryInfoList);
}
}
}
@@ -1441,12 +1451,13 @@ public final class GalleryListScene extends SearchBarScene
return;
}
mUrlBuilder.set(mQuickSearchList.get(position));
mUrlBuilder.setNextGid(0);
var q = mQuickSearchList.get(position);
mUrlBuilder.set(q);
onUpdateUrlBuilder();
mHelper.refresh();
var i = q.name.lastIndexOf("@");
mHelper.goTo(i != -1 ? q.name.substring(i + 1) : null, true);
setState(STATE_NORMAL);
closeDrawer(Gravity.RIGHT);
closeDrawer(GravityCompat.END);
});
} else {
int[] keywords = {11, 12, 13, 15};
@@ -1459,11 +1470,10 @@ public final class GalleryListScene extends SearchBarScene
}
mUrlBuilder.setKeyword(String.valueOf(keywords[position]));
mUrlBuilder.setNextGid(0);
onUpdateUrlBuilder();
mHelper.refresh();
setState(STATE_NORMAL);
closeDrawer(Gravity.RIGHT);
closeDrawer(GravityCompat.END);
});
}
}
@@ -1589,38 +1599,20 @@ public final class GalleryListScene extends SearchBarScene
}
private class GalleryListHelper extends GalleryInfoContentHelper {
public int pgCounter = 0;
@Override
protected void getPageData(int taskId, int type, int page) {
pgCounter = page;
protected void getPageData(int taskId, int type, int page, String index, boolean isNext) {
MainActivity activity = getMainActivity();
if (null == activity || null == mClient || null == mUrlBuilder) {
if (null == activity || null == mClient || null == mHelper || null == mUrlBuilder) {
return;
}
int prevGid = 0, nextGid = 0;
if (mIsTopList) {
if (jumpTo != null) {
pgCounter = Integer.parseInt(jumpTo);
nextGid = pgCounter;
} else {
nextGid = page;
}
} else if (jumpTo != null) {
nextGid = minGid;
} else if (page != 0) {
if (page >= mHelper.getPageForTop())
nextGid = minGid;
else
prevGid = maxGid;
mUrlBuilder.setJumpTo(String.valueOf(page));
} else {
mUrlBuilder.setIndex(index, isNext);
mUrlBuilder.setJumpTo(jumpTo);
}
mUrlBuilder.setPrevGid(prevGid);
mUrlBuilder.setNextGid(nextGid);
mUrlBuilder.setJumpTo(jumpTo);
jumpTo = null;
if (ListUrlBuilder.MODE_IMAGE_SEARCH == mUrlBuilder.getMode()) {
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_IMAGE_SEARCH);
@@ -1654,13 +1646,6 @@ public final class GalleryListScene extends SearchBarScene
}
}
@Override
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (null != mAdapter) {
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@Override
protected void notifyItemRangeInserted(int positionStart, int itemCount) {
if (null != mAdapter) {
@@ -247,7 +247,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
list.add(previewSet.getGalleryPreview(mGalleryInfo.gid, i));
}
mHelper.onGetPageData(taskId, result.second, 0, list);
mHelper.onGetPageData(taskId, result.second, 0, null, null, list);
}
}
@@ -343,7 +343,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
private class GalleryPreviewHelper extends ContentLayout.ContentHelper<GalleryPreview> {
@Override
protected void getPageData(final int taskId, int type, int page) {
protected void getPageData(final int taskId, int type, int page, String index, boolean isNext) {
MainActivity activity = getMainActivity();
if (null == activity || null == mClient || null == mGalleryInfo) {
onGetException(taskId, new EhException(getString(R.string.error_cannot_find_gallery)));
@@ -372,13 +372,6 @@ public class GalleryPreviewsScene extends ToolbarScene {
}
}
@Override
protected void notifyItemRangeChanged(int positionStart, int itemCount) {
if (mAdapter != null) {
mAdapter.notifyItemRangeChanged(positionStart, itemCount);
}
}
@Override
protected void notifyItemRangeInserted(int positionStart, int itemCount) {
if (mAdapter != null) {
@@ -25,6 +25,7 @@ import android.view.*
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.lifecycle.lifecycleScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
@@ -190,7 +191,7 @@ class HistoryScene : ToolbarScene() {
@SuppressLint("RtlHardcoded")
override fun onNavigationClick() {
toggleDrawer(Gravity.LEFT)
toggleDrawer(GravityCompat.START)
}
override fun getMenuResId(): Int {
@@ -9,13 +9,17 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.annotation.CallSuper
import androidx.core.view.GravityCompat
import androidx.core.widget.addTextChangedListener
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.search.SearchBar
import com.google.android.material.search.SearchView
import com.google.android.material.search.SearchView.TransitionListener
import com.google.android.material.shape.MaterialShapeDrawable
import com.hippo.app.BaseDialogBuilder
import com.hippo.ehviewer.R
@@ -73,18 +77,46 @@ abstract class SearchBarScene : ToolbarScene() {
else if (newState == SearchView.TransitionState.HIDING)
onSearchViewHidden()
}
mSearchView?.addTransitionListener(mSearchViewOnBackPressedCallback)
requireActivity().onBackPressedDispatcher.addCallback(mSearchViewOnBackPressedCallback)
val contentView = onCreateViewWithToolbar(inflater, view, savedInstanceState)
return view.apply { addView(contentView, 0) }
}
override fun onDestroyView() {
super.onDestroyView()
mSearchViewOnBackPressedCallback.remove()
}
private var privLockModeStart: Int? = null
private var privLockModeEnd: Int? = null
@CallSuper
open fun onSearchViewExpanded() {
privLockModeStart = getDrawerLockMode(GravityCompat.START)
privLockModeEnd = getDrawerLockMode(GravityCompat.END)
privLockModeStart?.let {
setDrawerLockMode(
DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
GravityCompat.START
)
}
privLockModeEnd?.let {
setDrawerLockMode(
DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
GravityCompat.END
)
}
(requireActivity() as StageActivity).updateBackPressCallBackStatus()
updateSuggestions()
}
@CallSuper
open fun onSearchViewHidden() {
privLockModeStart?.let { setDrawerLockMode(it, GravityCompat.START) }
privLockModeStart = null
privLockModeEnd?.let { setDrawerLockMode(it, GravityCompat.END) }
privLockModeEnd = null
(requireActivity() as StageActivity).updateBackPressCallBackStatus()
}
@@ -117,7 +149,7 @@ abstract class SearchBarScene : ToolbarScene() {
}
fun onApplySearch() {
val query = mSearchView?.text.toString().trim()
val query = (mToolbar as SearchBar).text.toString().trim()
if (!mAllowEmptySearch && query.isEmpty()) {
return
}
@@ -129,14 +161,6 @@ abstract class SearchBarScene : ToolbarScene() {
mAllowEmptySearch = allowEmptySearch
}
fun isSearchViewShown(): Boolean {
return mSearchView?.isShowing ?: false
}
fun hideSearchView() {
mSearchView?.hide()
}
private fun wrapTagKeyword(keyword: String): String {
var keyword = keyword
keyword = keyword.trim { it <= ' ' }
@@ -250,7 +274,7 @@ abstract class SearchBarScene : ToolbarScene() {
temp = text.substring(0, text.lastIndexOf(" ")) + " " + temp
}
it.setText(temp)
it.setSelection(text.length)
it.setSelection(temp.length)
}
}
}
@@ -311,7 +335,8 @@ abstract class SearchBarScene : ToolbarScene() {
val s = text.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (s.isNotEmpty()) {
val keyword = s[s.size - 1]
val translate = Settings.getShowTagTranslations() && isTranslatable()
val translate =
Settings.getShowTagTranslations() && isTranslatable(requireContext())
suggestFlow(keyword, translate, true).collect {
emit(TagSuggestion(it.first, it.second))
}
@@ -331,4 +356,22 @@ abstract class SearchBarScene : ToolbarScene() {
fun showSearchBar() {
mAppBarLayout?.setExpanded(true)
}
private val mSearchViewOnBackPressedCallback =
object : OnBackPressedCallback(false), TransitionListener {
override fun handleOnBackPressed() {
mSearchView?.hide()
}
override fun onStateChanged(
searchView: SearchView,
previousState: SearchView.TransitionState,
newState: SearchView.TransitionState
) {
if (newState == SearchView.TransitionState.SHOWING)
isEnabled = true
else if (newState == SearchView.TransitionState.HIDING)
isEnabled = false
}
}
}
@@ -27,16 +27,8 @@ import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.Locale
import kotlin.math.max
import kotlin.math.min
abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
@JvmField
var maxGid = -1
@JvmField
var minGid = -1
@JvmField
var jumpTo: String? = null
private val listener: FavouriteStatusRouter.Listener
@@ -58,26 +50,14 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
EhApplication.favouriteStatusRouter.removeListener(listener)
}
override fun onAddData(data: GalleryInfo?) {
data?.let { map[data.gid] = data }
}
override fun onAddData(data: List<GalleryInfo?>) {
for (info in data) {
info?.let {
if (maxGid == -1) maxGid = info.gid.toInt()
if (minGid == -1) minGid = info.gid.toInt()
maxGid = max(maxGid.toLong(), info.gid).toInt()
minGid = min(minGid.toLong(), info.gid).toInt()
map[info.gid] = info
}
}
}
override fun onRemoveData(data: GalleryInfo?) {
data?.let { map.remove(data.gid) }
}
override fun onRemoveData(data: List<GalleryInfo?>) {
for (info in data) {
info?.let { map.remove(info.gid) }
@@ -86,14 +66,6 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
override fun onClearData() {
map.clear()
maxGid = -1
minGid = -1
}
override fun beforeRefresh() {
super.beforeRefresh()
maxGid = -1
minGid = -1
}
override fun saveInstanceState(superState: Parcelable): Parcelable {
@@ -103,8 +75,6 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
val router = EhApplication.favouriteStatusRouter
val id = router.saveDataMap(map)
bundle.putInt(KEY_DATA_MAP, id)
bundle.putInt(KEY_MIN_GID, minGid)
bundle.putInt(KEY_MAX_GID, maxGid)
return bundle
}
@@ -118,28 +88,22 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
this.map = map
}
}
minGid = bundle.getInt(KEY_MIN_GID)
maxGid = bundle.getInt(KEY_MAX_GID)
return super.restoreInstanceState(state)
}
fun goTo(time: Long) {
fun goTo(time: Long, isNext: Boolean) {
val formatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd", Locale.US).withZone(ZoneOffset.UTC)
jumpTo = formatter.format(Instant.ofEpochMilli(time))
doGoToPage(Int.MAX_VALUE / 2)
}
fun goToPage(page: Int) {
jumpTo = page.toString()
doGoToPage(page)
if (isNext) {
goTo(mNext ?: "2", true)
} else {
goTo(mPrev, false)
}
jumpTo = null
}
companion object {
private const val KEY_DATA_MAP = "data_map"
private const val KEY_MAX_GID = "max_gid"
private const val KEY_MIN_GID = "min_gid"
const val KEY_UPPER_PAGE = "upper_page"
const val KEY_LOWER_PAGE = "lower_page"
}
}
@@ -207,8 +207,15 @@ class SearchLayout @JvmOverloads constructor(
if (mEnableAdvance) {
urlBuilder.advanceSearch = mTableAdvanceSearch.advanceSearch
urlBuilder.minRating = mTableAdvanceSearch.minRating
urlBuilder.pageFrom = mTableAdvanceSearch.pageFrom
urlBuilder.pageTo = mTableAdvanceSearch.pageTo
val pageFrom = mTableAdvanceSearch.pageFrom
val pageTo = mTableAdvanceSearch.pageTo
if (pageTo != -1 && pageTo < 10) {
throw EhException(context.getString(R.string.search_sp_err1))
} else if (pageFrom != -1 && pageTo != -1 && pageTo - pageFrom < 20) {
throw EhException(context.getString(R.string.search_sp_err2))
}
urlBuilder.pageFrom = pageFrom
urlBuilder.pageTo = pageTo
}
}
+26 -40
View File
@@ -19,70 +19,58 @@ package com.hippo.image
import android.graphics.ColorSpace
import android.graphics.ImageDecoder
import android.graphics.ImageDecoder.ALLOCATOR_HARDWARE
import android.graphics.ImageDecoder.DecodeException
import android.graphics.ImageDecoder.ImageInfo
import android.graphics.ImageDecoder.Source
import android.graphics.drawable.AnimatedImageDrawable
import android.graphics.drawable.Animatable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import com.hippo.UriArchiveAccessor
import com.hippo.ehviewer.EhApplication
import java.io.FileInputStream
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import kotlin.math.max
import kotlin.math.min
class Image private constructor(source: Source?, val release: () -> Unit = {}) {
class Image private constructor(source: Source, private val byteBuffer: ByteBuffer? = null) {
var mObtainedDrawable: Drawable?
init {
mObtainedDrawable = null
source?.let {
mObtainedDrawable =
ImageDecoder.decodeDrawable(source) { decoder: ImageDecoder, info: ImageInfo, src: Source ->
decoder.allocator = ALLOCATOR_HARDWARE
decoder.setTargetColorSpace(colorSpace)
decoder.setTargetSampleSize(
max(
min(
info.size.width / (2 * screenWidth),
info.size.height / (2 * screenHeight)
), 1
)
)
} // Should we lazy decode it?
}
mObtainedDrawable =
ImageDecoder.decodeDrawable(source) { decoder: ImageDecoder, info: ImageInfo, _: Source ->
decoder.setTargetColorSpace(colorSpace)
decoder.setTargetSampleSize(
min(
info.size.width / (2 * screenWidth),
info.size.height / (2 * screenHeight)
).coerceAtLeast(1)
)
}
if (mObtainedDrawable is BitmapDrawable)
byteBuffer?.let { UriArchiveAccessor.releaseByteBuffer(it) }
}
val width = mObtainedDrawable!!.intrinsicWidth
val height = mObtainedDrawable!!.intrinsicHeight
var started = false
val width: Int
get() = mObtainedDrawable?.intrinsicWidth ?: 0
val height: Int
get() = mObtainedDrawable?.intrinsicHeight ?: 0
@Synchronized
fun recycle() {
if (mObtainedDrawable is AnimatedImageDrawable) {
(mObtainedDrawable as AnimatedImageDrawable?)?.stop()
}
if (mObtainedDrawable is BitmapDrawable) {
(mObtainedDrawable as BitmapDrawable?)?.bitmap?.recycle()
(mObtainedDrawable as? Animatable)?.run {
stop()
byteBuffer?.let { UriArchiveAccessor.releaseByteBuffer(it) }
}
(mObtainedDrawable as? BitmapDrawable)?.run { bitmap.recycle() }
mObtainedDrawable?.callback = null
mObtainedDrawable = null
release()
}
fun start() {
if (!started) {
(mObtainedDrawable as AnimatedImageDrawable?)?.start()
}
}
companion object {
val screenWidth = EhApplication.application.resources.displayMetrics.widthPixels
val screenHeight = EhApplication.application.resources.displayMetrics.heightPixels
val isWideColorGamut = EhApplication.application.resources.configuration.isScreenWideColorGamut
val colorSpace = ColorSpace.get(if (isWideColorGamut) ColorSpace.Named.DISPLAY_P3 else ColorSpace.Named.SRGB)
var colorSpace = ColorSpace.get(if (isWideColorGamut && EhApplication.readerPreferences.wideColorGamut().get()) ColorSpace.Named.DISPLAY_P3 else ColorSpace.Named.SRGB)
@Throws(DecodeException::class)
@JvmStatic
@@ -98,11 +86,9 @@ class Image private constructor(source: Source?, val release: () -> Unit = {}) {
@Throws(DecodeException::class)
@JvmStatic
fun decode(buffer: ByteBuffer, release: () -> Unit? = {}): Image {
fun decode(buffer: ByteBuffer): Image {
val src = ImageDecoder.createSource(buffer)
return Image(src) {
release()
}
return Image(src, buffer)
}
}
}
@@ -34,6 +34,10 @@ fun Context.getClipboardManager(): ClipboardManager {
return getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
}
infix fun Context.tellClipboard(text: String?) {
addTextToClipboard(text, false)
}
fun Context.addTextToClipboard(text: String?, isSensitive: Boolean) {
getClipboardManager().apply {
setPrimaryClip(ClipData.newPlainText(null, text).apply {
@@ -104,8 +104,6 @@ public class ContentLayout extends FrameLayout {
mBottomProgress = mContentView.findViewById(R.id.bottom_progress);
mFastScroller = mContentView.findViewById(R.id.fast_scroller);
mRecyclerView = mRefreshLayout.findViewById(R.id.recycler_view);
mRefreshLayout.setColorSchemeColors(ResourcesKt.resolveColor(context.getTheme(), androidx.appcompat.R.attr.colorPrimary));
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable drawable = new HandlerDrawable();
drawable.setColor(ResourcesKt.resolveColor(context.getTheme(), androidx.appcompat.R.attr.colorPrimary));
@@ -192,6 +190,8 @@ public class ContentLayout extends FrameLayout {
private static final String KEY_START_PAGE = "start_page";
private static final String KEY_END_PAGE = "end_page";
private static final String KEY_PAGES = "pages";
private static final String KEY_PREV = "prev";
private static final String KEY_NEXT = "next";
/**
* Generate task id
*/
@@ -227,17 +227,19 @@ public class ContentLayout extends FrameLayout {
*/
private int mPages;
private int mNextPage;
protected String mPrev;
protected String mNext;
private int mCurrentTaskId;
private int mCurrentTaskType;
private int mCurrentTaskPage;
private final SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if (mStartPage > 0) {
if (mPrev != null || mStartPage > 0) {
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_PRE_PAGE_KEEP_POS;
mCurrentTaskPage = mStartPage - 1;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, mPrev, false);
} else {
doRefresh();
}
@@ -248,31 +250,28 @@ public class ContentLayout extends FrameLayout {
private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (!mRefreshLayout.isRefreshing() && !recyclerView.canScrollVertically(1) && mEndPage < mPages) {
// Get next page
mBottomProgress.show();
if (mEndPage < mPages) {
if (!mRefreshLayout.isRefreshing() && !recyclerView.canScrollVertically(1)) {
if (mNext != null || mEndPage < mPages) {
mBottomProgress.show();
// Get next page
// Fill pages before NextPage with empty list
while (mNextPage > mEndPage && mEndPage < mPages) {
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS;
mCurrentTaskPage = mEndPage;
onGetPageData(mCurrentTaskId, mPages, mNextPage, Collections.emptyList());
onGetPageData(mCurrentTaskId, mPages, mNextPage, null, null, Collections.emptyList());
}
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_NEXT_PAGE_KEEP_POS;
mCurrentTaskPage = mEndPage;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
} else if (mEndPage == mPages) {
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, mNext, true);
} else if (mStartPage > 0 && mEndPage == mPages) {
mBottomProgress.show();
// Refresh last page
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_REFRESH_PAGE;
mCurrentTaskPage = mEndPage - 1;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
} else {
Log.e(TAG, "Try to footer refresh, but mEndPage = " + mEndPage + ", mPages = " + mPages);
mBottomProgress.hide();
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true);
}
}
}
@@ -291,6 +290,7 @@ public class ContentLayout extends FrameLayout {
mRecyclerView = contentLayout.mRecyclerView;
Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.big_sad_pandroid);
assert drawable != null;
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
mTipView.setCompoundDrawables(null, drawable, null, null);
@@ -304,19 +304,19 @@ public class ContentLayout extends FrameLayout {
}
/**
* Call {@link #onGetPageData(int, int, int, List)} when get data
* Call {@link #onGetPageData(int, int, int, String, String, List)} when get data
*
* @param taskId task id
* @param page the page to get
* @param index the index to get
* @param isNext the index is next or prev
*/
protected abstract void getPageData(int taskId, int type, int page);
protected abstract void getPageData(int taskId, int type, int page, String index, boolean isNext);
protected abstract Context getContext();
protected abstract void notifyDataSetChanged();
protected abstract void notifyItemRangeChanged(int positionStart, int itemCount);
protected abstract void notifyItemRangeInserted(int positionStart, int itemCount);
protected void onScrollToPosition(int position) {
@@ -355,6 +355,11 @@ public class ContentLayout extends FrameLayout {
}
}
@Nullable
public E getFirstVisibleItem() {
return getDataAtEx(LayoutManagerUtils.getFirstVisibleItemPosition(mRecyclerView.getLayoutManager()));
}
public int size() {
return mData.size();
}
@@ -383,22 +388,16 @@ public class ContentLayout extends FrameLayout {
}
}
protected void onAddData(E data) {
}
protected void onAddData(List<E> data) {
}
protected void onRemoveData(E data) {
}
protected void onRemoveData(List<E> data) {
}
protected void onClearData() {
}
public void onGetPageData(int taskId, int pages, int nextPage, List<E> data) {
public void onGetPageData(int taskId, int pages, int nextPage, String prev, String next, List<E> data) {
if (mCurrentTaskId == taskId) {
int dataSize;
@@ -408,6 +407,8 @@ public class ContentLayout extends FrameLayout {
mEndPage = 1;
mPages = pages;
mNextPage = nextPage;
mPrev = prev;
mNext = next;
mPageDivider.clear();
mPageDivider.add(data.size());
if (data.isEmpty()) {
@@ -449,6 +450,7 @@ public class ContentLayout extends FrameLayout {
mPageDivider.add(0, dataSize);
mStartPage--;
mPages = Math.max(mEndPage, pages);
mPrev = prev;
// assert mStartPage >= 0
if (data.isEmpty()) {
@@ -475,7 +477,6 @@ public class ContentLayout extends FrameLayout {
mData.addAll(0, data);
onAddData(data);
notifyItemRangeInserted(0, data.size());
notifyItemRangeChanged(data.size(), mData.size() - data.size());
// Ui change, show content
mRefreshLayout.setRefreshing(false);
@@ -504,6 +505,7 @@ public class ContentLayout extends FrameLayout {
mEndPage++;
mNextPage = nextPage;
mPages = Math.max(mEndPage, pages);
mNext = next;
if (data.isEmpty()) {
// OK, that's all
if (mData.isEmpty()) {
@@ -551,6 +553,8 @@ public class ContentLayout extends FrameLayout {
mEndPage = mCurrentTaskPage + 1;
mNextPage = nextPage;
mPages = pages;
mPrev = prev;
mNext = next;
mPageDivider.clear();
mPageDivider.add(data.size());
if (data.isEmpty()) {
@@ -654,10 +658,6 @@ public class ContentLayout extends FrameLayout {
mViewTransition.showView(0);
}
protected void beforeRefresh() {
}
private boolean isContentShowing() {
return mViewTransition.getShownViewIndex() == 0;
}
@@ -679,12 +679,11 @@ public class ContentLayout extends FrameLayout {
showText(mEmptyString);
}
protected void doRefresh() {
beforeRefresh();
private void doRefresh() {
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_REFRESH;
mCurrentTaskPage = 0;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true);
}
/**
@@ -757,42 +756,44 @@ public class ContentLayout extends FrameLayout {
LayoutManagerUtils.scrollToPositionWithOffset(mRecyclerView.getLayoutManager(), position, 0);
onScrollToPosition(position);
} else if (page == mStartPage - 1) {
mRefreshLayout.setRefreshing(false);
mRefreshLayout.setRefreshing(true);
mBottomProgress.hide();
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_PRE_PAGE;
mCurrentTaskPage = page;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true);
} else if (page == mEndPage) {
mRefreshLayout.setRefreshing(false);
mBottomProgress.hide();
mBottomProgress.show();
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_NEXT_PAGE;
mCurrentTaskPage = page;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true);
} else {
mRefreshLayout.setRefreshing(false);
mRefreshLayout.setRefreshing(true);
mBottomProgress.hide();
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_SOMEWHERE;
mCurrentTaskPage = page;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, null, true);
}
}
protected void doGoToPage(int page) {
mRefreshLayout.setRefreshing(false);
public void goTo(String index, boolean isNext) {
mRefreshLayout.setRefreshing(true);
mBottomProgress.hide();
mCurrentTaskId = mIdGenerator.nextId();
mCurrentTaskType = TYPE_SOMEWHERE;
mCurrentTaskPage = page;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage);
mCurrentTaskPage = 0;
getPageData(mCurrentTaskId, mCurrentTaskType, mCurrentTaskPage, index, isNext);
}
protected Parcelable saveInstanceState(Parcelable superState) {
cancelCurrentTask();
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_SUPER, superState);
int shownView = mViewTransition.getShownViewIndex();
@@ -813,6 +814,8 @@ public class ContentLayout extends FrameLayout {
bundle.putInt(KEY_START_PAGE, mStartPage);
bundle.putInt(KEY_END_PAGE, mEndPage);
bundle.putInt(KEY_PAGES, mPages);
bundle.putString(KEY_PREV, mPrev);
bundle.putString(KEY_NEXT, mNext);
return bundle;
}
@@ -837,6 +840,8 @@ public class ContentLayout extends FrameLayout {
mStartPage = bundle.getInt(KEY_START_PAGE);
mEndPage = bundle.getInt(KEY_END_PAGE);
mPages = bundle.getInt(KEY_PAGES);
mPrev = bundle.getString(KEY_PREV);
mNext = bundle.getString(KEY_NEXT);
notifyDataSetChanged();
@@ -845,6 +850,8 @@ public class ContentLayout extends FrameLayout {
mStartPage = 0;
mEndPage = 0;
mPages = 0;
mPrev = null;
mNext = null;
firstRefresh();
}
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.source.model
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
open class Page(val index: Int) {
@@ -9,7 +10,14 @@ open class Page(val index: Int) {
var status = MutableStateFlow(State.QUEUE)
var progress = MutableStateFlow(0f)
private val _progressFlow = MutableStateFlow(0)
val progressFlow = _progressFlow.asStateFlow()
var progress: Int
get() = _progressFlow.value
set(value) {
_progressFlow.value = value
}
enum class State {
QUEUE,
@@ -23,6 +23,7 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.ContentValues
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.ColorMatrix
@@ -68,20 +69,18 @@ import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.databinding.ReaderActivityBinding
import com.hippo.ehviewer.gallery.ArchivePageLoader
import com.hippo.ehviewer.gallery.DirPageLoader
import com.hippo.ehviewer.gallery.EhPageLoader
import com.hippo.ehviewer.gallery.PageLoader2
import com.hippo.ehviewer.ui.EhActivity
import com.hippo.image.Image
import com.hippo.unifile.UniFile
import com.hippo.util.ExceptionUtils
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.IOUtils
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.core.preference.AndroidPreferenceStore
import eu.kanade.tachiyomi.ui.reader.loader.PageLoader
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
@@ -197,16 +196,10 @@ class ReaderActivity : EhActivity() {
return
}
if (ACTION_DIR == mAction) {
if (mFilename != null) {
mGalleryProvider = DirPageLoader(
UniFile.fromFile(File(mFilename!!))!!
)
}
} else if (ACTION_EH == mAction) {
if (ACTION_EH == mAction) {
if (mGalleryInfo != null) {
mGalleryProvider =
EhPageLoader(this, mGalleryInfo)
EhPageLoader(mGalleryInfo)
}
} else if (Intent.ACTION_VIEW == mAction) {
if (mUri != null) {
@@ -260,6 +253,7 @@ class ReaderActivity : EhActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.colorMode = if (Image.isWideColorGamut && readerPreferences.wideColorGamut().get()) ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT else ActivityInfo.COLOR_MODE_DEFAULT
if (savedInstanceState == null) {
onInit()
} else {
@@ -305,6 +299,7 @@ class ReaderActivity : EhActivity() {
binding.actionReadingMode.setImageResource(viewerMode.iconRes)
viewer?.destroy()
viewer = ReadingModeType.toViewer(readerPreferences.defaultReadingMode().get(), this)
binding.pageSlider.isRTL = viewer is R2LPagerViewer
updateViewerInset(readerPreferences.fullscreen().get())
binding.viewerContainer.removeAllViews()
setOrientation(readerPreferences.defaultOrientationType().get())
@@ -421,7 +416,7 @@ class ReaderActivity : EhActivity() {
}
}
private fun saveImage(page: Int) {
fun saveImage(page: Int) {
if (null == mGalleryProvider) {
return
}
@@ -589,7 +584,6 @@ class ReaderActivity : EhActivity() {
}
companion object {
const val ACTION_DIR = "dir"
const val ACTION_EH = "eh"
const val KEY_ACTION = "action"
const val KEY_FILENAME = "filename"
@@ -598,7 +592,7 @@ class ReaderActivity : EhActivity() {
const val KEY_PAGE = "page"
const val KEY_CURRENT_INDEX = "current_index"
val readerPreferences by lazy { ReaderPreferences(AndroidPreferenceStore(EhApplication.application)) }
val readerPreferences = EhApplication.readerPreferences
}
/* Tachiyomi funcs */
@@ -645,7 +639,8 @@ class ReaderActivity : EhActivity() {
} else {
if (readerPreferences.fullscreen().get()) {
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
if (animate) {
@@ -701,7 +696,8 @@ class ReaderActivity : EhActivity() {
initBottomShortcuts()
val toolbarBackground = MaterialShapeDrawable.createWithElevationOverlay(this).apply {
elevation = resources.getDimension(com.google.android.material.R.dimen.m3_sys_elevation_level2)
elevation =
resources.getDimension(com.google.android.material.R.dimen.m3_sys_elevation_level2)
alpha = if (isNightMode()) 230 else 242 // 90% dark 95% light
}
binding.toolbarBottom.background = toolbarBackground.copy(this@ReaderActivity)
@@ -747,7 +743,8 @@ class ReaderActivity : EhActivity() {
setTooltip(R.string.pref_crop_borders)
setOnClickListener {
val isPagerType = ReadingModeType.isPagerType(readerPreferences.defaultReadingMode().get())
val isPagerType =
ReadingModeType.isPagerType(readerPreferences.defaultReadingMode().get())
if (isPagerType) {
readerPreferences.cropBorders().toggle()
} else {
@@ -848,7 +845,7 @@ class ReaderActivity : EhActivity() {
* Moves the viewer to the given page [index]. It does nothing if the viewer is null or the
* page is not found.
*/
fun moveToPageIndex(index: Int) {
private fun moveToPageIndex(index: Int) {
val viewer = viewer ?: return
val page = mGalleryProvider?.mPages?.getOrNull(index) ?: return
viewer.moveToPage(page)
@@ -21,6 +21,7 @@ class ReaderPageSheet(
binding.share.setOnClickListener { share() }
binding.save.setOnClickListener { save() }
binding.saveTo.setOnClickListener { saveTo() }
return binding.root
}
@@ -37,6 +38,14 @@ class ReaderPageSheet(
* Saves the image of this page on external storage.
*/
private fun save() {
activity.saveImage(page.index)
dismiss()
}
/**
* Saves the image of this page to a custom location on external storage.
*/
private fun saveTo() {
activity.saveImageTo(page.index)
dismiss()
}
@@ -1,11 +1,17 @@
package eu.kanade.tachiyomi.ui.reader.loader
import androidx.annotation.CallSuper
import androidx.collection.LruCache
import com.hippo.image.Image
import com.hippo.yorozuya.MathUtils
import com.hippo.yorozuya.OSUtils
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import kotlinx.coroutines.flow.MutableStateFlow
abstract class PageLoader {
private val mImageCache = ImageCache()
val mPages by lazy {
check(size() > 0)
(0 until size()).map { ReaderPage(it) }
@@ -17,12 +23,28 @@ abstract class PageLoader {
abstract fun start()
abstract fun stop()
@CallSuper
open fun stop() {
mImageCache.evictAll()
}
fun restart() {
mImageCache.evictAll()
}
abstract fun size(): Int
fun request(index: Int) {
onRequest(index)
fun request(page: ReaderPage) {
val index = mPages.indexOf(page)
val image = mImageCache[index]
if (image != null)
notifyPageSucceed(index, image)
else
onRequest(index)
}
fun retryPage(page: ReaderPage) {
onForceRequest(mPages.indexOf(page))
}
protected abstract fun onRequest(index: Int)
@@ -48,20 +70,39 @@ abstract class PageLoader {
fun notifyPagePercent(index: Int, percent: Float) {
mPages[index].status.compareAndSet(Page.State.QUEUE, Page.State.DOWNLOAD_IMAGE)
mPages[index].progress.value = percent
mPages[index].progress = (percent * 100).toInt()
}
fun notifyPageSucceed(index: Int, image: Image) {
val priv = mPages[index].image
if (mImageCache[index] != image)
mImageCache.add(index, image)
mPages[index].image = image
mPages[index].status.value = Page.State.READY
priv?.recycle()
}
fun notifyPageFailed(index: Int) {
mPages[index].status.value = Page.State.ERROR
}
private class ImageCache : LruCache<Int, Image>(MathUtils.clamp(OSUtils.getTotalMemory() / 16, MIN_CACHE_SIZE, MAX_CACHE_SIZE).toInt()) {
fun add(key: Int, value: Image) {
put(key, value)
}
override fun sizeOf(key: Int, value: Image): Int {
return value.height * value.width * 4
}
override fun entryRemoved(evicted: Boolean, key: Int, oldValue: Image, newValue: Image?) {
oldValue.recycle()
}
companion object {
private const val MAX_CACHE_SIZE = (512 * 1024 * 1024).toLong()
private const val MIN_CACHE_SIZE = (128 * 1024 * 1024).toLong()
}
}
companion object {
const val STATE_WAIT = -1
const val STATE_ERROR = -2
@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.ui.reader.setting
import android.content.Context
import android.content.pm.ActivityInfo
import android.graphics.ColorSpace
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.annotation.ColorInt
@@ -12,7 +14,9 @@ import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
import eu.kanade.tachiyomi.core.preference.getAndSet
import com.hippo.ehviewer.databinding.ReaderColorFilterSettingsBinding
import com.hippo.image.Image
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.preference.asHotFlow
import eu.kanade.tachiyomi.util.preference.bindToPreference
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -31,9 +35,26 @@ class ReaderColorFilterSettings @JvmOverloads constructor(context: Context, attr
init {
addView(binding.root)
binding.wideColorGamut.isEnabled = Image.isWideColorGamut
if (!Image.isWideColorGamut)
readerPreferences.wideColorGamut().set(false)
binding.wideColorGamut.bindToPreference(readerPreferences.wideColorGamut())
readerPreferences.wideColorGamut().asHotFlow {
Image.colorSpace = ColorSpace.get(if (Image.isWideColorGamut && it) ColorSpace.Named.DISPLAY_P3 else ColorSpace.Named.SRGB)
val colorMode = if (Image.isWideColorGamut && it) ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT else ActivityInfo.COLOR_MODE_DEFAULT
(context as ReaderActivity).run {
if (window.colorMode != colorMode) {
window.colorMode = colorMode
mGalleryProvider?.restart()
setGallery()
}
}
}.launchIn((context as ReaderActivity).lifecycleScope)
readerPreferences.colorFilter().changes()
.onEach { setColorFilter(it) }
.launchIn((context as ReaderActivity).lifecycleScope)
.launchIn(context.lifecycleScope)
readerPreferences.colorFilterMode().changes()
.onEach { setColorFilter(readerPreferences.colorFilter().get()) }
@@ -40,7 +40,7 @@ class ReaderPreferences(private val preferenceStore: PreferenceStore) {
fun navigateToPan() = preferenceStore.getBoolean("navigate_pan", true)
fun landscapeZoom() = preferenceStore.getBoolean("landscape_zoom", true)
fun landscapeZoom() = preferenceStore.getBoolean("landscape_zoom", false)
fun cropBordersWebtoon() = preferenceStore.getBoolean("crop_borders_webtoon", false)
@@ -106,5 +106,7 @@ class ReaderPreferences(private val preferenceStore: PreferenceStore) {
fun showNavigationOverlayOnStart() = preferenceStore.getBoolean("reader_navigation_overlay_on_start", false)
fun wideColorGamut() = preferenceStore.getBoolean("wide_color_gamut", false)
// endregion
}
@@ -7,8 +7,8 @@ import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
import com.hippo.ehviewer.R
import eu.kanade.tachiyomi.data.preference.PreferenceValues
import com.hippo.ehviewer.databinding.ReaderReadingModeSettingsBinding
import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
@@ -19,12 +19,16 @@ import kotlinx.coroutines.flow.launchIn
/**
* Sheet to show reader and viewer preferences.
*/
class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
class ReaderReadingModeSettings @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) :
NestedScrollView(context, attrs) {
private val readerPreferences: ReaderPreferences = ReaderActivity.readerPreferences
private val binding = ReaderReadingModeSettingsBinding.inflate(LayoutInflater.from(context), this, false)
private val binding =
ReaderReadingModeSettingsBinding.inflate(LayoutInflater.from(context), this, false)
init {
addView(binding.root)
@@ -53,14 +57,18 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
initPagerPreferences()
}
}
binding.viewer.setSelection(ReaderActivity.readerPreferences.defaultReadingMode().get().let { ReadingModeType.fromPreference(it).prefValue })
binding.viewer.setSelection(
ReaderActivity.readerPreferences.defaultReadingMode().get()
.let { ReadingModeType.fromPreference(it).prefValue })
binding.rotationMode.onItemSelectedListener = { position ->
val rotationType = OrientationType.fromSpinner(position)
readerPreferences.defaultOrientationType().set(rotationType.flagValue)
(context as ReaderActivity).setGallery()
}
binding.rotationMode.setSelection(ReaderActivity.readerPreferences.defaultOrientationType().get().let { OrientationType.fromPreference(it).prefValue })
binding.rotationMode.setSelection(
ReaderActivity.readerPreferences.defaultOrientationType().get()
.let { OrientationType.fromPreference(it).prefValue })
}
/**
@@ -70,7 +78,10 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
binding.webtoonPrefsGroup.root.isVisible = false
binding.pagerPrefsGroup.root.isVisible = true
binding.pagerPrefsGroup.tappingInverted.bindToPreference(readerPreferences.pagerNavInverted(), PreferenceValues.TappingInvertMode::class.java)
binding.pagerPrefsGroup.tappingInverted.bindToPreference(
readerPreferences.pagerNavInverted(),
PreferenceValues.TappingInvertMode::class.java
)
binding.pagerPrefsGroup.navigatePan.bindToPreference(readerPreferences.navigateToPan())
binding.pagerPrefsGroup.pagerNav.bindToPreference(readerPreferences.navigationModePager())
@@ -99,13 +110,28 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
binding.pagerPrefsGroup.root.isVisible = false
binding.webtoonPrefsGroup.root.isVisible = true
binding.webtoonPrefsGroup.tappingInverted.bindToPreference(readerPreferences.webtoonNavInverted(), PreferenceValues.TappingInvertMode::class.java)
binding.webtoonPrefsGroup.tappingInverted.bindToPreference(
readerPreferences.webtoonNavInverted(),
PreferenceValues.TappingInvertMode::class.java
)
binding.webtoonPrefsGroup.webtoonNav.bindToPreference(readerPreferences.navigationModeWebtoon())
readerPreferences.navigationModeWebtoon()
.asHotFlow { binding.webtoonPrefsGroup.tappingInverted.isVisible = it != 5 }
.launchIn((context as ReaderActivity).lifecycleScope)
binding.webtoonPrefsGroup.cropBordersWebtoon.bindToPreference(readerPreferences.cropBordersWebtoon())
binding.webtoonPrefsGroup.webtoonSidePadding.bindToIntPreference(readerPreferences.webtoonSidePadding(), R.array.webtoon_side_padding_values)
binding.webtoonPrefsGroup.webtoonSidePadding.bindToIntPreference(
readerPreferences.webtoonSidePadding(),
R.array.webtoon_side_padding_values
)
}
fun init() {
initGeneralPreferences()
when ((context as ReaderActivity).viewer) {
is PagerViewer -> initPagerPreferences()
is WebtoonViewer -> initWebtoonPreferences()
}
}
}
@@ -91,7 +91,7 @@ class ReaderSettingsSheet(
)
override fun show() {
initSettings()
readingModeSettings.init()
super.show()
}
}
@@ -28,7 +28,6 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.Companion.E
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.Companion.SCALE_TYPE_CENTER_INSIDE
import com.github.chrisbanes.photoview.PhotoView
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
import java.io.InputStream
@@ -220,7 +219,6 @@ open class ReaderPageImageView @JvmOverloads constructor(
} else {
SubsamplingScaleImageView(context)
}.apply {
setMaxTileSize(GLUtil.maxTextureSize)
setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER)
setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE)
setMinimumTileDpi(180)
@@ -278,7 +276,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
when (image) {
is Drawable -> {
val bitmap = (image as BitmapDrawable).bitmap
setImage(ImageSource.bitmap(bitmap))
setImage(ImageSource.cachedBitmap(bitmap))
}
is InputStream -> setImage(ImageSource.inputStream(image))
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
@@ -11,10 +11,9 @@ import androidx.annotation.IntRange
import com.google.android.material.progressindicator.CircularProgressIndicator
/**
* A wrapper for [CircularProgressIndicator] that always rotates while being determinate.
* A wrapper for [CircularProgressIndicator] that always rotates.
*
* By always rotating we give the feedback to the user that the application isn't 'stuck',
* and by making it determinate the user also approximately knows how much the operation will take.
* By always rotating we give the feedback to the user that the application isn't 'stuck'.
*/
class ReaderProgressIndicator @JvmOverloads constructor(
context: Context,
@@ -39,19 +39,18 @@ class PagerPageHolder(
/**
* Loading progress bar to indicate the current progress.
*/
private val progressIndicator: ReaderProgressIndicator =
ReaderProgressIndicator(readerThemedContext).apply {
updateLayoutParams<LayoutParams> {
gravity = Gravity.CENTER
}
private val progressIndicator: ReaderProgressIndicator = ReaderProgressIndicator(readerThemedContext).apply {
updateLayoutParams<LayoutParams> {
gravity = Gravity.CENTER
}
}
/**
* Error layout to show when the image fails to load.
*/
private var errorLayout: ReaderErrorBinding? = null
private var scope = CoroutineScope(Dispatchers.IO)
private val scope = CoroutineScope(Dispatchers.IO)
/**
* Subscription for status changes of the page.
@@ -59,17 +58,12 @@ class PagerPageHolder(
private var statusJob: Job? = null
/**
* Subscription for progress changes of the page.
* Job for progress changes of the page.
*/
private var progressJob: Job? = null
init {
addView(progressIndicator)
progressJob = scope.launch(Dispatchers.Main) {
page.progress.collect {
progressIndicator.setProgress(it.toInt())
}
}
statusJob = scope.launch(Dispatchers.Main) {
page.status.collectLatest {
processStatus(it)
@@ -83,10 +77,17 @@ class PagerPageHolder(
@SuppressLint("ClickableViewAccessibility")
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
unsubscribeProgress()
cancelProgressJob()
unsubscribeStatus()
}
private fun launchProgressJob() {
progressJob?.cancel()
progressJob = scope.launch(Dispatchers.Main) {
page.progressFlow.collectLatest { value -> progressIndicator.setProgress(value) }
}
}
/**
* Called when the status of the page changes.
*
@@ -97,17 +98,16 @@ class PagerPageHolder(
Page.State.QUEUE -> setQueued()
Page.State.LOAD_PAGE -> setLoading()
Page.State.DOWNLOAD_IMAGE -> {
launchProgressJob()
setDownloading()
}
Page.State.READY -> {
page.image?.mObtainedDrawable?.let { setImage(it) }
unsubscribeProgress()
cancelProgressJob()
}
Page.State.ERROR -> {
setError()
unsubscribeProgress()
cancelProgressJob()
}
}
}
@@ -117,13 +117,12 @@ class PagerPageHolder(
*/
private fun unsubscribeStatus() {
statusJob?.cancel()
statusJob = null
}
/**
* Unsubscribes from the progress subscription.
*/
private fun unsubscribeProgress() {
private fun cancelProgressJob() {
progressJob?.cancel()
progressJob = null
}
/**
@@ -203,7 +202,7 @@ class PagerPageHolder(
errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), this, true)
errorLayout?.actionRetry?.viewer = viewer
errorLayout?.actionRetry?.setOnClickListener {
viewer.activity.mGalleryProvider?.request(page.index)
viewer.activity.mGalleryProvider?.retryPage(page)
}
}
errorLayout?.root?.isVisible = true
@@ -54,7 +54,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
* Creates a new view for the item at the given [position].
*/
override fun createView(container: ViewGroup, position: Int): View {
currentChapter?.request(position)
currentChapter?.request(items[position] as ReaderPage)
return when (val item = items[position]) {
is ReaderPage -> PagerPageHolder(readerThemedContext, viewer, item)
else -> throw NotImplementedError("Holder for ${item.javaClass} not implemented")
@@ -74,9 +74,9 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter<RecyclerV
*/
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = items[position]
currentChapter!!.request(position)
currentChapter!!.request(item as ReaderPage)
when (holder) {
is WebtoonPageHolder -> holder.bind(item as ReaderPage)
is WebtoonPageHolder -> holder.bind(item)
}
}
@@ -63,14 +63,21 @@ class WebtoonPageHolder(
*/
private var page: ReaderPage? = null
private var progressJob: Job? = null
private val scope = CoroutineScope(Dispatchers.IO)
/**
* Subscription for status changes of the page.
*/
private var statusJob: Job? = null
private var scope = CoroutineScope(Dispatchers.IO)
/**
* Job for progress changes of the page.
*/
private var progressJob: Job? = null
init {
refreshLayoutParams()
frame.onImageLoaded = { onImageDecoded() }
frame.onImageLoadError = { onImageDecodeError() }
frame.onScaleChanged = { viewer.activity.hideMenu() }
@@ -81,11 +88,6 @@ class WebtoonPageHolder(
*/
fun bind(page: ReaderPage) {
this.page = page
progressJob = scope.launch(Dispatchers.Main) {
page.progress.collect {
progressIndicator.setProgress(it.toInt())
}
}
statusJob = scope.launch(Dispatchers.Main) {
page.status.collectLatest {
processStatus(it)
@@ -111,13 +113,26 @@ class WebtoonPageHolder(
*/
override fun recycle() {
statusJob?.cancel()
progressJob?.cancel()
cancelProgressJob()
removeErrorLayout()
frame.recycle()
page?.image?.recycle()
progressIndicator.setProgress(0, animated = false)
}
/**
* Observes the progress of the page and updates view.
*/
private fun launchProgressJob() {
cancelProgressJob()
val page = page ?: return
progressJob = scope.launch(Dispatchers.Main) {
page.progressFlow.collectLatest { value -> progressIndicator.setProgress(value) }
}
}
/**
* Called when the status of the page changes.
*
@@ -128,21 +143,28 @@ class WebtoonPageHolder(
Page.State.QUEUE -> setQueued()
Page.State.LOAD_PAGE -> setLoading()
Page.State.DOWNLOAD_IMAGE -> {
launchProgressJob()
setDownloading()
}
Page.State.READY -> {
page?.image?.mObtainedDrawable?.let { setImage(it) }
progressJob?.cancel()
cancelProgressJob()
}
Page.State.ERROR -> {
setError()
progressJob?.cancel()
cancelProgressJob()
}
}
}
/**
* Unsubscribes from the progress subscription.
*/
private fun cancelProgressJob() {
progressJob?.cancel()
progressJob = null
}
/**
* Called when the page is queued.
*/
@@ -232,7 +254,7 @@ class WebtoonPageHolder(
errorLayout = ReaderErrorBinding.inflate(LayoutInflater.from(context), frame, true)
errorLayout?.root?.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, (parentHeight * 0.8).toInt())
errorLayout?.actionRetry?.setOnClickListener {
viewer.activity.mGalleryProvider?.request(page!!.index)
viewer.activity.mGalleryProvider?.retryPage(page!!)
}
}
return errorLayout!!
@@ -1,47 +0,0 @@
package eu.kanade.tachiyomi.util.system
import javax.microedition.khronos.egl.EGL10
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.egl.EGLContext
import kotlin.math.max
object GLUtil {
val maxTextureSize: Int by lazy {
// Get EGL Display
val egl = EGLContext.getEGL() as EGL10
val display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
// Initialise
val version = IntArray(2)
egl.eglInitialize(display, version)
// Query total number of configurations
val totalConfigurations = IntArray(1)
egl.eglGetConfigs(display, null, 0, totalConfigurations)
// Query actual list configurations
val configurationsList = arrayOfNulls<EGLConfig>(totalConfigurations[0])
egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations)
val textureSize = IntArray(1)
var maximumTextureSize = 0
// Iterate through all the configurations to located the maximum texture size
for (i in 0 until totalConfigurations[0]) {
// Only need to check for width since opengl textures are always squared
egl.eglGetConfigAttrib(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize)
// Keep track of the maximum texture size
if (maximumTextureSize < textureSize[0]) maximumTextureSize = textureSize[0]
}
// Release
egl.eglTerminate(display)
// Return largest texture size found, or default
max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION)
}
}
// Safe minimum default size
private const val IMAGE_MAX_BITMAP_DIMENSION = 2048
@@ -73,8 +73,10 @@ class MaterialSpinnerView @JvmOverloads constructor(context: Context, attrs: Att
}
}
selectedPosition = selection
popup?.menu?.getItem(selectedPosition)?.let {
it.icon = checkmarkIcon
if (selectedPosition < (popup?.menu?.size ?: 0)) {
popup?.menu?.getItem(selectedPosition)?.let {
it.icon = checkmarkIcon
}
}
binding.details.text = entries.getOrNull(selection).orEmpty()
}
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="200"
android:fromYDelta="-100%"
android:toYDelta="0%" />
</set>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="200"
android:fromYDelta="0%"
android:toYDelta="-100%" />
</set>
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
+2 -2
View File
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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 GeneralPublic 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/>.
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="?colorControlHighlight"
tools:ignore="PrivateResource">
<item
android:id="@android:id/mask"
android:right="8dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners
android:bottomRightRadius="32dp"
android:topRightRadius="32dp" />
</shape>
</item>
</ripple>
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M7,18 L5.6,16.6 10.2,12 5.6,7.4 7,6 13,12ZM16,18V6H18V18Z" />
</vector>
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2015 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.
-->
<!--suppress AndroidUnresolvableTag -->
<com.google.android.material.internal.NavigationMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:foreground="@drawable/navigation_item_foreground"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight" />
+15 -5
View File
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -29,10 +29,20 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingStart="?attr/listPreferredItemPaddingLeft"
android:textAppearance="?attr/textAppearanceListItemSmall"
android:textColor="?android:attr/textColorPrimary" />
<ImageView
android:id="@+id/iv_edit"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingStart="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingRight"
android:visibility="gone"
app:srcCompat="@drawable/v_pencil_dark_x24" />
<ImageView
android:id="@+id/iv_option"
android:layout_width="wrap_content"
@@ -40,8 +50,8 @@
android:layout_gravity="center_vertical"
android:clickable="true"
android:focusable="true"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:paddingStart="?attr/listPreferredItemPaddingLeft"
android:paddingEnd="?attr/listPreferredItemPaddingRight"
app:srcCompat="@drawable/ic_baseline_reorder_24" />
</LinearLayout>
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -9,6 +9,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/wide_color_gamut"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/wide_color_gamut"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintTop_toTopOf="parent" />
<!-- Brightness -->
<com.google.android.material.materialswitch.MaterialSwitch
@@ -18,7 +28,7 @@
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/pref_custom_brightness"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/wide_color_gamut" />
<!-- Brightness value -->
@@ -41,9 +51,9 @@
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:stepSize="1.0"
android:valueFrom="-75.0"
android:valueTo="100.0"
android:stepSize="1.0"
app:layout_constraintEnd_toStartOf="@id/txt_brightness_seekbar_value"
app:layout_constraintStart_toEndOf="@id/txt_brightness_seekbar_icon"
app:layout_constraintTop_toBottomOf="@id/custom_brightness" />
@@ -91,9 +101,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:stepSize="1.0"
android:valueTo="255.0"
android:padding="8dp"
app:layout_constraintEnd_toStartOf="@id/txt_color_filter_red_value"
app:layout_constraintStart_toEndOf="@id/color_filter_symbols_barrier"
app:layout_constraintTop_toBottomOf="@id/switch_color_filter" />
@@ -129,9 +139,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:stepSize="1.0"
android:valueTo="255.0"
android:padding="8dp"
app:layout_constraintEnd_toStartOf="@id/txt_color_filter_green_value"
app:layout_constraintStart_toEndOf="@id/color_filter_symbols_barrier"
app:layout_constraintTop_toBottomOf="@id/slider_color_filter_red" />
@@ -167,9 +177,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:stepSize="1.0"
android:valueTo="255.0"
android:padding="8dp"
app:layout_constraintEnd_toStartOf="@id/txt_color_filter_blue_value"
app:layout_constraintStart_toEndOf="@id/color_filter_symbols_barrier"
app:layout_constraintTop_toBottomOf="@id/slider_color_filter_green" />
@@ -205,9 +215,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:stepSize="1.0"
android:valueTo="255.0"
android:padding="8dp"
app:layout_constraintEnd_toStartOf="@id/txt_color_filter_alpha_value"
app:layout_constraintStart_toEndOf="@id/color_filter_symbols_barrier"
app:layout_constraintTop_toBottomOf="@id/slider_color_filter_blue" />
@@ -35,4 +35,19 @@
app:drawableStartCompat="@drawable/ic_save_24dp"
app:drawableTint="?attr/colorOnBackground" />
<TextView
android:id="@+id/save_to"
android:layout_width="match_parent"
android:layout_height="56dp"
android:drawablePadding="32dp"
android:gravity="center_vertical"
android:clickable="true"
android:focusable="true"
android:paddingHorizontal="16dp"
android:foreground="?attr/selectableItemBackground"
android:text="@string/action_save_to"
android:textColor="?attr/colorOnBackground"
app:drawableStartCompat="@drawable/ic_save_24dp"
app:drawableTint="?attr/colorOnBackground" />
</LinearLayout>
@@ -10,17 +10,6 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/for_this_series_prefs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/pref_category_for_this_series"
android:textAppearance="@style/TextAppearance.Tachiyomi.SectionHeader" />
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
android:id="@+id/viewer"
android:layout_width="match_parent"
+7 -7
View File
@@ -32,16 +32,10 @@
android:id="@+id/fab_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingRight="@dimen/corner_fab_margin"
android:paddingEnd="@dimen/corner_fab_margin"
android:paddingBottom="@dimen/corner_fab_margin"
app:fitsSystemWindowsInsets="top|bottom">
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/v_heart_box_dark_x24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
android:layout_width="wrap_content"
@@ -54,6 +48,12 @@
android:layout_height="wrap_content"
app:srcCompat="@drawable/v_refresh_dark_x24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/v_last_page_x24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
android:layout_width="wrap_content"
@@ -20,7 +20,7 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
<androidx.swiperefreshlayout.widget.ThemedSwipeRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -30,11 +30,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="@dimen/fab_rv_padding_size"
android:paddingLeft="@dimen/keyline_margin"
android:paddingRight="@dimen/keyline_margin"
app:fitsSystemWindowsInsets="bottom" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.swiperefreshlayout.widget.ThemedSwipeRefreshLayout>
<TextView
android:id="@+id/tip"
@@ -40,16 +40,10 @@
android:id="@+id/fab_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingRight="@dimen/corner_fab_margin"
android:paddingEnd="@dimen/corner_fab_margin"
android:paddingBottom="@dimen/corner_fab_margin"
app:fitsSystemWindowsInsets="top|bottom">
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/v_magnify_x24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
android:layout_width="wrap_content"
@@ -62,6 +56,12 @@
android:layout_height="wrap_content"
app:srcCompat="@drawable/v_refresh_dark_x24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/v_last_page_x24" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@@ -39,7 +39,7 @@
android:layout_height="match_parent"
android:clipChildren="false">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
<androidx.swiperefreshlayout.widget.ThemedSwipeRefreshLayout
android:id="@+id/refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -51,7 +51,7 @@
android:layout_height="match_parent"
android:clipToPadding="false" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.swiperefreshlayout.widget.ThemedSwipeRefreshLayout>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/bottom_progress"
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
@@ -4,8 +4,8 @@
~ This file is part of EhViewer
~
~ EhViewer is free software: you can redistribute it and/or modify it
~ under the terms of the GNU GeneralPublic License as published
~ by the Free Software Foundation,either version 3 of the License, or
~ 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
-1
View File
@@ -45,7 +45,6 @@
<string name="error_unknown">Unbekannter Fehler</string>
<string name="error_cant_find_activity">Die Application wurde nicht gefunden</string>
<string name="error_cannot_parse_the_url">Die URL konnte analysiert nicht werden</string>
<string name="error_not_folder_path">Der Ordnerpfad ist ungültig</string>
<string name="error_decoding_failed">Dekodierung fehlgeschlagen</string>
<string name="error_reading_failed">Lesefehler</string>
<string name="error_out_of_range">Außer Reichweite</string>
Loaded 100 of 131 files, more files were not shown because too many files have changed in this diff. Show more