Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36b16d7be8 | ||
|
|
fab5532407 | ||
|
|
8ad4e8640f | ||
|
|
ca74bfe2e4 | ||
|
|
41767f0fd7 | ||
|
|
c62ca4dd23 | ||
|
|
9c5eddb903 | ||
|
|
4a0df07805 | ||
|
|
37a3edd44b | ||
|
|
f117abd483 | ||
|
|
4593714298 | ||
|
|
a148fd6ec3 | ||
|
|
23a44e2f3b | ||
|
|
01e03ec0f0 | ||
|
|
5b5a2a9a33 | ||
|
|
7719312355 | ||
|
|
4594ac592c | ||
|
|
eee0f2dded | ||
|
|
b4080302c3 | ||
|
|
337e210b5b | ||
|
|
48f1b0a958 | ||
|
|
9abeba6b15 | ||
|
|
a05b9174d4 | ||
|
|
aa8adfb6f7 | ||
|
|
1034fcd7b0 | ||
|
|
69a26aeedb | ||
|
|
dd607b41e9 | ||
|
|
fd45319715 | ||
|
|
c34ba56896 | ||
|
|
6e00a4133c | ||
|
|
75bec4314e | ||
|
|
b767c041b9 |
No files matched your search
@@ -64,7 +64,7 @@ android {
|
||||
minSdk = 28
|
||||
targetSdk = 34
|
||||
versionCode = 180043
|
||||
versionName = "1.8.9.0"
|
||||
versionName = "1.8.9.3-gms"
|
||||
resourceConfigurations.addAll(
|
||||
listOf(
|
||||
"zh",
|
||||
@@ -216,7 +216,7 @@ dependencies {
|
||||
val accompanist_version = "0.31.6-rc"
|
||||
implementation("com.google.accompanist:accompanist-themeadapter-material3:$accompanist_version")
|
||||
implementation("com.google.accompanist:accompanist-webview:$accompanist_version")
|
||||
implementation("com.google.android.material:material:1.11.0-alpha01")
|
||||
implementation("com.google.android.material:material:1.11.0-alpha02")
|
||||
|
||||
val splitties = "3.0.0"
|
||||
implementation("com.louiscad.splitties:splitties-appctx:$splitties")
|
||||
@@ -250,7 +250,8 @@ dependencies {
|
||||
implementation("io.ktor:ktor-io-jvm:$ktor")
|
||||
implementation("io.ktor:ktor-utils-jvm:$ktor")
|
||||
|
||||
implementation("org.chromium.net:cronet-embedded:113.5672.61")
|
||||
implementation("org.chromium.net:cronet-api:113.5672.61")
|
||||
implementation("com.google.android.gms:play-services-cronet:18.0.1")
|
||||
|
||||
val serialization = "1.5.1"
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:$serialization")
|
||||
|
||||
Vendored
+1
@@ -17,6 +17,7 @@
|
||||
-keep class com.hippo.ehviewer.client.parser.GalleryListResult { *; }
|
||||
-keep class com.hippo.ehviewer.client.parser.Limits { *; }
|
||||
-keep class com.hippo.ehviewer.client.data.BaseGalleryInfo { *; }
|
||||
-keep class com.hippo.ehviewer.ui.scene.SearchBarScene { getBinding(); }
|
||||
|
||||
# Ktor logger
|
||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.navigation.NavOptions
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
fun onNavDestinationSelected2(item: MenuItem, navController: NavController): Boolean {
|
||||
if (navController.currentDestination?.matchDestination(item.itemId) == true) return true
|
||||
val builder = NavOptions.Builder().setLaunchSingleTop(true)
|
||||
if (
|
||||
navController.currentDestination!!.parent!!.findNode(item.itemId)
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.lifecycle.coroutineScope
|
||||
import coil.ImageLoaderFactory
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.util.DebugLogger
|
||||
import com.google.android.gms.net.CronetProviderInstaller
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
@@ -77,6 +78,7 @@ class EhApplication : Application(), ImageLoaderFactory {
|
||||
handler?.uncaughtException(t, e)
|
||||
}
|
||||
super.onCreate()
|
||||
CronetProviderInstaller.installProvider(appCtx)
|
||||
System.loadLibrary("ehviewer")
|
||||
System.loadLibrary("ehviewer_rust")
|
||||
ReadableTime.initialize(this)
|
||||
|
||||
@@ -62,7 +62,6 @@ object Settings : DataStorePreferences(null) {
|
||||
var blackDarkTheme by boolPref("black_dark_theme", false).observed { updateWhenAmoledModeChanges() }
|
||||
var preloadThumbAggressively by boolPref("preload_thumb_aggressively", false)
|
||||
var downloadOriginImage by boolPref("download_origin_image", false)
|
||||
var enableQuic by boolPref("enable_quic", true)
|
||||
var thumbSizeDp by intPref("thumb_size_", 120)
|
||||
var recentFavCat by intPref("recent_fav_cat", FavListUrlBuilder.FAV_CAT_LOCAL)
|
||||
var defaultFavSlot by intPref("default_favorite_slot", -2)
|
||||
|
||||
@@ -70,8 +70,8 @@ data class ListUrlBuilder(
|
||||
mPrev = index?.takeUnless { isNext }
|
||||
}
|
||||
|
||||
fun setJumpTo(jumpTo: String?) {
|
||||
mJumpTo = jumpTo
|
||||
fun setJumpTo(jumpTo: Int) {
|
||||
mJumpTo = jumpTo.takeUnless { it == 0 }?.toString()
|
||||
}
|
||||
|
||||
var keyword: String?
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
|
||||
object GalleryListParser {
|
||||
suspend fun parse(body: String) = runCatching {
|
||||
@@ -15,13 +18,21 @@ object GalleryListParser {
|
||||
it.generateSLang()
|
||||
}
|
||||
}
|
||||
}.getOrDefault(GalleryListResult())
|
||||
}.getOrElse {
|
||||
if (body.contains("<p>You do not have any watched tags")) {
|
||||
throw EhException(R.string.gallery_list_empty_hit_subscription)
|
||||
}
|
||||
if (body.contains("No hits found</p>")) {
|
||||
throw EhException(R.string.gallery_list_empty_hit)
|
||||
}
|
||||
throw ParseException("Can't parse gallery list", body, it)
|
||||
}
|
||||
}
|
||||
|
||||
class GalleryListResult(
|
||||
val prev: String? = null,
|
||||
val next: String? = null,
|
||||
val galleryInfoList: ArrayList<BaseGalleryInfo> = arrayListOf(),
|
||||
val prev: String?,
|
||||
val next: String?,
|
||||
val galleryInfoList: ArrayList<BaseGalleryInfo>,
|
||||
)
|
||||
|
||||
private external fun parseGalleryInfoList(e: String): GalleryListResult
|
||||
@@ -33,9 +33,7 @@ val cronetHttpClient: CronetEngine = CronetEngine.Builder(appCtx).apply {
|
||||
val cache = (appCtx.cacheDir.toOkioPath() / "http_cache").toFile().apply { mkdirs() }
|
||||
setStoragePath(cache.absolutePath)
|
||||
enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 100 * 1024)
|
||||
addQuicHint("e-hentai.org", 443, 443)
|
||||
addQuicHint("forums.e-hentai.org", 443, 443)
|
||||
addQuicHint("exhentai.org", 443, 443)
|
||||
setUserAgent(CHROME_USER_AGENT)
|
||||
}.build()
|
||||
|
||||
val cronetHttpClientExecutor = EhApplication.baseOkHttpClient.dispatcher.executorService
|
||||
@@ -75,7 +73,6 @@ class CronetRequest {
|
||||
inline fun cronetRequest(url: String, referer: String? = null, conf: UrlRequest.Builder.() -> Unit = {}) = CronetRequest().apply {
|
||||
request = cronetHttpClient.newUrlRequestBuilder(url, callback, cronetHttpClientExecutor).apply {
|
||||
addHeader("Cookie", EhCookieStore.getCookieHeader(url.toHttpUrl()))
|
||||
addHeader("User-Agent", CHROME_USER_AGENT)
|
||||
addHeader("Accept", CHROME_ACCEPT)
|
||||
addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE)
|
||||
referer?.let { addHeader("Referer", it) }
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.time.Instant
|
||||
),
|
||||
],
|
||||
)
|
||||
class DownloadEntity(
|
||||
data class DownloadEntity(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "GID")
|
||||
var gid: Long,
|
||||
|
||||
@@ -71,7 +71,7 @@ object DownloadManager : OnSpiderListener {
|
||||
// Store download info wait to start
|
||||
private val mWaitList = LinkedList<DownloadInfo>()
|
||||
private val mSpeedReminder = SpeedReminder()
|
||||
private val mDownloadInfoListeners = mutableListOf<DownloadInfoListener>()
|
||||
private var mDownloadInfoListener: DownloadInfoListener? = null
|
||||
private val mNotifyTaskPool = ConcurrentPool<NotifyTask?>(5)
|
||||
private var mDownloadListener: DownloadListener? = null
|
||||
private var mCurrentTask: DownloadInfo? = null
|
||||
@@ -125,14 +125,6 @@ object DownloadManager : OnSpiderListener {
|
||||
return info?.state ?: DownloadInfo.STATE_INVALID
|
||||
}
|
||||
|
||||
fun addDownloadInfoListener(downloadInfoListener: DownloadInfoListener) {
|
||||
mDownloadInfoListeners.add(downloadInfoListener)
|
||||
}
|
||||
|
||||
fun removeDownloadInfoListener(downloadInfoListener: DownloadInfoListener) {
|
||||
mDownloadInfoListeners.remove(downloadInfoListener)
|
||||
}
|
||||
|
||||
fun setDownloadListener(listener: DownloadListener?) {
|
||||
mDownloadListener = listener
|
||||
}
|
||||
@@ -166,12 +158,7 @@ object DownloadManager : OnSpiderListener {
|
||||
mDownloadListener!!.onStart(info)
|
||||
}
|
||||
// Notify state update
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,12 +179,7 @@ object DownloadManager : OnSpiderListener {
|
||||
// Update in DB
|
||||
EhDB.putDownloadInfo(info)
|
||||
// Notify state update
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
// Make sure download is running
|
||||
ensureDownload()
|
||||
}
|
||||
@@ -227,9 +209,7 @@ object DownloadManager : OnSpiderListener {
|
||||
EhDB.putDownloadInfo(info)
|
||||
|
||||
// Notify
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onAdd(info, list, list.size - 1)
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
// Make sure download is running
|
||||
ensureDownload()
|
||||
|
||||
@@ -259,9 +239,7 @@ object DownloadManager : OnSpiderListener {
|
||||
}
|
||||
if (update) {
|
||||
// Notify Listener
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdateAll()
|
||||
}
|
||||
mDownloadInfoListener?.onUpdateAll()
|
||||
// Ensure download
|
||||
ensureDownload()
|
||||
}
|
||||
@@ -271,33 +249,23 @@ object DownloadManager : OnSpiderListener {
|
||||
|
||||
private val _stateFlow = callbackFlow {
|
||||
val listener = object : DownloadInfoListener {
|
||||
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
trySend(info)
|
||||
}
|
||||
|
||||
override fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>) {
|
||||
trySend(info)
|
||||
override fun onUpdate(info: DownloadInfo) {
|
||||
trySend(info.gid)
|
||||
}
|
||||
|
||||
override fun onUpdateAll() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
override fun onReload() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
override fun onRemove(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
trySend(info)
|
||||
trySend(0)
|
||||
}
|
||||
}
|
||||
addDownloadInfoListener(listener)
|
||||
mDownloadInfoListener = listener
|
||||
awaitClose {
|
||||
removeDownloadInfoListener(listener)
|
||||
mDownloadInfoListener = null
|
||||
}
|
||||
}.shareIn(callbackFlowScope, SharingStarted.Eagerly)
|
||||
|
||||
fun stateFlow(gid: Long) = _stateFlow.filter { it.gid == gid }
|
||||
fun stateFlow(gid: Long) = _stateFlow.filter { it == gid }
|
||||
|
||||
fun stateFlow() = _stateFlow
|
||||
|
||||
suspend fun startAllDownload() {
|
||||
var update = false
|
||||
@@ -315,9 +283,7 @@ object DownloadManager : OnSpiderListener {
|
||||
}
|
||||
if (update) {
|
||||
// Notify Listener
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdateAll()
|
||||
}
|
||||
mDownloadInfoListener?.onUpdateAll()
|
||||
// Ensure download
|
||||
ensureDownload()
|
||||
}
|
||||
@@ -353,9 +319,7 @@ object DownloadManager : OnSpiderListener {
|
||||
|
||||
// Notify
|
||||
if (notify) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onReload()
|
||||
}
|
||||
mDownloadInfoListener?.onUpdateAll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,9 +350,7 @@ object DownloadManager : OnSpiderListener {
|
||||
|
||||
// Notify
|
||||
withUIContext {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onAdd(info, list, list.size - 1)
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,12 +358,7 @@ object DownloadManager : OnSpiderListener {
|
||||
val info = stopDownloadInternal(gid)
|
||||
if (info != null) {
|
||||
// Update listener
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
// Ensure download
|
||||
ensureDownload()
|
||||
}
|
||||
@@ -411,12 +368,7 @@ object DownloadManager : OnSpiderListener {
|
||||
val info = stopCurrentDownloadInternal()
|
||||
if (info != null) {
|
||||
// Update listener
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
// Ensure download
|
||||
ensureDownload()
|
||||
}
|
||||
@@ -426,9 +378,7 @@ object DownloadManager : OnSpiderListener {
|
||||
stopRangeDownloadInternal(gidList)
|
||||
|
||||
// Update listener
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdateAll()
|
||||
}
|
||||
mDownloadInfoListener?.onUpdateAll()
|
||||
|
||||
// Ensure download
|
||||
ensureDownload()
|
||||
@@ -447,9 +397,7 @@ object DownloadManager : OnSpiderListener {
|
||||
stopCurrentDownloadInternal()
|
||||
|
||||
// Notify mDownloadInfoListener
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdateAll()
|
||||
}
|
||||
mDownloadInfoListener?.onUpdateAll()
|
||||
}
|
||||
|
||||
suspend fun deleteDownload(gid: Long, deleteFiles: Boolean = false) {
|
||||
@@ -470,9 +418,7 @@ object DownloadManager : OnSpiderListener {
|
||||
if (index >= 0) {
|
||||
list.remove(info)
|
||||
// Update listener
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onRemove(info, list, index)
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -495,15 +441,13 @@ object DownloadManager : OnSpiderListener {
|
||||
allInfoList.removeAll(list.toSet())
|
||||
|
||||
// Update listener
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onReload()
|
||||
}
|
||||
mDownloadInfoListener?.onUpdateAll()
|
||||
|
||||
// Ensure download
|
||||
ensureDownload()
|
||||
}
|
||||
|
||||
fun moveDownload(fromPosition: Int, toPosition: Int): List<DownloadInfo> {
|
||||
private fun moveDownload(fromPosition: Int, toPosition: Int): List<DownloadInfo> {
|
||||
val info = allInfoList.removeAt(fromPosition)
|
||||
allInfoList.add(toPosition, info)
|
||||
val range = if (fromPosition < toPosition) fromPosition..toPosition else toPosition..fromPosition
|
||||
@@ -515,12 +459,11 @@ object DownloadManager : OnSpiderListener {
|
||||
return list
|
||||
}
|
||||
|
||||
fun moveDownload(label: String?, fromPosition: Int, toPosition: Int) =
|
||||
getInfoListForLabel(label)!!.let {
|
||||
val absFromPosition = allInfoList.indexOf(it[fromPosition])
|
||||
val absToPosition = allInfoList.indexOf(it[toPosition])
|
||||
moveDownload(absFromPosition, absToPosition)
|
||||
}
|
||||
fun moveDownload(fromItem: DownloadInfo, toItem: DownloadInfo): List<DownloadInfo> {
|
||||
val fromPosition = allInfoList.indexOf(fromItem)
|
||||
val toPosition = allInfoList.indexOf(toItem)
|
||||
return moveDownload(fromPosition, toPosition)
|
||||
}
|
||||
|
||||
suspend fun resetAllReadingProgress() = coroutineScope {
|
||||
allInfoList.forEach { info ->
|
||||
@@ -658,9 +601,7 @@ object DownloadManager : OnSpiderListener {
|
||||
// Save to DB
|
||||
EhDB.putDownloadInfo(info)
|
||||
}
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onReload()
|
||||
}
|
||||
mDownloadInfoListener?.onUpdateAll()
|
||||
}
|
||||
|
||||
suspend fun addLabel(label: String?) {
|
||||
@@ -810,30 +751,15 @@ object DownloadManager : OnSpiderListener {
|
||||
}
|
||||
|
||||
interface DownloadInfoListener {
|
||||
/**
|
||||
* Add the special info to the special position
|
||||
*/
|
||||
fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int)
|
||||
|
||||
/**
|
||||
* The special info is changed
|
||||
*/
|
||||
fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>)
|
||||
fun onUpdate(info: DownloadInfo)
|
||||
|
||||
/**
|
||||
* Maybe all data is changed, but size is the same
|
||||
*/
|
||||
fun onUpdateAll()
|
||||
|
||||
/**
|
||||
* Maybe all data is changed, maybe list is changed
|
||||
*/
|
||||
fun onReload()
|
||||
|
||||
/**
|
||||
* Remove the special info from the special position
|
||||
*/
|
||||
fun onRemove(info: DownloadInfo, list: List<DownloadInfo>, position: Int)
|
||||
}
|
||||
|
||||
interface DownloadListener {
|
||||
@@ -940,12 +866,7 @@ object DownloadManager : OnSpiderListener {
|
||||
Log.e(TAG, "Current task is null, but it should not be")
|
||||
} else {
|
||||
info.total = mPages
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -974,12 +895,7 @@ object DownloadManager : OnSpiderListener {
|
||||
if (mDownloadListener != null) {
|
||||
mDownloadListener!!.onGetPage(info)
|
||||
}
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,12 +908,7 @@ object DownloadManager : OnSpiderListener {
|
||||
info.finished = mFinished
|
||||
info.downloaded = mDownloaded
|
||||
info.total = mTotal
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1036,12 +947,7 @@ object DownloadManager : OnSpiderListener {
|
||||
if (mDownloadListener != null) {
|
||||
mDownloadListener!!.onFinish(info)
|
||||
}
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
// Start next download
|
||||
ensureDownload()
|
||||
}
|
||||
@@ -1127,12 +1033,7 @@ object DownloadManager : OnSpiderListener {
|
||||
if (mDownloadListener != null) {
|
||||
mDownloadListener!!.onDownload(info)
|
||||
}
|
||||
val list: List<DownloadInfo>? = getInfoListForLabel(info.label)
|
||||
if (list != null) {
|
||||
for (l in mDownloadInfoListeners) {
|
||||
l.onUpdate(info, list)
|
||||
}
|
||||
}
|
||||
mDownloadInfoListener?.onUpdate(info)
|
||||
}
|
||||
mBytesRead = 0
|
||||
if (!mStop) {
|
||||
|
||||
@@ -20,6 +20,8 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
@@ -28,7 +30,12 @@ import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.material.icons.filled.FavoriteBorder
|
||||
import androidx.compose.material.icons.filled.HeartBroken
|
||||
import androidx.compose.material.icons.filled.MenuBook
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.hippo.ehviewer.EhDB
|
||||
@@ -55,6 +62,7 @@ import com.hippo.ehviewer.util.requestPermission
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.util.lang.launchNow
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -312,7 +320,6 @@ suspend fun DialogState.doGalleryInfoAction(info: BaseGalleryInfo, context: Cont
|
||||
1 -> withUIContext {
|
||||
if (downloaded) {
|
||||
confirmRemoveDownload(info)
|
||||
DownloadManager.deleteDownload(info.gid)
|
||||
} else {
|
||||
CommonOperations.startDownload(this@with, info, false)
|
||||
}
|
||||
@@ -342,12 +349,28 @@ suspend fun DialogState.doGalleryInfoAction(info: BaseGalleryInfo, context: Cont
|
||||
|
||||
private const val MAX_FAVNOTE_CHAR = 200
|
||||
|
||||
suspend fun DialogState.confirmRemoveDownload(info: GalleryInfo) = awaitPermissionOrCancel(
|
||||
confirmText = android.R.string.ok,
|
||||
dismissText = android.R.string.cancel,
|
||||
title = R.string.download_remove_dialog_title,
|
||||
text = { Text(text = stringResource(id = R.string.download_remove_dialog_message, info.title.orEmpty())) },
|
||||
)
|
||||
suspend fun DialogState.confirmRemoveDownload(info: GalleryInfo, onDismiss: () -> Unit = {}) {
|
||||
var checked by mutableStateOf(Settings.removeImageFiles)
|
||||
awaitPermissionOrCancel(
|
||||
confirmText = android.R.string.ok,
|
||||
dismissText = android.R.string.cancel,
|
||||
title = R.string.download_remove_dialog_title,
|
||||
onDismiss = onDismiss,
|
||||
text = {
|
||||
Column {
|
||||
Text(text = stringResource(id = R.string.download_remove_dialog_message, info.title.orEmpty()))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = checked, onCheckedChange = { checked = it })
|
||||
Text(text = stringResource(id = R.string.download_remove_dialog_check_text))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
Settings.removeImageFiles = checked
|
||||
withIOContext {
|
||||
DownloadManager.deleteDownload(info.gid, checked)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun DialogState.showMoveDownloadLabel(info: GalleryInfo) {
|
||||
val defaultLabel = appCtx.getString(R.string.default_download_label_name)
|
||||
|
||||
@@ -197,7 +197,7 @@ class MainActivity : EhActivity() {
|
||||
// Trick: Tweak NavigationUI to disable multiple backstack
|
||||
binding.navView.setNavigationItemSelectedListener {
|
||||
val navigationView = binding.navView
|
||||
val handled = onNavDestinationSelected2(it, navController)
|
||||
val handled = navigationView.checkedItem?.itemId == it.itemId || onNavDestinationSelected2(it, navController)
|
||||
if (handled) {
|
||||
val parent = navigationView.parent
|
||||
if (parent is Openable) {
|
||||
@@ -398,6 +398,10 @@ class MainActivity : EhActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun clearNavCheckedItem() {
|
||||
binding.navView.setCheckedItem(R.id.nav_stub)
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, length: Int, useToast: Boolean = false) {
|
||||
showTip(getString(id), length, useToast)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.SoftwareKeyboardControllerCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.setViewTreeViewModelStoreOwner
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import com.google.android.material.sidesheet.SideSheetDialog
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
@@ -155,8 +158,10 @@ abstract class BaseScene : Fragment() {
|
||||
createDrawerView(savedInstanceState)?.let {
|
||||
sideSheetDialog = SideSheetDialog(requireContext()).apply {
|
||||
window?.decorView?.apply {
|
||||
setViewTreeLifecycleOwner(viewLifecycleOwner)
|
||||
setViewTreeSavedStateRegistryOwner(this@BaseScene)
|
||||
val owner = viewLifecycleOwner
|
||||
setViewTreeLifecycleOwner(owner)
|
||||
setViewTreeViewModelStoreOwner(owner as ViewModelStoreOwner)
|
||||
setViewTreeSavedStateRegistryOwner(owner as SavedStateRegistryOwner)
|
||||
}
|
||||
setContentView(it)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -54,11 +55,12 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import arrow.core.partially1
|
||||
import coil.compose.AsyncImage
|
||||
@@ -79,11 +81,11 @@ import com.hippo.ehviewer.databinding.ItemDownloadBinding
|
||||
import com.hippo.ehviewer.databinding.ItemDrawerListBinding
|
||||
import com.hippo.ehviewer.databinding.SceneDownloadBinding
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
|
||||
import com.hippo.ehviewer.download.DownloadService
|
||||
import com.hippo.ehviewer.download.DownloadService.Companion.clear
|
||||
import com.hippo.ehviewer.download.downloadDir
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
import com.hippo.ehviewer.ui.confirmRemoveDownload
|
||||
import com.hippo.ehviewer.ui.legacy.AutoStaggeredGridLayoutManager
|
||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.ui.legacy.CheckBoxDialogBuilder
|
||||
@@ -98,8 +100,10 @@ import com.hippo.ehviewer.ui.main.requestOf
|
||||
import com.hippo.ehviewer.ui.navToReader
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
import com.hippo.ehviewer.ui.tools.CropDefaults
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.LongList
|
||||
import com.hippo.ehviewer.util.containsIgnoreCase
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
@@ -108,20 +112,21 @@ import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.pxToDp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import rikka.core.res.resolveColor
|
||||
import com.hippo.ehviewer.download.DownloadManager as downloadManager
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
class DownloadsScene :
|
||||
BaseToolbarScene(),
|
||||
DownloadInfoListener,
|
||||
SearchBarScene(),
|
||||
OnClickFabListener {
|
||||
/*---------------
|
||||
Whole life cycle
|
||||
---------------*/
|
||||
private var mKeyword: String? = null
|
||||
private var mLabel: String? = null
|
||||
private var mList: List<DownloadInfo>? = null
|
||||
private val mList get() = mAdapter?.currentList
|
||||
|
||||
/*---------------
|
||||
View life cycle
|
||||
@@ -132,18 +137,23 @@ class DownloadsScene :
|
||||
private var mAdapter: DownloadAdapter? = null
|
||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||
private val tracker get() = mAdapter!!.tracker!!
|
||||
private var mInitPosition = -1
|
||||
private var mGid = -1L
|
||||
private var mLabelAdapter: DownloadLabelAdapter? = null
|
||||
private lateinit var mLabels: MutableList<String>
|
||||
private var mType = -1
|
||||
|
||||
private val dialogState = DialogState()
|
||||
|
||||
override val fabLayout get() = binding.fabLayout
|
||||
|
||||
private fun initLabels() {
|
||||
context ?: return
|
||||
val listLabel = downloadManager.labelList
|
||||
mLabels = ArrayList(listLabel.size + LABEL_OFFSET)
|
||||
// Add "All" and "Default" label names
|
||||
mLabels.add(getString(R.string.download_all))
|
||||
mLabels.add(getString(R.string.default_download_label_name))
|
||||
mLabels = arrayListOf(
|
||||
getString(R.string.download_all),
|
||||
getString(R.string.default_download_label_name),
|
||||
)
|
||||
listLabel.forEach {
|
||||
mLabels.add(it.label)
|
||||
}
|
||||
@@ -161,18 +171,7 @@ class DownloadsScene :
|
||||
val info = DownloadManager.getDownloadInfo(gid)
|
||||
if (null != info) {
|
||||
mLabel = info.label
|
||||
updateForLabel()
|
||||
updateView()
|
||||
|
||||
// Get position
|
||||
if (null != mList) {
|
||||
val position = mList!!.indexOf(info)
|
||||
if (position >= 0 && null != _binding) {
|
||||
binding.recyclerView.scrollToPosition(position)
|
||||
} else {
|
||||
mInitPosition = position
|
||||
}
|
||||
}
|
||||
mGid = gid
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -181,7 +180,6 @@ class DownloadsScene :
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
DownloadManager.addDownloadInfoListener(this)
|
||||
if (savedInstanceState == null) {
|
||||
onInit()
|
||||
} else {
|
||||
@@ -189,50 +187,54 @@ class DownloadsScene :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mList = null
|
||||
DownloadManager.removeDownloadInfoListener(this)
|
||||
private fun filter(info: DownloadInfo) = info.run {
|
||||
(mType == -1 || state == mType) &&
|
||||
mKeyword?.let {
|
||||
title.containsIgnoreCase(it) || titleJpn.containsIgnoreCase(it) ||
|
||||
uploader.containsIgnoreCase(it)
|
||||
} ?: true
|
||||
}
|
||||
|
||||
private fun updateInfoList() {
|
||||
mAdapter?.run {
|
||||
val list = when (mLabel) {
|
||||
null -> DownloadManager.allInfoList
|
||||
getString(R.string.default_download_label_name) -> DownloadManager.defaultInfoList
|
||||
else -> DownloadManager.getLabelDownloadInfoList(mLabel)
|
||||
?: DownloadManager.allInfoList.also { mLabel = null }
|
||||
}.filter(::filter)
|
||||
submitList(list) {
|
||||
if (mGid != -1L) {
|
||||
val position = list.indexOfFirst { it.gid == mGid }
|
||||
if (position != -1) {
|
||||
binding.recyclerView.scrollToPosition(position)
|
||||
}
|
||||
mGid = -1L
|
||||
}
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun updateForLabel() {
|
||||
val list = when (mLabel) {
|
||||
null -> DownloadManager.allInfoList
|
||||
getString(R.string.default_download_label_name) -> DownloadManager.defaultInfoList
|
||||
else -> DownloadManager.getLabelDownloadInfoList(mLabel)
|
||||
?: DownloadManager.allInfoList.also { mLabel = null }
|
||||
}
|
||||
mList = if (mType != -1) {
|
||||
list.filter { it.state == mType }
|
||||
} else {
|
||||
list
|
||||
}
|
||||
if (mAdapter != null) {
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
}
|
||||
updateInfoList()
|
||||
Settings.recentDownloadLabel = mLabel
|
||||
}
|
||||
|
||||
private fun updateTitle() {
|
||||
setTitle(
|
||||
getString(
|
||||
R.string.scene_download_title,
|
||||
if (mLabel != null) mLabel else getString(R.string.download_all),
|
||||
),
|
||||
)
|
||||
val title = getString(R.string.scene_download_title, mLabel ?: getString(R.string.download_all))
|
||||
setSearchBarHint(title)
|
||||
setEditTextHint(getString(R.string.search_bar_hint, title))
|
||||
}
|
||||
|
||||
private fun onInit() {
|
||||
if (!handleArguments(arguments)) {
|
||||
mLabel = Settings.recentDownloadLabel
|
||||
updateForLabel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRestore(savedInstanceState: Bundle) {
|
||||
mLabel = savedInstanceState.getString(KEY_LABEL)
|
||||
updateForLabel()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -247,6 +249,7 @@ class DownloadsScene :
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
_binding = SceneDownloadBinding.inflate(inflater, container!!)
|
||||
container.addView(ComposeView(inflater.context).apply { setMD3Content { dialogState.Intercept() } })
|
||||
binding.run {
|
||||
setLiftOnScrollTargetView(recyclerView)
|
||||
mViewTransition = ViewTransition(content, tip)
|
||||
@@ -288,11 +291,6 @@ class DownloadsScene :
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.clipChildren = false
|
||||
// Cancel change animation
|
||||
val itemAnimator = recyclerView.itemAnimator
|
||||
if (itemAnimator is SimpleItemAnimator) {
|
||||
itemAnimator.supportsChangeAnimations = false
|
||||
}
|
||||
val interval = resources.getDimensionPixelOffset(R.dimen.gallery_list_interval)
|
||||
val decoration = object : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
@@ -300,10 +298,6 @@ class DownloadsScene :
|
||||
}
|
||||
}
|
||||
recyclerView.addItemDecoration(decoration)
|
||||
if (mInitPosition >= 0) {
|
||||
recyclerView.scrollToPosition(mInitPosition)
|
||||
mInitPosition = -1
|
||||
}
|
||||
mItemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
|
||||
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
|
||||
super.onSelectedChanged(viewHolder, actionState)
|
||||
@@ -313,7 +307,7 @@ class DownloadsScene :
|
||||
}
|
||||
override fun isLongPressDragEnabled() = false
|
||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||
return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
|
||||
return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT)
|
||||
}
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
@@ -325,18 +319,24 @@ class DownloadsScene :
|
||||
if (fromPosition == toPosition) {
|
||||
return false
|
||||
}
|
||||
val list = when (mLabel) {
|
||||
null -> DownloadManager.moveDownload(fromPosition, toPosition)
|
||||
getString(R.string.default_download_label_name) -> DownloadManager.moveDownload(null, fromPosition, toPosition)
|
||||
else -> DownloadManager.moveDownload(mLabel, fromPosition, toPosition)
|
||||
}
|
||||
mAdapter!!.notifyItemMoved(fromPosition, toPosition)
|
||||
val fromItem = mList!![fromPosition]
|
||||
val toItem = mList!![toPosition]
|
||||
val list = DownloadManager.moveDownload(fromItem, toItem)
|
||||
updateInfoList()
|
||||
lifecycleScope.launchIO {
|
||||
EhDB.updateDownloadInfo(list)
|
||||
}
|
||||
return true
|
||||
}
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {}
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val info = mList!![position]
|
||||
lifecycleScope.launchUI {
|
||||
dialogState.confirmRemoveDownload(info) {
|
||||
mAdapter!!.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
mItemTouchHelper!!.attachToRecyclerView(recyclerView)
|
||||
fastScroller.attachToRecyclerView(recyclerView)
|
||||
@@ -349,15 +349,29 @@ class DownloadsScene :
|
||||
fabLayout.setAutoCancel(false)
|
||||
fabLayout.setOnClickFabListener(this@DownloadsScene)
|
||||
addAboveSnackView(fabLayout)
|
||||
updateView()
|
||||
updateForLabel()
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
DownloadManager.stateFlow().collectLatest {
|
||||
updateInfoList()
|
||||
}
|
||||
}
|
||||
setOnApplySearch {
|
||||
mKeyword = it.takeUnless { it.isEmpty() }
|
||||
updateInfoList()
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
updateTitle()
|
||||
setNavigationIcon(R.drawable.ic_baseline_menu_24)
|
||||
override fun onSearchViewExpanded() {
|
||||
super.onSearchViewExpanded()
|
||||
if (tracker.isInCustomChoice) tracker.clearSelection()
|
||||
showSearchFab(true)
|
||||
}
|
||||
|
||||
override fun onSearchViewHidden() {
|
||||
super.onSearchViewHidden()
|
||||
hideSearchFab(true)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -393,8 +407,7 @@ class DownloadsScene :
|
||||
mType + 1,
|
||||
) { dialog: DialogInterface, which: Int ->
|
||||
mType = which - 1
|
||||
updateForLabel()
|
||||
updateView()
|
||||
updateInfoList()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
@@ -447,7 +460,7 @@ class DownloadsScene :
|
||||
}
|
||||
}
|
||||
|
||||
fun updateView() {
|
||||
private fun updateView() {
|
||||
if (mViewTransition != null) {
|
||||
if (mList.isNullOrEmpty()) {
|
||||
mViewTransition!!.showView(1)
|
||||
@@ -595,61 +608,6 @@ class DownloadsScene :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAdd(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
if (mList !== list) {
|
||||
return
|
||||
}
|
||||
lifecycleScope.launchUI {
|
||||
if (mAdapter != null) {
|
||||
mAdapter!!.notifyItemInserted(position)
|
||||
}
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>) {
|
||||
if (mLabel != null && mList !== list) {
|
||||
return
|
||||
}
|
||||
val index = mList!!.indexOf(info)
|
||||
lifecycleScope.launchUI {
|
||||
if (index >= 0 && mAdapter != null) {
|
||||
mAdapter!!.notifyItemChanged(index, PAYLOAD_STATE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onUpdateAll() {
|
||||
lifecycleScope.launchUI {
|
||||
if (mAdapter != null) {
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onReload() {
|
||||
lifecycleScope.launchUI {
|
||||
if (mAdapter != null) {
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
}
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRemove(info: DownloadInfo, list: List<DownloadInfo>, position: Int) {
|
||||
if (mList !== list) {
|
||||
return
|
||||
}
|
||||
lifecycleScope.launchUI {
|
||||
if (mAdapter != null) {
|
||||
mAdapter!!.notifyItemRemoved(position)
|
||||
}
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadLabelHolder(val binding: ItemDrawerListBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
||||
@@ -660,17 +618,14 @@ class DownloadsScene :
|
||||
DownloadLabelHolder(ItemDrawerListBinding.inflate(mInflater, parent, false))
|
||||
holder.itemView.setOnClickListener {
|
||||
val position = holder.bindingAdapterPosition
|
||||
val label1: String? = if (position == 0) {
|
||||
null
|
||||
} else {
|
||||
mLabels[position]
|
||||
}
|
||||
if (mLabel != label1) {
|
||||
mLabel = label1
|
||||
val label = mLabels[position].takeUnless { position == 0 }
|
||||
if (mLabel != label) {
|
||||
mKeyword = null
|
||||
setSearchBarText(null)
|
||||
mLabel = label
|
||||
updateForLabel()
|
||||
updateView()
|
||||
closeSideSheet()
|
||||
}
|
||||
closeSideSheet()
|
||||
}
|
||||
holder.binding.edit.setOnClickListener {
|
||||
val context = context
|
||||
@@ -708,7 +663,7 @@ class DownloadsScene :
|
||||
}
|
||||
holder.binding.run {
|
||||
if (list != null) {
|
||||
text.text = label + " [" + list.size + "]"
|
||||
text.text = "$label [${list.size}]"
|
||||
} else {
|
||||
text.text = label
|
||||
}
|
||||
@@ -952,17 +907,23 @@ class DownloadsScene :
|
||||
}
|
||||
}
|
||||
|
||||
private inner class DownloadAdapter : RecyclerView.Adapter<DownloadHolder>() {
|
||||
private val diffCallback = object : DiffUtil.ItemCallback<DownloadInfo>() {
|
||||
override fun areItemsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem.gid == newItem.gid
|
||||
override fun areContentsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem == newItem
|
||||
override fun getChangePayload(oldItem: DownloadInfo, newItem: DownloadInfo): Any? {
|
||||
return if (oldItem.downloadInfo != newItem.downloadInfo) {
|
||||
PAYLOAD_DOWNLOAD_INFO
|
||||
} else {
|
||||
super.getChangePayload(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class DownloadAdapter : ListAdapter<DownloadInfo, DownloadHolder>(diffCallback) {
|
||||
private val mInflater: LayoutInflater = layoutInflater
|
||||
var tracker: GallerySelectionTracker<DownloadInfo>? = null
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return if (mList == null || position < 0 || position >= mList!!.size) {
|
||||
0
|
||||
} else {
|
||||
mList!![position].gid
|
||||
}
|
||||
}
|
||||
override fun getItemId(position: Int) = getItem(position).gid
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadHolder {
|
||||
val holder = DownloadHolder(ItemDownloadBinding.inflate(mInflater, parent, false))
|
||||
@@ -971,10 +932,8 @@ class DownloadsScene :
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: DownloadHolder, position: Int) {
|
||||
mList?.let {
|
||||
val info = it[position]
|
||||
holder.bind(info, tracker?.isSelected(info.gid) ?: false)
|
||||
}
|
||||
val info = getItem(position)
|
||||
holder.bind(info, tracker?.isSelected(info.gid) ?: false)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(
|
||||
@@ -984,18 +943,15 @@ class DownloadsScene :
|
||||
) {
|
||||
payloads.forEach { payload ->
|
||||
when (payload) {
|
||||
PAYLOAD_STATE -> {
|
||||
mList?.let { holder.bindForState(it[position]) }
|
||||
PAYLOAD_DOWNLOAD_INFO -> {
|
||||
val info = getItem(position)
|
||||
holder.bindForState(info)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onBindViewHolder(holder, position, payloads)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return if (mList == null) 0 else mList!!.size
|
||||
}
|
||||
}
|
||||
|
||||
private inner class RenameLabelDialogHelper(
|
||||
@@ -1030,7 +986,6 @@ class DownloadsScene :
|
||||
if (mLabel == mOriginalLabel) {
|
||||
mLabel = text
|
||||
updateForLabel()
|
||||
updateView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1119,7 +1074,6 @@ class DownloadsScene :
|
||||
withUIContext {
|
||||
mLabel = null
|
||||
updateForLabel()
|
||||
updateView()
|
||||
mLabelAdapter!!.notifyItemRemoved(position)
|
||||
}
|
||||
}
|
||||
@@ -1137,7 +1091,7 @@ class DownloadsScene :
|
||||
const val ACTION_CLEAR_DOWNLOAD_SERVICE = "clear_download_service"
|
||||
private const val KEY_LABEL = "label"
|
||||
private const val LABEL_OFFSET = 2
|
||||
private const val PAYLOAD_STATE = 0
|
||||
private const val PAYLOAD_DOWNLOAD_INFO = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.datepicker.CalendarConstraints
|
||||
import com.google.android.material.datepicker.CalendarConstraints.DateValidator
|
||||
import com.google.android.material.datepicker.CompositeDateValidator
|
||||
@@ -78,17 +80,18 @@ import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
||||
import com.hippo.ehviewer.ui.legacy.ViewTransition
|
||||
import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import com.hippo.ehviewer.util.SimpleHandler
|
||||
import com.hippo.ehviewer.util.getValue
|
||||
import com.hippo.ehviewer.util.lazyMut
|
||||
import com.hippo.ehviewer.util.setValue
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import rikka.core.res.resolveColor
|
||||
import java.time.Instant
|
||||
@@ -160,6 +163,10 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
|
||||
private val dialogState = DialogState()
|
||||
|
||||
override val fabLayout get() = binding.fabLayout
|
||||
|
||||
private fun onItemClick(position: Int) {
|
||||
// Skip if in search mode
|
||||
if (!tracker.isInCustomChoice) {
|
||||
@@ -191,10 +198,11 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
if (keyword.isNullOrEmpty()) {
|
||||
setSearchBarHint(getString(R.string.favorites_title, favCatName))
|
||||
setSearchBarText(null)
|
||||
} else {
|
||||
setSearchBarHint(getString(R.string.favorites_title_2, favCatName, keyword))
|
||||
}
|
||||
setEditTextHint(getString(R.string.favorites_search_bar_hint, favCatName))
|
||||
setEditTextHint(getString(R.string.search_bar_hint, favCatName))
|
||||
Settings.recentFavCat = urlBuilder.favCat
|
||||
}
|
||||
|
||||
@@ -222,6 +230,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
_binding = SceneFavoritesBinding.inflate(inflater, container!!)
|
||||
container.addView(ComposeView(inflater.context).apply { setMD3Content { dialogState.Intercept() } })
|
||||
setOnApplySearch {
|
||||
if (!tracker.isInCustomChoice) {
|
||||
switchFav(urlBuilder.favCat, it)
|
||||
@@ -327,8 +336,8 @@ class FavoritesScene : SearchBarScene() {
|
||||
binding.refreshLayout.setOnRefreshListener { mAdapter?.refresh() }
|
||||
val transition = ViewTransition(binding.refreshLayout, binding.progress, binding.tip)
|
||||
val empty = getString(R.string.gallery_list_empty_hit)
|
||||
adapter.addLoadStateListener {
|
||||
lifecycleScope.launchUI {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
adapter.loadStateFlow.collectLatest {
|
||||
when (val state = it.refresh) {
|
||||
is LoadState.Loading -> {
|
||||
showSearchBar()
|
||||
@@ -381,7 +390,36 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
switchFav(Settings.recentFavCat)
|
||||
}
|
||||
allowEmptySearch = false
|
||||
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
|
||||
return makeMovementFlags(0, ItemTouchHelper.LEFT)
|
||||
}
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder,
|
||||
) = false
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val info = mAdapter!!.peek(position)!!
|
||||
lifecycleScope.launchIO {
|
||||
dialogState.awaitPermissionOrCancel(
|
||||
confirmText = R.string.delete,
|
||||
dismissText = android.R.string.cancel,
|
||||
title = R.string.delete_favorites_dialog_title,
|
||||
onDismiss = { mAdapter!!.notifyItemChanged(position) },
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.delete_favorites_dialog_message, 1))
|
||||
}
|
||||
if (urlBuilder.favCat == FavListUrlBuilder.FAV_CAT_LOCAL) {
|
||||
EhDB.removeLocalFavorites(info)
|
||||
} else {
|
||||
EhEngine.modifyFavorites(info.gid, info.token)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
itemTouchHelper.attachToRecyclerView(binding.recyclerView)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@@ -439,6 +477,16 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSearchViewExpanded() {
|
||||
super.onSearchViewExpanded()
|
||||
selectSearchFab(true)
|
||||
}
|
||||
|
||||
override fun onSearchViewHidden() {
|
||||
super.onSearchViewHidden()
|
||||
selectActionFab(true)
|
||||
}
|
||||
|
||||
private fun showGoToDialog() {
|
||||
context ?: return
|
||||
val local = LocalDateTime.of(2007, 3, 21, 0, 0)
|
||||
@@ -528,5 +576,3 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val ANIMATE_TIME = 300L
|
||||
@@ -68,7 +68,6 @@ import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.FilledTonalIconToggleButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
@@ -159,6 +158,7 @@ import com.hippo.ehviewer.ui.setMD3Content
|
||||
import com.hippo.ehviewer.ui.tools.CrystalCard
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.FilledTertiaryIconButton
|
||||
import com.hippo.ehviewer.ui.tools.FilledTertiaryIconToggleButton
|
||||
import com.hippo.ehviewer.ui.tools.GalleryDetailRating
|
||||
import com.hippo.ehviewer.util.AppHelper
|
||||
import com.hippo.ehviewer.util.ExceptionUtils
|
||||
@@ -712,7 +712,7 @@ class GalleryDetailScene : BaseScene() {
|
||||
stringResource(id = R.string.not_favorited)
|
||||
}
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
FilledTonalIconToggleButton(
|
||||
FilledTertiaryIconToggleButton(
|
||||
checked = favored,
|
||||
onCheckedChange = { modifyFavourite() },
|
||||
) {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.animation.Animator
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
@@ -66,10 +65,12 @@ import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_IMAGE_SEARCH
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_NORMAL
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_SUBSCRIPTION
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_TAG
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_TOPLIST
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_UPLOADER
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_WHATS_HOT
|
||||
import com.hippo.ehviewer.client.exception.CloudflareBypassException
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
@@ -98,13 +99,11 @@ import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.updater.AppUpdater
|
||||
import com.hippo.ehviewer.util.AnimationUtils
|
||||
import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import com.hippo.ehviewer.util.SimpleAnimatorListener
|
||||
import com.hippo.ehviewer.util.getParcelableCompat
|
||||
import com.hippo.ehviewer.util.getValue
|
||||
import com.hippo.ehviewer.util.lazyMut
|
||||
import com.hippo.ehviewer.util.setValue
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -130,10 +129,10 @@ class VMStorage1 : ViewModel() {
|
||||
if (urlBuilder.mode == MODE_TOPLIST) {
|
||||
// TODO: Since we know total pages, let pager support jump
|
||||
val key = (params.key ?: urlBuilder.mJumpTo ?: "0").toInt()
|
||||
val prev = if (key != 0) key - 1 else null
|
||||
val next = if (key != 199) key + 1 else null
|
||||
val prev = (key - 1).takeIf { it > 0 }
|
||||
val next = (key + 1).takeIf { it < TOPLIST_PAGES }
|
||||
runSuspendCatching {
|
||||
urlBuilder.setJumpTo(key.toString())
|
||||
urlBuilder.setJumpTo(key)
|
||||
EhEngine.getGalleryList(urlBuilder.build())
|
||||
}.onFailure {
|
||||
return@withIOContext LoadResult.Error(it)
|
||||
@@ -156,7 +155,7 @@ class VMStorage1 : ViewModel() {
|
||||
}
|
||||
}
|
||||
val r = runSuspendCatching {
|
||||
if (ListUrlBuilder.MODE_IMAGE_SEARCH == urlBuilder.mode) {
|
||||
if (MODE_IMAGE_SEARCH == urlBuilder.mode) {
|
||||
EhEngine.imageSearch(
|
||||
File(urlBuilder.imagePath!!),
|
||||
urlBuilder.isUseSimilarityScan,
|
||||
@@ -192,23 +191,15 @@ class GalleryListScene : SearchBarScene() {
|
||||
}
|
||||
private var _binding: SceneGalleryListBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val mSearchFabAnimatorListener = object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mSearchFab.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
private val mActionFabAnimatorListener = object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
binding.fabLayout.primaryFab?.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
private var fabAnimator: ViewPropertyAnimator? = null
|
||||
private var mViewTransition: ViewTransition? = null
|
||||
private var mAdapter: GalleryAdapter? = null
|
||||
lateinit var mQuickSearchList: MutableList<QuickSearch>
|
||||
private var mHideActionFabSlop = 0
|
||||
private var mShowActionFab = true
|
||||
private var mState = State.NORMAL
|
||||
|
||||
override val fabLayout get() = binding.fabLayout
|
||||
|
||||
override fun getMenuResId(): Int {
|
||||
return R.menu.scene_gallery_list_searchbar_menu
|
||||
}
|
||||
@@ -223,7 +214,6 @@ class GalleryListScene : SearchBarScene() {
|
||||
ACTION_LIST_URL_BUILDER -> args?.getParcelableCompat<ListUrlBuilder>(KEY_LIST_URL_BUILDER)?.copy() ?: ListUrlBuilder()
|
||||
else -> throw IllegalStateException("Wrong KEY_ACTION:${args?.getString(KEY_ACTION)} when handle args!")
|
||||
}
|
||||
args?.getString(KEY_GOTO)?.let { mUrlBuilder.mNext = it }
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -266,10 +256,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
val category = mUrlBuilder.category
|
||||
val mode = mUrlBuilder.mode
|
||||
val isPopular = mode == MODE_WHATS_HOT
|
||||
val isTopList = mode == MODE_TOPLIST
|
||||
if (isTopList != mIsTopList) {
|
||||
mIsTopList = isTopList
|
||||
}
|
||||
mIsTopList = mode == MODE_TOPLIST
|
||||
|
||||
// Update fab visibility
|
||||
binding.fabLayout.setSecondaryFabVisibilityAt(0, !isPopular)
|
||||
@@ -295,6 +282,14 @@ class GalleryListScene : SearchBarScene() {
|
||||
title = resources.getString(R.string.search)
|
||||
}
|
||||
setSearchBarHint(title)
|
||||
|
||||
when (mode) {
|
||||
MODE_NORMAL -> if (category != EhUtils.NONE || !keyword.isNullOrEmpty()) {
|
||||
mainActivity?.clearNavCheckedItem()
|
||||
}
|
||||
MODE_TAG, MODE_UPLOADER, MODE_IMAGE_SEARCH -> mainActivity?.clearNavCheckedItem()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private val dialogState = DialogState()
|
||||
@@ -368,9 +363,8 @@ class GalleryListScene : SearchBarScene() {
|
||||
binding.refreshLayout.setOnRefreshListener { mAdapter?.refresh() }
|
||||
val transition = ViewTransition(binding.refreshLayout, binding.progress, binding.tip)
|
||||
val empty = getString(R.string.gallery_list_empty_hit)
|
||||
val noWatch = getString(R.string.gallery_list_empty_hit_subscription)
|
||||
adapter.addLoadStateListener {
|
||||
lifecycleScope.launchUI {
|
||||
launch {
|
||||
adapter.loadStateFlow.collectLatest {
|
||||
when (val state = it.refresh) {
|
||||
is LoadState.Loading -> {
|
||||
showSearchBar()
|
||||
@@ -397,11 +391,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
delay(500)
|
||||
binding.refreshLayout.isRefreshing = false
|
||||
if (mAdapter?.itemCount == 0) {
|
||||
if (mUrlBuilder.mode == MODE_SUBSCRIPTION) {
|
||||
binding.tip.text = noWatch
|
||||
} else {
|
||||
binding.tip.text = empty
|
||||
}
|
||||
binding.tip.text = empty
|
||||
transition.showView(2)
|
||||
} else {
|
||||
transition.showView(0)
|
||||
@@ -452,12 +442,14 @@ class GalleryListScene : SearchBarScene() {
|
||||
override fun onClickSecondaryFab(view: FabLayout, fab: FloatingActionButton, position: Int) {
|
||||
when (position) {
|
||||
0 -> showGoToDialog()
|
||||
1 -> {
|
||||
1 -> { // First
|
||||
mUrlBuilder.setIndex(null, true)
|
||||
mUrlBuilder.mJumpTo = null
|
||||
mAdapter?.refresh()
|
||||
}
|
||||
2 -> {
|
||||
2 -> { // Last
|
||||
if (mIsTopList) {
|
||||
mUrlBuilder.mJumpTo = "${TOPLIST_PAGES - 1}"
|
||||
mAdapter?.refresh()
|
||||
} else {
|
||||
mUrlBuilder.setIndex("1", false)
|
||||
@@ -481,7 +473,6 @@ class GalleryListScene : SearchBarScene() {
|
||||
}
|
||||
binding.fabLayout.primaryFab!!.setImageDrawable(actionFabDrawable)
|
||||
addAboveSnackView(binding.fabLayout)
|
||||
mSearchFab.setOnClickListener { onApplySearch() }
|
||||
|
||||
// Update list url builder
|
||||
onUpdateUrlBuilder()
|
||||
@@ -520,7 +511,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
val context = context ?: return
|
||||
|
||||
// Can't add image search as quick search
|
||||
if (ListUrlBuilder.MODE_IMAGE_SEARCH == mUrlBuilder.mode) {
|
||||
if (MODE_IMAGE_SEARCH == mUrlBuilder.mode) {
|
||||
showTip(R.string.image_search_not_quick_search, LENGTH_LONG)
|
||||
return
|
||||
}
|
||||
@@ -644,9 +635,9 @@ class GalleryListScene : SearchBarScene() {
|
||||
private fun showGoToDialog() {
|
||||
mAdapter ?: return
|
||||
if (mIsTopList) {
|
||||
val pages = 200
|
||||
val page = mUrlBuilder.mJumpTo?.toIntOrNull() ?: 0
|
||||
val title = getString(R.string.go_to)
|
||||
val hint = getString(R.string.go_to_hint, 1, 200)
|
||||
val hint = getString(R.string.go_to_hint, page + 1, TOPLIST_PAGES)
|
||||
lifecycleScope.launch {
|
||||
val text = dialogState.awaitInputText(
|
||||
title = title,
|
||||
@@ -659,13 +650,13 @@ class GalleryListScene : SearchBarScene() {
|
||||
}.onFailure {
|
||||
return@awaitInputText getString(R.string.error_invalid_number)
|
||||
}.getOrThrow()
|
||||
if (goTo < 0 || goTo >= pages) {
|
||||
if (goTo !in 0..<TOPLIST_PAGES) {
|
||||
getString(R.string.error_out_of_range)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.trim().toInt() - 1
|
||||
mUrlBuilder.setJumpTo(text.toString())
|
||||
mUrlBuilder.setJumpTo(text)
|
||||
mAdapter?.refresh()
|
||||
}
|
||||
} else {
|
||||
@@ -738,70 +729,6 @@ class GalleryListScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectSearchFab(animation: Boolean) {
|
||||
_binding ?: return
|
||||
mShowActionFab = false
|
||||
if (animation) {
|
||||
val fab: View? = binding.fabLayout.primaryFab
|
||||
val delay: Long
|
||||
if (View.INVISIBLE == fab!!.visibility) {
|
||||
delay = 0L
|
||||
} else {
|
||||
delay = ANIMATE_TIME
|
||||
binding.fabLayout.setExpanded(expanded = false, animation = true)
|
||||
fab.animate().scaleX(0.0f).scaleY(0.0f).setListener(mActionFabAnimatorListener)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(0L)
|
||||
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start()
|
||||
}
|
||||
mSearchFab.visibility = View.VISIBLE
|
||||
mSearchFab.rotation = -45.0f
|
||||
mSearchFab.animate().scaleX(1.0f).scaleY(1.0f).rotation(0.0f).setListener(null)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(delay)
|
||||
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
|
||||
} else {
|
||||
binding.fabLayout.setExpanded(expanded = false, animation = false)
|
||||
val fab: View? = binding.fabLayout.primaryFab
|
||||
fab!!.visibility = View.INVISIBLE
|
||||
fab.scaleX = 0.0f
|
||||
fab.scaleY = 0.0f
|
||||
mSearchFab.visibility = View.VISIBLE
|
||||
mSearchFab.scaleX = 1.0f
|
||||
mSearchFab.scaleY = 1.0f
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectActionFab(animation: Boolean) {
|
||||
_binding ?: return
|
||||
mShowActionFab = true
|
||||
if (animation) {
|
||||
val delay: Long
|
||||
if (View.INVISIBLE == mSearchFab.visibility) {
|
||||
delay = 0L
|
||||
} else {
|
||||
delay = ANIMATE_TIME
|
||||
mSearchFab.animate().scaleX(0.0f).scaleY(0.0f)
|
||||
.setListener(mSearchFabAnimatorListener)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(0L)
|
||||
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start()
|
||||
}
|
||||
val fab: View? = binding.fabLayout.primaryFab
|
||||
fab!!.visibility = View.VISIBLE
|
||||
fab.rotation = -45.0f
|
||||
fab.animate().scaleX(1.0f).scaleY(1.0f).rotation(0.0f).setListener(null)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(delay)
|
||||
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
|
||||
} else {
|
||||
binding.fabLayout.setExpanded(expanded = false, animation = false)
|
||||
val fab: View? = binding.fabLayout.primaryFab
|
||||
fab!!.visibility = View.VISIBLE
|
||||
fab.scaleX = 1.0f
|
||||
fab.scaleY = 1.0f
|
||||
mSearchFab.visibility = View.INVISIBLE
|
||||
mSearchFab.scaleX = 0.0f
|
||||
mSearchFab.scaleY = 0.0f
|
||||
}
|
||||
}
|
||||
|
||||
private fun setState(state: State) {
|
||||
setState(state, true)
|
||||
}
|
||||
@@ -863,24 +790,35 @@ class GalleryListScene : SearchBarScene() {
|
||||
private fun onApplySearch(query: String?) {
|
||||
_binding ?: return
|
||||
lifecycleScope.launchIO {
|
||||
val builder = ListUrlBuilder()
|
||||
val oldMode = mUrlBuilder.mode
|
||||
if (mState == State.SEARCH || mState == State.SEARCH_SHOW_LIST) {
|
||||
try {
|
||||
binding.searchLayout.formatListUrlBuilder(mUrlBuilder, query)
|
||||
binding.searchLayout.formatListUrlBuilder(builder, query)
|
||||
} catch (e: EhException) {
|
||||
showTip(e.message, LENGTH_LONG)
|
||||
return@launchIO
|
||||
}
|
||||
} else {
|
||||
val oldMode = mUrlBuilder.mode
|
||||
// If it's MODE_SUBSCRIPTION, keep it
|
||||
val newMode = if (oldMode == MODE_SUBSCRIPTION) MODE_SUBSCRIPTION else MODE_NORMAL
|
||||
mUrlBuilder.reset()
|
||||
mUrlBuilder.mode = newMode
|
||||
mUrlBuilder.keyword = query
|
||||
builder.mode = newMode
|
||||
builder.keyword = query
|
||||
}
|
||||
withUIContext {
|
||||
onUpdateUrlBuilder()
|
||||
mAdapter?.refresh()
|
||||
when (oldMode) {
|
||||
MODE_TOPLIST, MODE_WHATS_HOT -> {
|
||||
// Wait for search view to hide
|
||||
delay(300)
|
||||
navAnimated(R.id.galleryListScene, builder.toStartArgs())
|
||||
}
|
||||
|
||||
else -> {
|
||||
mUrlBuilder = builder
|
||||
onUpdateUrlBuilder()
|
||||
mAdapter?.refresh()
|
||||
}
|
||||
}
|
||||
setState(State.NORMAL)
|
||||
}
|
||||
}
|
||||
@@ -899,26 +837,23 @@ class GalleryListScene : SearchBarScene() {
|
||||
|
||||
private inner class QsDrawerAdapter(private val mInflater: LayoutInflater) :
|
||||
RecyclerView.Adapter<QsDrawerHolder>() {
|
||||
private val keywords = intArrayOf(11, 12, 13, 15)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QsDrawerHolder {
|
||||
val holder = QsDrawerHolder(ItemDrawerListBinding.inflate(mInflater, parent, false))
|
||||
if (!mIsTopList) {
|
||||
holder.itemView.setOnClickListener {
|
||||
holder.itemView.setOnClickListener {
|
||||
if (!mIsTopList) {
|
||||
val q = mQuickSearchList[holder.bindingAdapterPosition]
|
||||
mUrlBuilder.set(q)
|
||||
onUpdateUrlBuilder()
|
||||
mAdapter?.refresh()
|
||||
setState(State.NORMAL)
|
||||
closeSideSheet()
|
||||
}
|
||||
} else {
|
||||
val keywords = intArrayOf(11, 12, 13, 15)
|
||||
holder.itemView.setOnClickListener {
|
||||
// Navigate to galleryListScene to clear nav checked item
|
||||
navAnimated(R.id.galleryListScene, ListUrlBuilder().apply { set(q) }.toStartArgs())
|
||||
} else {
|
||||
mUrlBuilder.keyword = keywords[holder.bindingAdapterPosition].toString()
|
||||
mUrlBuilder.mJumpTo = null
|
||||
onUpdateUrlBuilder()
|
||||
mAdapter?.refresh()
|
||||
setState(State.NORMAL)
|
||||
closeSideSheet()
|
||||
}
|
||||
setState(State.NORMAL)
|
||||
closeSideSheet()
|
||||
}
|
||||
return holder
|
||||
}
|
||||
@@ -1048,7 +983,6 @@ class GalleryListScene : SearchBarScene() {
|
||||
|
||||
companion object {
|
||||
const val KEY_ACTION = "action"
|
||||
const val KEY_GOTO = "goto"
|
||||
const val ACTION_HOMEPAGE = "action_homepage"
|
||||
const val ACTION_SUBSCRIPTION = "action_subscription"
|
||||
const val ACTION_WHATS_HOT = "action_whats_hot"
|
||||
@@ -1056,7 +990,6 @@ class GalleryListScene : SearchBarScene() {
|
||||
const val ACTION_LIST_URL_BUILDER = "action_list_url_builder"
|
||||
const val KEY_LIST_URL_BUILDER = "list_url_builder"
|
||||
const val KEY_STATE = "state"
|
||||
private const val ANIMATE_TIME = 300L
|
||||
fun ListUrlBuilder.toStartArgs() = bundleOf(
|
||||
KEY_ACTION to ACTION_LIST_URL_BUILDER,
|
||||
KEY_LIST_URL_BUILDER to this,
|
||||
@@ -1064,6 +997,8 @@ class GalleryListScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
|
||||
private const val TOPLIST_PAGES = 200
|
||||
|
||||
@Parcelize
|
||||
enum class State : Parcelable {
|
||||
NORMAL, SIMPLE_SEARCH, SEARCH, SEARCH_SHOW_LIST
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.animation.Animator
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
@@ -40,7 +41,10 @@ import com.hippo.ehviewer.dao.Search
|
||||
import com.hippo.ehviewer.dao.SearchDao
|
||||
import com.hippo.ehviewer.databinding.SceneSearchbarBinding
|
||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
import com.hippo.ehviewer.util.AnimationUtils
|
||||
import com.hippo.ehviewer.util.SimpleAnimatorListener
|
||||
import com.jamal.composeprefs3.ui.ifNotNullThen
|
||||
import com.jamal.composeprefs3.ui.ifTrueThen
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
@@ -57,6 +61,18 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
private val mSearchDatabase = searchDatabase.searchDao()
|
||||
private var onApplySearch: (String) -> Unit = {}
|
||||
protected val mSearchFab get() = binding.searchFab
|
||||
protected abstract val fabLayout: FabLayout
|
||||
protected var mShowActionFab = true
|
||||
protected val mActionFabAnimatorListener = object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
fabLayout.primaryFab?.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
private val mSearchFabAnimatorListener = object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mSearchFab.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = SceneSearchbarBinding.inflate(inflater, container, false)
|
||||
@@ -99,6 +115,7 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
onSearchViewHidden()
|
||||
}
|
||||
}
|
||||
mSearchFab.setOnClickListener { onApplySearch() }
|
||||
onCreateViewWithToolbar(inflater, binding.root, savedInstanceState)
|
||||
// This has to be placed after onCreateViewWithToolbar() since
|
||||
// callbacks are invoked in the reverse order in which they are added
|
||||
@@ -147,6 +164,83 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
privLockModeStart = null
|
||||
}
|
||||
|
||||
fun showSearchFab(animation: Boolean, delay: Long = 0) {
|
||||
if (animation) {
|
||||
mSearchFab.visibility = View.VISIBLE
|
||||
mSearchFab.rotation = -45.0f
|
||||
mSearchFab.animate().scaleX(1.0f).scaleY(1.0f).rotation(0.0f).setListener(null)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(delay)
|
||||
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
|
||||
} else {
|
||||
mSearchFab.visibility = View.VISIBLE
|
||||
mSearchFab.scaleX = 1.0f
|
||||
mSearchFab.scaleY = 1.0f
|
||||
}
|
||||
}
|
||||
|
||||
fun hideSearchFab(animation: Boolean): Long {
|
||||
return if (View.INVISIBLE == mSearchFab.visibility) {
|
||||
0L
|
||||
} else {
|
||||
if (animation) {
|
||||
mSearchFab.animate().scaleX(0.0f).scaleY(0.0f)
|
||||
.setListener(mSearchFabAnimatorListener)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(0L)
|
||||
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start()
|
||||
} else {
|
||||
mSearchFab.visibility = View.INVISIBLE
|
||||
mSearchFab.scaleX = 0.0f
|
||||
mSearchFab.scaleY = 0.0f
|
||||
}
|
||||
ANIMATE_TIME
|
||||
}
|
||||
}
|
||||
|
||||
fun selectSearchFab(animation: Boolean) {
|
||||
_binding ?: return
|
||||
mShowActionFab = false
|
||||
val delay = if (animation) {
|
||||
val fab = fabLayout.primaryFab!!
|
||||
if (View.INVISIBLE == fab.visibility) {
|
||||
0L
|
||||
} else {
|
||||
fabLayout.setExpanded(expanded = false, animation = true)
|
||||
fab.animate().scaleX(0.0f).scaleY(0.0f).setListener(mActionFabAnimatorListener)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(0L)
|
||||
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start()
|
||||
ANIMATE_TIME
|
||||
}
|
||||
} else {
|
||||
fabLayout.setExpanded(expanded = false, animation = false)
|
||||
val fab = fabLayout.primaryFab!!
|
||||
fab.visibility = View.INVISIBLE
|
||||
fab.scaleX = 0.0f
|
||||
fab.scaleY = 0.0f
|
||||
0L
|
||||
}
|
||||
showSearchFab(animation, delay)
|
||||
}
|
||||
|
||||
fun selectActionFab(animation: Boolean) {
|
||||
_binding ?: return
|
||||
mShowActionFab = true
|
||||
val delay = hideSearchFab(animation)
|
||||
if (animation) {
|
||||
val fab = fabLayout.primaryFab!!
|
||||
fab.visibility = View.VISIBLE
|
||||
fab.rotation = -45.0f
|
||||
fab.animate().scaleX(1.0f).scaleY(1.0f).rotation(0.0f).setListener(null)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(delay)
|
||||
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
|
||||
} else {
|
||||
fabLayout.setExpanded(expanded = false, animation = false)
|
||||
val fab = fabLayout.primaryFab!!
|
||||
fab.visibility = View.VISIBLE
|
||||
fab.scaleX = 1.0f
|
||||
fab.scaleY = 1.0f
|
||||
}
|
||||
}
|
||||
|
||||
fun setSearchBarHint(hint: String?) {
|
||||
binding.toolbar.hint = hint
|
||||
}
|
||||
@@ -329,3 +423,5 @@ fun wrapTagKeyword(keyword: String, translate: Boolean = false): String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const val ANIMATE_TIME = 300L
|
||||
@@ -126,11 +126,6 @@ fun AdvancedScreen() {
|
||||
entryValueRes = R.array.app_language_entry_values,
|
||||
value = Settings::language,
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_advanced_enable_quic),
|
||||
summary = stringResource(id = R.string.settings_advanced_enable_quic_summary),
|
||||
value = Settings::enableQuic,
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.preload_thumb_aggressively),
|
||||
value = Settings::preloadThumbAggressively,
|
||||
|
||||
@@ -193,6 +193,27 @@ fun EhScreen() {
|
||||
summary = stringResource(id = R.string.settings_my_tags_summary),
|
||||
) { navController.navigate(MYTAGS_SCREEN) }
|
||||
}
|
||||
var defaultFavSlot by Settings::defaultFavSlot.observed
|
||||
val disabled = stringResource(id = R.string.disabled_nav)
|
||||
val localFav = stringResource(id = R.string.local_favorites)
|
||||
val summary = when (defaultFavSlot) {
|
||||
-1 -> localFav
|
||||
in 0..9 -> Settings.favCat[defaultFavSlot]
|
||||
else -> stringResource(id = R.string.default_favorites_warning)
|
||||
}
|
||||
val items = if (signin) {
|
||||
arrayOf(disabled, localFav, *Settings.favCat)
|
||||
} else {
|
||||
arrayOf(disabled, localFav)
|
||||
}
|
||||
Preference(
|
||||
title = stringResource(id = R.string.default_favorites_collection),
|
||||
summary = summary,
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
defaultFavSlot = dialogState.showSelectItem(*items, title = R.string.default_favorites_collection) - 2
|
||||
}
|
||||
}
|
||||
SimpleMenuPreferenceInt(
|
||||
title = stringResource(id = rikka.core.R.string.dark_theme),
|
||||
entry = R.array.night_mode_entries,
|
||||
@@ -244,27 +265,6 @@ fun EhScreen() {
|
||||
entryValueRes = R.array.thumb_resolution_entry_values,
|
||||
value = thumbResolution,
|
||||
)
|
||||
var defaultFavSlot by Settings::defaultFavSlot.observed
|
||||
val disabled = stringResource(id = R.string.disabled_nav)
|
||||
val localFav = stringResource(id = R.string.local_favorites)
|
||||
val summary = when (defaultFavSlot) {
|
||||
-1 -> localFav
|
||||
in 0..9 -> Settings.favCat[defaultFavSlot]
|
||||
else -> stringResource(id = R.string.default_favorites_warning)
|
||||
}
|
||||
val items = if (signin) {
|
||||
arrayOf(disabled, localFav, *Settings.favCat)
|
||||
} else {
|
||||
arrayOf(disabled, localFav)
|
||||
}
|
||||
Preference(
|
||||
title = stringResource(id = R.string.default_favorites_collection),
|
||||
summary = summary,
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
defaultFavSlot = dialogState.showSelectItem(*items, title = R.string.default_favorites_collection) - 2
|
||||
}
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_eh_force_eh_thumb),
|
||||
summary = stringResource(id = R.string.settings_eh_force_eh_thumb_summary),
|
||||
|
||||
@@ -3,12 +3,10 @@ package com.hippo.ehviewer.ui.tools
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
@@ -148,18 +146,25 @@ class DialogState {
|
||||
@StringRes confirmText: Int? = null,
|
||||
@StringRes dismissText: Int? = null,
|
||||
@StringRes title: Int? = null,
|
||||
onDismiss: () -> Unit = {},
|
||||
text: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
return dialog { cont ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { cont.cancel() },
|
||||
onDismissRequest = {
|
||||
onDismiss()
|
||||
cont.cancel()
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { cont.resume(Unit) }) {
|
||||
Text(text = stringResource(id = confirmText ?: android.R.string.ok))
|
||||
}
|
||||
},
|
||||
dismissButton = dismissText.ifNotNullThen {
|
||||
TextButton(onClick = { cont.cancel() }) {
|
||||
TextButton(onClick = {
|
||||
onDismiss()
|
||||
cont.cancel()
|
||||
}) {
|
||||
Text(text = stringResource(id = dismissText!!))
|
||||
}
|
||||
},
|
||||
@@ -195,12 +200,22 @@ class DialogState {
|
||||
vararg items: String,
|
||||
@StringRes title: Int,
|
||||
): Int = showNoButton {
|
||||
Column(modifier = Modifier.padding(24.dp)) {
|
||||
Text(text = stringResource(id = title), style = MaterialTheme.typography.headlineSmall)
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.tertiary) {
|
||||
items.forEachIndexed { index, text ->
|
||||
Text(text = text, modifier = Modifier.clickable { dismissWith(index) }.fillMaxWidth().padding(vertical = 8.dp), style = MaterialTheme.typography.titleMedium)
|
||||
Column(modifier = Modifier.padding(vertical = 8.dp)) {
|
||||
Text(
|
||||
text = stringResource(id = title),
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
)
|
||||
LazyColumn {
|
||||
itemsIndexed(items) { index, text ->
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.tertiary) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier.clickable { dismissWith(index) }.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.google.android.gms.net.CronetProviderInstaller
|
||||
|
||||
val isCronetSupported: Boolean
|
||||
get() = Settings.enableQuic && isAtLeastQ
|
||||
get() = isAtLeastQ && CronetProviderInstaller.isInstalled()
|
||||
@@ -21,3 +21,5 @@ fun String.toLongOrDefault(defaultValue: Long): Long {
|
||||
fun String.toFloatOrDefault(defaultValue: Float): Float {
|
||||
return toFloatOrNull() ?: defaultValue
|
||||
}
|
||||
|
||||
fun String?.containsIgnoreCase(other: String) = this?.contains(other, true) ?: false
|
||||
@@ -17,29 +17,26 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_filter"
|
||||
android:icon="@drawable/v_filter_dark_x24"
|
||||
android:title="@string/download_filter"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_open_side_sheet"
|
||||
android:icon="@drawable/ic_outline_label_24"
|
||||
android:title="@string/download_labels"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_filter"
|
||||
android:title="@string/download_filter"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_start_all"
|
||||
android:icon="@drawable/v_play_x24"
|
||||
android:title="@string/download_start_all"
|
||||
app:showAsAction="ifRoom" />
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_stop_all"
|
||||
android:icon="@drawable/v_pause_x24"
|
||||
android:title="@string/download_stop_all"
|
||||
app:showAsAction="ifRoom" />
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_reset_reading_progress"
|
||||
|
||||
@@ -294,7 +294,7 @@
|
||||
<string name="settings_advanced_dump_logcat_to">Protokolldatei wurde auf %s gespeichert.</string>
|
||||
<string name="cloud_favorites">Cloud-Favoriten</string>
|
||||
<string name="local_favorites">Lokale Favoriten</string>
|
||||
<string name="favorites_search_bar_hint">%s durchsuchen</string>
|
||||
<string name="search_bar_hint">%s durchsuchen</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">Aus Favoriten löschen</string>
|
||||
|
||||
@@ -354,7 +354,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">Favoritos en la nube</string>
|
||||
<string name="local_favorites">Favoritos locales</string>
|
||||
<string name="favorites_search_bar_hint">Buscar %s</string>
|
||||
<string name="search_bar_hint">Buscar %s</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">Eliminar de favoritos</string>
|
||||
|
||||
@@ -393,7 +393,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">Favoris de cloud</string>
|
||||
<string name="local_favorites">Favoris locales</string>
|
||||
<string name="favorites_search_bar_hint">Chercher dans %s</string>
|
||||
<string name="search_bar_hint">Chercher dans %s</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">Effacer de favoris</string>
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">Cloudのお気に入り</string>
|
||||
<string name="local_favorites">ローカルのお気に入り</string>
|
||||
<string name="favorites_search_bar_hint">%sを検索</string>
|
||||
<string name="search_bar_hint">%sを検索</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">お気に入りから削除</string>
|
||||
|
||||
@@ -364,7 +364,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">클라우드 즐겨찾기</string>
|
||||
<string name="local_favorites">로컬 즐겨찾기</string>
|
||||
<string name="favorites_search_bar_hint">%s 검색</string>
|
||||
<string name="search_bar_hint">%s 검색</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">즐겨찾기에서 삭제</string>
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">รายการโปรดบนเว็บ</string>
|
||||
<string name="local_favorites">รายการโปรดบนเครื่อง</string>
|
||||
<string name="favorites_search_bar_hint">ค้นหา %s</string>
|
||||
<string name="search_bar_hint">ค้นหา %s</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">ลบออกจากรายการโปรด</string>
|
||||
|
||||
@@ -376,8 +376,6 @@
|
||||
<string name="settings_advanced_dump_logcat_to">已保存日志至 %s</string>
|
||||
<string name="settings_advanced_read_cache_size">阅读缓存大小</string>
|
||||
<string name="settings_advanced_app_language_title">App 界面语言</string>
|
||||
<string name="settings_advanced_enable_quic">[实验性] QUIC 支持</string>
|
||||
<string name="settings_advanced_enable_quic_summary">启用 QUIC 协议支持(需要重启应用)</string>
|
||||
<string name="settings_advanced_export_data">导出数据</string>
|
||||
<string name="settings_advanced_export_data_summary">保存数据至外置存储器,例如下载列表,快速搜索列表</string>
|
||||
<string name="settings_advanced_export_data_to">已导出数据至 %s</string>
|
||||
@@ -409,7 +407,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">云端收藏</string>
|
||||
<string name="local_favorites">本地收藏</string>
|
||||
<string name="favorites_search_bar_hint">搜索 %s</string>
|
||||
<string name="search_bar_hint">搜索 %s</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">删除收藏</string>
|
||||
|
||||
@@ -419,7 +419,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">雲端收藏</string>
|
||||
<string name="local_favorites">本地收藏</string>
|
||||
<string name="favorites_search_bar_hint">搜索 %s</string>
|
||||
<string name="search_bar_hint">搜索 %s</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">刪除收藏</string>
|
||||
|
||||
@@ -400,7 +400,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">雲端收藏</string>
|
||||
<string name="local_favorites">本機收藏</string>
|
||||
<string name="favorites_search_bar_hint">搜尋 %s</string>
|
||||
<string name="search_bar_hint">搜尋 %s</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">移除收藏</string>
|
||||
|
||||
@@ -420,8 +420,6 @@
|
||||
<string name="settings_advanced_dump_logcat_to">Logcat dumped to %s</string>
|
||||
<string name="settings_advanced_read_cache_size">Read cache size</string>
|
||||
<string name="settings_advanced_app_language_title">App language</string>
|
||||
<string name="settings_advanced_enable_quic">[Experimental] QUIC support</string>
|
||||
<string name="settings_advanced_enable_quic_summary">Enable QUIC protocol support (App restart required)</string>
|
||||
<string name="settings_advanced_export_data">Export data</string>
|
||||
<string name="settings_advanced_export_data_summary">Save data like download list, quick search list, to external storage</string>
|
||||
<string name="settings_advanced_export_data_to">Exported data to %s</string>
|
||||
@@ -467,7 +465,7 @@
|
||||
<!-- Favorites -->
|
||||
<string name="cloud_favorites">Cloud Favorites</string>
|
||||
<string name="local_favorites">Local Favorites</string>
|
||||
<string name="favorites_search_bar_hint">Search %s</string>
|
||||
<string name="search_bar_hint">Search %s</string>
|
||||
<string name="favorites_title">%s</string>
|
||||
<string name="favorites_title_2">%1$s - %2$s</string>
|
||||
<string name="delete_favorites_dialog_title">Delete from favorites</string>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Reference in new issue
Block a user