Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f51da1a948 | ||
|
|
c1ac592855 | ||
|
|
371103908b | ||
|
|
a2a029bb28 | ||
|
|
9266fba415 | ||
|
|
0963e2f564 | ||
|
|
72a5906a9b | ||
|
|
4dbd54c495 | ||
|
|
6219acb15c | ||
|
|
c4179bef5d | ||
|
|
117a1eaab8 | ||
|
|
78e31e4fb9 | ||
|
|
3da86fd745 | ||
|
|
e0c710ff5f | ||
|
|
2946459f1d | ||
|
|
5b5f328e06 | ||
|
|
b496e18e7a | ||
|
|
4abb2ab0ce | ||
|
|
16edaacf37 | ||
|
|
b69aea9d14 | ||
|
|
656d372612 | ||
|
|
09a2ebcc61 | ||
|
|
b74d0e9006 | ||
|
|
a7c81130cd | ||
|
|
c0b75cdbe9 | ||
|
|
2c15c74697 | ||
|
|
ba9308bfbc | ||
|
|
21050b49d4 | ||
|
|
842da49b08 | ||
|
|
44fb2b70a5 | ||
|
|
9ef95f56cf | ||
|
|
b5b08404bd | ||
|
|
17abca7f7d | ||
|
|
c8d4782b2a | ||
|
|
e0af610c2c | ||
|
|
ddc645c323 | ||
|
|
779dd1f8fd | ||
|
|
cf10e2ff6d | ||
|
|
55d1094e55 | ||
|
|
8248ac6e2f | ||
|
|
4b2ac8ae5e | ||
|
|
085e0938cb | ||
|
|
9a2c0d7121 | ||
|
|
aa8834b941 | ||
|
|
d6b524bc47 | ||
|
|
1ca960995f | ||
|
|
31be34f551 | ||
|
|
e0d1c42391 | ||
|
|
1b9c3050aa | ||
|
|
5ba0afdd24 | ||
|
|
a8eb4ce47a | ||
|
|
feba6dfeb0 | ||
|
|
061f4a0ed0 | ||
|
|
f7c273a4f3 | ||
|
|
096dae3cd0 | ||
|
|
eebf51f4a6 | ||
|
|
000ae02d47 | ||
|
|
84c1ee3e88 | ||
|
|
0fea2076d9 | ||
|
|
b7a90b171f | ||
|
|
7a68f331b0 | ||
|
|
9e7abd5a85 | ||
|
|
9bc3f1137e | ||
|
|
0c7e3ac8b4 | ||
|
|
715f863167 | ||
|
|
e062301d42 | ||
|
|
77396558d7 | ||
|
|
d11c16c549 | ||
|
|
6a90ac40be | ||
|
|
25cc1d77b1 | ||
|
|
fd489d93bb | ||
|
|
e80487e379 | ||
|
|
3b4c2981ac | ||
|
|
348d45f2f5 | ||
|
|
4db96c7bdb | ||
|
|
b1617c6996 | ||
|
|
c078166e8d | ||
|
|
092936eda5 | ||
|
|
257a999300 | ||
|
|
df6a5040a5 | ||
|
|
3085ca2eeb | ||
|
|
fa4005cf9a | ||
|
|
c4cd375c04 | ||
|
|
19d9bb198f | ||
|
|
7016190ce5 | ||
|
|
8020ffa666 | ||
|
|
bc9914ffa6 | ||
|
|
4ded4ff470 | ||
|
|
7742299651 | ||
|
|
0b348a59d2 |
No files matched your search
@@ -48,7 +48,7 @@ jobs:
|
||||
key: cmake-
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
|
||||
cache-read-only: true
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
run: cargo clippy --target aarch64-linux-android --all-features -- -D warnings
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-read-only: true
|
||||
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
save-if: "false"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
|
||||
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
save-if: "false"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
key: cmake-
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
|
||||
cache-read-only: true
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
# 变更日志
|
||||
|
||||
## [1.14.5] - 2025-10-19
|
||||
|
||||
### 改进
|
||||
|
||||
* [阅读器] 显示画廊标题
|
||||
* [阅读器] 显示菜单时改变标题栏颜色
|
||||
|
||||
### Bug 修复
|
||||
|
||||
* 横屏模式下打开阅读器设置会闪退 [#2673](https://github.com/FooIbar/EhViewer/issues/2673)
|
||||
* 强制横屏时竖直拿取设备加载图片会横竖屏乱跳 [#2674](https://github.com/FooIbar/EhViewer/issues/2674)
|
||||
|
||||
|
||||
## [1.14.4] - 2025-10-12
|
||||
|
||||
### 改进
|
||||
|
||||
* [阅读器] 支持禁用使用音量键翻页 [#2593](https://github.com/FooIbar/EhViewer/issues/2593)
|
||||
* 视搜索警告为错误 [#2604](https://github.com/FooIbar/EhViewer/issues/2604)
|
||||
* 余额不足时停止下载并显示通知 [#2479](https://github.com/FooIbar/EhViewer/issues/2479)
|
||||
* [阅读器] 使用默认阅读模式时对带有 webtoon 标签的画廊自动使用条漫模式 [#2632](https://github.com/FooIbar/EhViewer/issues/2632)
|
||||
* 优化按页数排序下载项目的速度
|
||||
* 开启显示标签投票状态时为 power >= 100 的标签添加下划线
|
||||
* 杂项优化
|
||||
|
||||
### Bug 修复
|
||||
|
||||
* Android 16 QPR1 上画廊详情中的标签文本对比度不足
|
||||
* 初始化 DownloadManager 用时过长导致 ANR [#2588](https://github.com/FooIbar/EhViewer/issues/2588)
|
||||
* 某些设备上无法下载名称较长的画廊 [#2607](https://github.com/FooIbar/EhViewer/issues/2607)
|
||||
* 评论区链接实际可点击区域比画面显示上的略长 [#2615](https://github.com/FooIbar/EhViewer/issues/2615)
|
||||
* 主页快速搜索再次打开出现以前的画廊重复出现在最上层 [#2628](https://github.com/FooIbar/EhViewer/issues/2628)
|
||||
* 非条漫模式下原始大小较小的动图不播放 [#2652](https://github.com/FooIbar/EhViewer/issues/2652)
|
||||
* Android 9 以下打开评论区切屏后回来会闪退 [#2655](https://github.com/FooIbar/EhViewer/issues/2655)
|
||||
* 杂项修复
|
||||
|
||||
|
||||
## [1.14.3] - 2025-09-01
|
||||
|
||||
### 新功能
|
||||
@@ -9,6 +46,7 @@
|
||||
### Bug 修复
|
||||
|
||||
* [阅读器] 关闭滤镜设置时崩溃 [#2573](https://github.com/FooIbar/EhViewer/issues/2573)
|
||||
* 编辑评论时上下文菜单缺少格式化选项
|
||||
* 某些情况下搜索相似画廊时出错
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ plugins {
|
||||
alias(libs.plugins.ehviewer.android.application)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.aboutlibrariesPlugin)
|
||||
alias(libs.plugins.aboutlibraries)
|
||||
alias(libs.plugins.aboutlibrariesAndroid)
|
||||
alias(libs.plugins.baselineprofile)
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "moe.tarsin.ehviewer"
|
||||
versionCode = 180063
|
||||
versionName = "1.14.3"
|
||||
versionName = "1.14.5"
|
||||
// versionNameSuffix = "-SNAPSHOT"
|
||||
buildConfigField("String", "RAW_VERSION_NAME", "\"$versionName${versionNameSuffix.orEmpty()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
|
||||
@@ -204,7 +205,6 @@ dependencies {
|
||||
implementation(libs.androidx.paging.compose)
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/room
|
||||
ksp(libs.androidx.room.compiler)
|
||||
implementation(libs.androidx.room.paging)
|
||||
|
||||
implementation(libs.androidx.work.runtime)
|
||||
@@ -216,8 +216,6 @@ dependencies {
|
||||
// https://square.github.io/okhttp/changelogs/changelog/
|
||||
implementation(platform(libs.okhttp.bom))
|
||||
|
||||
implementation(platform(libs.okio.bom))
|
||||
|
||||
implementation(libs.logcat)
|
||||
|
||||
implementation(libs.diff)
|
||||
@@ -258,13 +256,12 @@ kotlin {
|
||||
optIn.addAll(
|
||||
"coil3.annotation.ExperimentalCoilApi",
|
||||
"androidx.paging.ExperimentalPagingApi",
|
||||
"kotlinx.serialization.ExperimentalSerializationApi",
|
||||
"me.saket.telephoto.ExperimentalTelephotoApi",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
arg("compose-destinations.codeGenPackageName", "com.hippo.ehviewer.ui")
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:banner="@mipmap/ic_leanback_banner"
|
||||
android:configChanges="screenSize"
|
||||
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/AppTheme.Splash"
|
||||
|
||||
@@ -4,17 +4,17 @@ include(ExternalProject)
|
||||
include(FetchContent)
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Ofast -fvisibility=hidden -fvisibility-inlines-hidden -funroll-loops -flto \
|
||||
-mllvm -polly \
|
||||
-mllvm -polly-run-dce \
|
||||
-mllvm -polly-run-inliner \
|
||||
-mllvm -polly-isl-arg=--no-schedule-serialize-sccs \
|
||||
-mllvm -polly-ast-use-context \
|
||||
-mllvm -polly-detect-keep-going \
|
||||
-mllvm -polly-position=before-vectorizer \
|
||||
-mllvm -polly-vectorizer=stripmine \
|
||||
-mllvm -polly-detect-profitability-min-per-loop-insts=40 \
|
||||
-mllvm -polly-invariant-load-hoisting")
|
||||
string(CONCAT CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -ffast-math -fvisibility=hidden -fvisibility-inlines-hidden -funroll-loops -flto "
|
||||
"-mllvm -polly "
|
||||
"-mllvm -polly-run-dce "
|
||||
"-mllvm -polly-run-inliner "
|
||||
"-mllvm -polly-isl-arg=--no-schedule-serialize-sccs "
|
||||
"-mllvm -polly-ast-use-context "
|
||||
"-mllvm -polly-detect-keep-going "
|
||||
"-mllvm -polly-position=before-vectorizer "
|
||||
"-mllvm -polly-vectorizer=stripmine "
|
||||
"-mllvm -polly-detect-profitability-min-per-loop-insts=40 "
|
||||
"-mllvm -polly-invariant-load-hoisting")
|
||||
endif (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
|
||||
option(BUILD_TESTING OFF)
|
||||
@@ -96,7 +96,7 @@ set(LIBARCHIVE_PATCH
|
||||
FetchContent_Declare(
|
||||
libarchive
|
||||
GIT_REPOSITORY https://github.com/libarchive/libarchive.git
|
||||
GIT_TAG v3.8.1
|
||||
GIT_TAG v3.8.2
|
||||
GIT_SHALLOW 1
|
||||
PATCH_COMMAND git apply --check -R ${LIBARCHIVE_PATCH} || git apply ${LIBARCHIVE_PATCH}
|
||||
)
|
||||
@@ -143,8 +143,9 @@ corrosion_import_crate(
|
||||
MANIFEST_PATH ../rust/Cargo.toml
|
||||
LOCKED
|
||||
FEATURES $<IF:$<VERSION_GREATER_EQUAL:${ANDROID_NATIVE_API_LEVEL},26>,android-26,android>
|
||||
FLAGS $<$<NOT:$<CONFIG:Debug>>:-Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort>
|
||||
FLAGS $<$<NOT:$<CONFIG:Debug>>:-Z build-std -Z build-std-features=>
|
||||
)
|
||||
corrosion_add_target_rustflags(ehviewer_rust $<$<NOT:$<CONFIG:Debug>>:-Z unstable-options -C panic=immediate-abort>)
|
||||
corrosion_link_libraries(ehviewer_rust android jnigraphics log webp webpdemux)
|
||||
|
||||
# Build and link our app's native lib
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 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
|
||||
*
|
||||
* https://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.google.accompanist.systemuicontroller
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.view.View
|
||||
import android.view.Window
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
|
||||
/**
|
||||
* A class which provides easy-to-use utilities for updating the System UI bar
|
||||
* colors within Jetpack Compose.
|
||||
*/
|
||||
@Stable
|
||||
interface SystemUiController {
|
||||
|
||||
/**
|
||||
* Control for the behavior of the system bars. This value should be one of the
|
||||
* [WindowInsetsControllerCompat] behavior constants:
|
||||
* [WindowInsetsControllerCompat.BEHAVIOR_SHOW_BARS_BY_TOUCH] (Deprecated),
|
||||
* [WindowInsetsControllerCompat.BEHAVIOR_DEFAULT] and
|
||||
* [WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE].
|
||||
*/
|
||||
var systemBarsBehavior: Int
|
||||
|
||||
/**
|
||||
* Property which holds the status bar visibility. If set to true, show the status bar,
|
||||
* otherwise hide the status bar.
|
||||
*/
|
||||
var isStatusBarVisible: Boolean
|
||||
|
||||
/**
|
||||
* Property which holds the navigation bar visibility. If set to true, show the navigation bar,
|
||||
* otherwise hide the navigation bar.
|
||||
*/
|
||||
var isNavigationBarVisible: Boolean
|
||||
|
||||
/**
|
||||
* Property which holds the status & navigation bar visibility. If set to true, show both bars,
|
||||
* otherwise hide both bars.
|
||||
*/
|
||||
var isSystemBarsVisible: Boolean
|
||||
get() = isNavigationBarVisible && isStatusBarVisible
|
||||
set(value) {
|
||||
isStatusBarVisible = value
|
||||
isNavigationBarVisible = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Property which holds whether the status bar icons + content are 'dark' or not.
|
||||
*/
|
||||
var statusBarDarkContentEnabled: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Remembers a [SystemUiController] for the given [window].
|
||||
*
|
||||
* If no [window] is provided, an attempt to find the correct [Window] is made.
|
||||
*
|
||||
* First, if the [LocalView]'s parent is a [DialogWindowProvider], then that dialog's [Window] will
|
||||
* be used.
|
||||
*
|
||||
* Second, we attempt to find [Window] for the [Activity] containing the [LocalView].
|
||||
*
|
||||
* If none of these are found (such as may happen in a preview), then the functionality of the
|
||||
* returned [SystemUiController] will be degraded, but won't throw an exception.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberSystemUiController(
|
||||
window: Window? = findWindow(),
|
||||
): SystemUiController {
|
||||
val view = LocalView.current
|
||||
return remember(view, window) { AndroidSystemUiController(view, window) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun findWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window
|
||||
?: LocalView.current.context.findWindow()
|
||||
|
||||
private tailrec fun Context.findWindow(): Window? = when (this) {
|
||||
is Activity -> window
|
||||
is ContextWrapper -> baseContext.findWindow()
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class for setting the navigation and status bar colors for a [View], gracefully
|
||||
* degrading behavior based upon API level.
|
||||
*
|
||||
* Typically you would use [rememberSystemUiController] to remember an instance of this.
|
||||
*/
|
||||
internal class AndroidSystemUiController(
|
||||
private val view: View,
|
||||
window: Window?,
|
||||
) : SystemUiController {
|
||||
private val windowInsetsController = window?.let {
|
||||
WindowCompat.getInsetsController(it, view)
|
||||
}
|
||||
|
||||
override var systemBarsBehavior: Int
|
||||
get() = windowInsetsController?.systemBarsBehavior ?: 0
|
||||
set(value) {
|
||||
windowInsetsController?.systemBarsBehavior = value
|
||||
}
|
||||
|
||||
override var isStatusBarVisible: Boolean
|
||||
get() {
|
||||
return ViewCompat.getRootWindowInsets(view)
|
||||
?.isVisible(WindowInsetsCompat.Type.statusBars()) == true
|
||||
}
|
||||
set(value) {
|
||||
if (value) {
|
||||
windowInsetsController?.show(WindowInsetsCompat.Type.statusBars())
|
||||
} else {
|
||||
windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars())
|
||||
}
|
||||
}
|
||||
|
||||
override var isNavigationBarVisible: Boolean
|
||||
get() {
|
||||
return ViewCompat.getRootWindowInsets(view)
|
||||
?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true
|
||||
}
|
||||
set(value) {
|
||||
if (value) {
|
||||
windowInsetsController?.show(WindowInsetsCompat.Type.navigationBars())
|
||||
} else {
|
||||
windowInsetsController?.hide(WindowInsetsCompat.Type.navigationBars())
|
||||
}
|
||||
}
|
||||
|
||||
override var statusBarDarkContentEnabled: Boolean
|
||||
get() = windowInsetsController?.isAppearanceLightStatusBars == true
|
||||
set(value) {
|
||||
windowInsetsController?.isAppearanceLightStatusBars = value
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import android.os.StrictMode
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.runtime.Composer
|
||||
import androidx.compose.runtime.tooling.ComposeStackTraceMode
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import coil3.EventListener
|
||||
@@ -37,9 +38,15 @@ import coil3.request.allowRgb565
|
||||
import coil3.request.crossfade
|
||||
import coil3.serviceLoaderEnabled
|
||||
import coil3.util.DebugLogger
|
||||
import com.ehviewer.core.database.SearchDatabase
|
||||
import com.ehviewer.core.files.deleteContent
|
||||
import com.ehviewer.core.ui.util.initSETConnection
|
||||
import com.ehviewer.core.util.isAtLeastO
|
||||
import com.ehviewer.core.util.isAtLeastP
|
||||
import com.ehviewer.core.util.isAtLeastS
|
||||
import com.ehviewer.core.util.isAtLeastSExtension7
|
||||
import com.ehviewer.core.util.launchIO
|
||||
import com.ehviewer.core.util.launchUI
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.withUIContext
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.coil.AnimatedWebPDecoder
|
||||
@@ -51,7 +58,6 @@ import com.hippo.ehviewer.coil.MapExtraInfoInterceptor
|
||||
import com.hippo.ehviewer.coil.MergeInterceptor
|
||||
import com.hippo.ehviewer.coil.QrCodeInterceptor
|
||||
import com.hippo.ehviewer.dailycheck.checkDawn
|
||||
import com.hippo.ehviewer.dao.SearchDatabase
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadsFilterMode
|
||||
import com.hippo.ehviewer.ktbuilder.diskCache
|
||||
@@ -68,12 +74,6 @@ import com.hippo.ehviewer.util.CrashHandler
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.OSUtils
|
||||
import com.hippo.ehviewer.util.isAtLeastO
|
||||
import com.hippo.ehviewer.util.isAtLeastP
|
||||
import com.hippo.ehviewer.util.isAtLeastS
|
||||
import com.hippo.ehviewer.util.isAtLeastSExtension7
|
||||
import com.hippo.files.deleteContent
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.okhttp.OkHttp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -89,13 +89,11 @@ import splitties.init.appCtx
|
||||
private val lifecycle = ProcessLifecycleOwner.get().lifecycle
|
||||
private val lifecycleScope = lifecycle.coroutineScope
|
||||
|
||||
class EhApplication :
|
||||
Application(),
|
||||
SingletonImageLoader.Factory {
|
||||
override fun onCreate() {
|
||||
class EhApplication : Application(), SingletonImageLoader.Factory {
|
||||
override fun onCreate() = with(lifecycleScope) {
|
||||
initSETConnection()
|
||||
// Initialize Settings on first access
|
||||
lifecycleScope.launchIO {
|
||||
launchIO {
|
||||
val mode = Settings.theme.value
|
||||
if (!isAtLeastS) {
|
||||
withUIContext {
|
||||
@@ -115,24 +113,25 @@ class EhApplication :
|
||||
CrashHandler.install()
|
||||
super.onCreate()
|
||||
System.loadLibrary("ehviewer")
|
||||
lifecycleScope.launchIO {
|
||||
launchUI {
|
||||
FavouriteStatusRouter.collect { info ->
|
||||
detailCache[info.gid]?.apply {
|
||||
favoriteSlot = info.favoriteSlot
|
||||
favoriteName = info.favoriteName
|
||||
favoriteNote = info.favoriteNote
|
||||
}
|
||||
launch {
|
||||
FavouriteStatusRouter.collect { info ->
|
||||
detailCache[info.gid]?.apply {
|
||||
favoriteSlot = info.favoriteSlot
|
||||
favoriteName = info.favoriteName
|
||||
favoriteNote = info.favoriteNote
|
||||
}
|
||||
}
|
||||
}
|
||||
launchIO {
|
||||
EhTagDatabase.launchUpdate()
|
||||
launch { EhDB }
|
||||
launchIO { dataStateFlow.value }
|
||||
launchIO { OSUtils.totalMemory }
|
||||
launch { dataStateFlow.value }
|
||||
launch { OSUtils.totalMemory }
|
||||
launch {
|
||||
if (DownloadManager.labelList.isNotEmpty() && Settings.downloadFilterMode !in Settings.snapshot()) {
|
||||
Settings.downloadFilterMode.value = DownloadsFilterMode.CUSTOM.flag
|
||||
}
|
||||
initialized = true
|
||||
DownloadManager.readMetadataFromLocal()
|
||||
}
|
||||
launch {
|
||||
@@ -146,7 +145,7 @@ class EhApplication :
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.enableDefaults()
|
||||
Composer.setDiagnosticStackTraceEnabled(true)
|
||||
Composer.setDiagnosticStackTraceMode(ComposeStackTraceMode.SourceInformation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +213,10 @@ class EhApplication :
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
var initialized = false
|
||||
private set
|
||||
|
||||
val ktorClient by lazy {
|
||||
if (isAtLeastSExtension7 && Settings.enableCronet.value) {
|
||||
HttpClient(Cronet) {
|
||||
|
||||
@@ -19,20 +19,22 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import arrow.fx.coroutines.resource
|
||||
import arrow.fx.coroutines.resourceScope
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadArtist
|
||||
import com.hippo.ehviewer.dao.DownloadDirname
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.DownloadLabel
|
||||
import com.hippo.ehviewer.dao.EhDatabase
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
import com.hippo.ehviewer.dao.HistoryInfo
|
||||
import com.hippo.ehviewer.dao.LocalFavoriteInfo
|
||||
import com.hippo.ehviewer.dao.ProgressInfo
|
||||
import com.hippo.ehviewer.dao.QuickSearch
|
||||
import com.hippo.ehviewer.dao.Schema17to18
|
||||
import com.ehviewer.core.data.model.asEntity
|
||||
import com.ehviewer.core.database.EhDatabase
|
||||
import com.ehviewer.core.database.Schema17to18
|
||||
import com.ehviewer.core.database.model.DownloadArtist
|
||||
import com.ehviewer.core.database.model.DownloadDirname
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.database.model.DownloadLabel
|
||||
import com.ehviewer.core.database.model.Filter
|
||||
import com.ehviewer.core.database.model.GalleryEntity
|
||||
import com.ehviewer.core.database.model.HistoryInfo
|
||||
import com.ehviewer.core.database.model.LocalFavoriteInfo
|
||||
import com.ehviewer.core.database.model.ProgressInfo
|
||||
import com.ehviewer.core.database.model.QuickSearch
|
||||
import com.ehviewer.core.files.sendTo
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import okio.Path
|
||||
import okio.Path.Companion.toOkioPath
|
||||
@@ -44,15 +46,15 @@ object EhDB {
|
||||
addMigrations(Schema17to18())
|
||||
}
|
||||
|
||||
suspend fun putGalleryInfo(galleryInfo: BaseGalleryInfo) {
|
||||
suspend fun putGalleryInfo(galleryInfo: GalleryEntity) {
|
||||
db.galleryDao().upsert(galleryInfo)
|
||||
}
|
||||
|
||||
private suspend fun deleteGalleryInfo(galleryInfo: BaseGalleryInfo) {
|
||||
private suspend fun deleteGalleryInfo(galleryInfo: GalleryEntity) {
|
||||
runCatching { db.galleryDao().delete(galleryInfo) }
|
||||
}
|
||||
|
||||
suspend fun updateGalleryInfo(galleryInfoList: List<BaseGalleryInfo>) {
|
||||
suspend fun updateGalleryInfo(galleryInfoList: List<GalleryEntity>) {
|
||||
db.galleryDao().update(galleryInfoList)
|
||||
}
|
||||
|
||||
@@ -156,12 +158,12 @@ object EhDB {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removeLocalFavorites(galleryInfo: BaseGalleryInfo) {
|
||||
suspend fun removeLocalFavorites(galleryInfo: GalleryInfo) {
|
||||
db.localFavoritesDao().deleteByKey(galleryInfo.gid)
|
||||
deleteGalleryInfo(galleryInfo)
|
||||
deleteGalleryInfo(galleryInfo.asEntity())
|
||||
}
|
||||
|
||||
suspend fun removeLocalFavorites(galleryInfoList: Collection<BaseGalleryInfo>) {
|
||||
suspend fun removeLocalFavorites(galleryInfoList: Collection<GalleryInfo>) {
|
||||
galleryInfoList.forEach {
|
||||
removeLocalFavorites(it)
|
||||
}
|
||||
@@ -172,12 +174,12 @@ object EhDB {
|
||||
return dao.contains(gid)
|
||||
}
|
||||
|
||||
suspend fun putLocalFavorites(galleryInfo: BaseGalleryInfo) {
|
||||
putGalleryInfo(galleryInfo)
|
||||
suspend fun putLocalFavorites(galleryInfo: GalleryInfo) {
|
||||
putGalleryInfo(galleryInfo.asEntity())
|
||||
db.localFavoritesDao().upsert(LocalFavoriteInfo(galleryInfo.gid))
|
||||
}
|
||||
|
||||
suspend fun putLocalFavorites(galleryInfoList: Collection<BaseGalleryInfo>) {
|
||||
suspend fun putLocalFavorites(galleryInfoList: Collection<GalleryInfo>) {
|
||||
galleryInfoList.forEach {
|
||||
putLocalFavorites(it)
|
||||
}
|
||||
@@ -223,8 +225,8 @@ object EhDB {
|
||||
|
||||
fun searchLocalFav(keyword: String) = db.localFavoritesDao().joinListLazy("*$keyword*")
|
||||
|
||||
suspend fun putHistoryInfo(galleryInfo: BaseGalleryInfo) {
|
||||
putGalleryInfo(galleryInfo)
|
||||
suspend fun putHistoryInfo(galleryInfo: GalleryInfo) {
|
||||
putGalleryInfo(galleryInfo.asEntity())
|
||||
db.historyDao().upsert(HistoryInfo(galleryInfo.gid))
|
||||
}
|
||||
|
||||
@@ -241,7 +243,7 @@ object EhDB {
|
||||
dao.insertOrIgnore(historyInfoList)
|
||||
}
|
||||
|
||||
suspend fun deleteHistoryInfo(galleryInfo: BaseGalleryInfo) {
|
||||
suspend fun deleteHistoryInfo(galleryInfo: GalleryEntity) {
|
||||
val dao = db.historyDao()
|
||||
dao.deleteByKey(galleryInfo.gid)
|
||||
deleteGalleryInfo(galleryInfo)
|
||||
|
||||
@@ -118,7 +118,7 @@ object Settings : DataStorePreferences(null) {
|
||||
val desktopSite = boolPref("desktop_site", true)
|
||||
|
||||
// About
|
||||
val backupBeforeUpdate = boolPref("backup_before_update", true)
|
||||
val backupBeforeUpdate = boolPref("backup_before_update", false)
|
||||
val useCIUpdateChannel = boolPref("ci_update_channel", AppConfig.isSnapshot)
|
||||
val updateIntervalDays = intPref("update_interval_days", 7)
|
||||
|
||||
@@ -158,8 +158,8 @@ object Settings : DataStorePreferences(null) {
|
||||
val colorFilterMode = intPref("color_filter_mode", 0)
|
||||
val customBrightness = boolPref("pref_custom_brightness_key", false)
|
||||
val customBrightnessValue = intPref("custom_brightness_value", 0)
|
||||
val readingMode = intPref("pref_default_reading_mode_key", ReadingModeType.LEFT_TO_RIGHT.flagValue)
|
||||
val orientationMode = intPref("pref_default_orientation_type_key", OrientationType.FREE.flagValue)
|
||||
val readingMode = intPref("pref_default_reading_mode_key", ReadingModeType.DEFAULT.prefValue)
|
||||
val orientationMode = intPref("pref_default_orientation_type_key", OrientationType.DEFAULT.prefValue)
|
||||
val showReaderSeekbar = boolPref("pref_show_reader_seekbar", true)
|
||||
val showPageNumber = boolPref("pref_show_page_number_key", true)
|
||||
val readerTheme = intPref("pref_reader_theme_key", 1)
|
||||
@@ -169,7 +169,8 @@ object Settings : DataStorePreferences(null) {
|
||||
val keepScreenOn = boolPref("pref_keep_screen_on_key", true)
|
||||
val readerLongTapAction = boolPref("reader_long_tap", true)
|
||||
val pageTransitions = boolPref("pref_enable_transitions_key", true)
|
||||
val readerReverseControls = boolPref("reader_volume_keys_inverted", false)
|
||||
val readWithVolumeKeys = boolPref("reader_volume_keys", false)
|
||||
val readWithVolumeKeysInverted = boolPref("reader_volume_keys_inverted", false)
|
||||
val grayScale = boolPref("pref_grayscale", false)
|
||||
val invertedColors = boolPref("pref_inverted_colors", false)
|
||||
val readerWebtoonNav = intPref("reader_navigation_mode_webtoon", 0)
|
||||
@@ -193,6 +194,10 @@ object Settings : DataStorePreferences(null) {
|
||||
if (pref[downloadFilterMode] == DownloadsFilterMode.ARTIST.flag && pref[recentDownloadLabel] == null) {
|
||||
pref[recentDownloadLabel] = ""
|
||||
}
|
||||
val orientation = pref[orientationMode]
|
||||
if (OrientationType.entries.none { it.prefValue == orientation }) {
|
||||
pref.remove(orientationMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import android.app.UiModeManager
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.ehviewer.core.preferences.PrefDelegate
|
||||
import com.ehviewer.core.util.isAtLeastS
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.dailycheck.updateDailyCheckWork
|
||||
import com.hippo.ehviewer.ui.keepNoMediaFileStatus
|
||||
import com.hippo.ehviewer.util.isAtLeastS
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
|
||||
// Normal Preview (v2): https://*.hath.network/c(m|1|2)/[timed token]/[gid]-[index].(jpg|webp)
|
||||
// ExHentai Large Preview (v1 Cover): https://s.exhentai.org/t/***
|
||||
|
||||
@@ -22,14 +22,19 @@ import arrow.core.right
|
||||
import arrow.fx.coroutines.parMap
|
||||
import arrow.fx.coroutines.parZip
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.fillInfo
|
||||
import com.hippo.ehviewer.client.data.filterComments
|
||||
import com.hippo.ehviewer.client.exception.CloudflareBypassException
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import com.hippo.ehviewer.client.exception.InsufficientFundsException
|
||||
import com.hippo.ehviewer.client.exception.InsufficientGpException
|
||||
import com.hippo.ehviewer.client.exception.IpBannedException
|
||||
import com.hippo.ehviewer.client.exception.NoHathClientException
|
||||
import com.hippo.ehviewer.client.exception.NoHitsFoundException
|
||||
import com.hippo.ehviewer.client.exception.NotLoggedInException
|
||||
@@ -41,6 +46,7 @@ import com.hippo.ehviewer.client.parser.FavoritesParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryApiParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryDetailParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryListParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryTokenApiParser
|
||||
import com.hippo.ehviewer.client.parser.HomeParser
|
||||
@@ -58,10 +64,11 @@ import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
import com.hippo.ehviewer.util.bodyAsUtf8Text
|
||||
import com.hippo.ehviewer.util.ensureSuccess
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.client.statement.HttpStatement
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.client.statement.request
|
||||
import io.ktor.http.Url
|
||||
import io.ktor.utils.io.pool.DirectByteBufferPool
|
||||
import io.ktor.utils.io.pool.useInstance
|
||||
import io.ktor.utils.io.readAvailable
|
||||
@@ -86,7 +93,9 @@ private const val MAX_REQUEST_SIZE = 25
|
||||
private const val MAX_SEQUENTIAL_REQUESTS = 5
|
||||
private const val REQUEST_INTERVAL = 5000L
|
||||
|
||||
fun Either<String, ByteBuffer>.saveParseError(e: Throwable) {
|
||||
private val Url.isLogin get() = segments.firstOrNull() == "bounce_login.php"
|
||||
|
||||
private fun Either<String, ByteBuffer>.saveParseError(e: Throwable) {
|
||||
val dir = AppConfig.externalParseErrorDir ?: return
|
||||
val file = File(dir, ReadableTime.getFilenamableTime() + ".txt")
|
||||
file.sink().buffer().use { sink ->
|
||||
@@ -98,7 +107,7 @@ fun Either<String, ByteBuffer>.saveParseError(e: Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
fun rethrowExactly(response: HttpResponse, body: Either<String, ByteBuffer>, e: Throwable): Nothing {
|
||||
private fun rethrowExactly(response: HttpResponse, body: Either<String, ByteBuffer>, e: Throwable): Nothing {
|
||||
// Don't translate coroutine cancellation
|
||||
if (e is CancellationException) throw e
|
||||
|
||||
@@ -110,16 +119,12 @@ fun rethrowExactly(response: HttpResponse, body: Either<String, ByteBuffer>, e:
|
||||
{ !it.hasRemaining() },
|
||||
)
|
||||
if (empty) {
|
||||
if (EhUtils.isExHentai) {
|
||||
throw EhException("Sad Panda\n(without panda)")
|
||||
val message = if (EhUtils.isExHentai) {
|
||||
"Sad Panda\n(without panda)"
|
||||
} else {
|
||||
throw EhException("IP banned")
|
||||
appCtx.getString(R.string.error_ip_banned)
|
||||
}
|
||||
}
|
||||
|
||||
// Check Gallery Not Available
|
||||
body.onLeft {
|
||||
if ("Gallery Not Available - " in it) throw e
|
||||
throw IpBannedException(message)
|
||||
}
|
||||
|
||||
// Check parse error thrown by rust
|
||||
@@ -131,7 +136,12 @@ fun rethrowExactly(response: HttpResponse, body: Either<String, ByteBuffer>, e:
|
||||
"2" -> throw NotLoggedInException()
|
||||
"3" -> throw NoHathClientException()
|
||||
"4" -> throw InsufficientFundsException()
|
||||
is String if message.startsWith('5') -> throw EhException(message.drop(1))
|
||||
is String if message.isNotEmpty() -> {
|
||||
when (message[0]) {
|
||||
'5' -> throw IpBannedException(message.substring(1))
|
||||
'6' -> throw EhException(message.substring(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +149,7 @@ fun rethrowExactly(response: HttpResponse, body: Either<String, ByteBuffer>, e:
|
||||
response.status.ensureSuccess()
|
||||
|
||||
if (e is ParseException || e is SerializationException) {
|
||||
body.onLeft { if ("<" !in it) throw EhException(it) }
|
||||
body.onLeft { if ("<" !in it) throw IpBannedException(it) }
|
||||
if (Settings.saveParseErrorBody.value) body.saveParseError(e)
|
||||
throw EhException(appCtx.getString(R.string.error_parse_error), e)
|
||||
}
|
||||
@@ -148,9 +158,10 @@ fun rethrowExactly(response: HttpResponse, body: Either<String, ByteBuffer>, e:
|
||||
throw e
|
||||
}
|
||||
|
||||
val httpContentPool = DirectByteBufferPool(8, 0x80000)
|
||||
private val httpContentPool = DirectByteBufferPool(8, 0x80000)
|
||||
|
||||
suspend inline fun <T> HttpStatement.fetchUsingAsText(crossinline block: suspend String.() -> T) = executeSafely { response ->
|
||||
private suspend inline fun <T> HttpStatement.fetchUsingAsText(crossinline block: suspend String.() -> T) = executeSafely { response ->
|
||||
if (response.request.url.isLogin) throw NotLoggedInException()
|
||||
val body = response.bodyAsUtf8Text()
|
||||
runSuspendCatching {
|
||||
block(body)
|
||||
@@ -159,7 +170,8 @@ suspend inline fun <T> HttpStatement.fetchUsingAsText(crossinline block: suspend
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
suspend inline fun <T> HttpStatement.fetchUsingAsByteBuffer(crossinline block: suspend ByteBuffer.() -> T) = executeSafely { response ->
|
||||
private suspend inline fun <T> HttpStatement.fetchUsingAsByteBuffer(crossinline block: suspend ByteBuffer.() -> T) = executeSafely { response ->
|
||||
if (response.request.url.isLogin) throw NotLoggedInException()
|
||||
httpContentPool.useInstance { buffer ->
|
||||
with(response.bodyAsChannel()) { while (!isClosedForRead) readAvailable(buffer) }
|
||||
buffer.flip()
|
||||
@@ -174,10 +186,12 @@ suspend inline fun <T> HttpStatement.fetchUsingAsByteBuffer(crossinline block: s
|
||||
|
||||
object EhEngine {
|
||||
suspend fun getOriginalImageUrl(url: String, referer: String?) = noRedirectEhRequest(url, referer).executeSafely { response ->
|
||||
val location = response.headers["Location"] ?: throw InsufficientFundsException()
|
||||
location.takeIf { "bounce_login" !in it } ?: throw NotLoggedInException()
|
||||
response.headers["Location"]?.takeUnless { Url(it).isLogin } ?: throw InsufficientGpException()
|
||||
}
|
||||
|
||||
suspend fun getPTokenListFromMpv(gid: Long, token: String) = ehRequest(EhUrl.getGalleryMultiPageViewerUrl(gid, token), EhUrl.referer)
|
||||
.fetchUsingAsText(GalleryMultiPageViewerPTokenParser::parse)
|
||||
|
||||
suspend fun getTorrentList(gid: Long, token: String): TorrentResult {
|
||||
val url = EhUrl.getTorrentUrl(gid, token)
|
||||
val referer = EhUrl.getGalleryDetailUrl(gid, token)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import arrow.core.memoize
|
||||
import com.ehviewer.core.database.model.Filter
|
||||
import com.ehviewer.core.database.model.FilterMode
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
import com.hippo.ehviewer.dao.FilterMode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.client
|
||||
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.EhApplication.Companion.ktorClient
|
||||
import com.hippo.ehviewer.EhApplication.Companion.noRedirectKtorClient
|
||||
import com.hippo.ehviewer.util.bodyAsUtf8Text
|
||||
import com.hippo.ehviewer.util.ensureSuccess
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.forms.FormDataContent
|
||||
import io.ktor.client.request.header
|
||||
|
||||
@@ -20,17 +20,17 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import arrow.core.Either
|
||||
import com.ehviewer.core.files.metadataOrNull
|
||||
import com.ehviewer.core.files.read
|
||||
import com.ehviewer.core.files.write
|
||||
import com.ehviewer.core.model.TagNamespace
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.EhApplication.Companion.ktorClient
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.bodyAsUtf8Text
|
||||
import com.hippo.ehviewer.util.ensureSuccess
|
||||
import com.hippo.ehviewer.util.utf8
|
||||
import com.hippo.files.metadataOrNull
|
||||
import com.hippo.files.read
|
||||
import com.hippo.files.write
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.client.request.prepareGet
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
@@ -27,10 +27,10 @@ import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.core.net.toUri
|
||||
import arrow.core.memoize
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.util.withUIContext
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.parser.Archive
|
||||
import com.hippo.ehviewer.spider.SpiderDen
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
interface AbstractDownloadInfo {
|
||||
var state: Int
|
||||
var legacy: Int
|
||||
var time: Long
|
||||
var label: String?
|
||||
var speed: Long
|
||||
var remaining: Long
|
||||
var finished: Int
|
||||
var downloaded: Int
|
||||
var total: Int
|
||||
}
|
||||
@@ -1,96 +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.client.data
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverter
|
||||
import androidx.room.TypeConverters
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@Entity(tableName = "GALLERIES")
|
||||
@TypeConverters(SimpleTagsConverter::class)
|
||||
data class BaseGalleryInfo(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "GID")
|
||||
override var gid: Long = 0,
|
||||
|
||||
@ColumnInfo(name = "TOKEN")
|
||||
override var token: String = "",
|
||||
|
||||
@ColumnInfo(name = "TITLE")
|
||||
override var title: String? = null,
|
||||
|
||||
@ColumnInfo(name = "TITLE_JPN")
|
||||
override var titleJpn: String? = null,
|
||||
|
||||
@ColumnInfo(name = "THUMB")
|
||||
override var thumbKey: String? = null,
|
||||
|
||||
@ColumnInfo(name = "CATEGORY")
|
||||
override var category: Int = 0,
|
||||
|
||||
@ColumnInfo(name = "POSTED")
|
||||
override var posted: String? = null,
|
||||
|
||||
@ColumnInfo(name = "UPLOADER")
|
||||
override var uploader: String? = null,
|
||||
|
||||
@Ignore
|
||||
override var disowned: Boolean = false,
|
||||
|
||||
@ColumnInfo(name = "RATING")
|
||||
override var rating: Float = 0f,
|
||||
|
||||
@Ignore
|
||||
override var rated: Boolean = false,
|
||||
|
||||
@ColumnInfo(name = "SIMPLE_TAGS")
|
||||
override var simpleTags: List<String>? = null,
|
||||
|
||||
@ColumnInfo(name = "PAGES", defaultValue = "0")
|
||||
override var pages: Int = 0,
|
||||
|
||||
@Ignore
|
||||
override var thumbWidth: Int = 0,
|
||||
|
||||
@Ignore
|
||||
override var thumbHeight: Int = 0,
|
||||
|
||||
@ColumnInfo(name = "SIMPLE_LANGUAGE")
|
||||
override var simpleLanguage: String? = null,
|
||||
|
||||
@ColumnInfo(name = "FAVORITE_SLOT")
|
||||
override var favoriteSlot: Int = NOT_FAVORITED,
|
||||
|
||||
@Ignore
|
||||
override var favoriteName: String? = null,
|
||||
|
||||
@Ignore
|
||||
override var favoriteNote: String? = null,
|
||||
) : GalleryInfo
|
||||
|
||||
class SimpleTagsConverter {
|
||||
@TypeConverter
|
||||
fun fromString(value: String) = value.split(", ")
|
||||
|
||||
@TypeConverter
|
||||
fun toString(value: List<String>) = value.joinToString()
|
||||
}
|
||||
@@ -25,9 +25,9 @@ import kotlinx.serialization.Serializable
|
||||
data class FavListUrlBuilder(
|
||||
val favCat: Int = FAV_CAT_ALL,
|
||||
val keyword: String? = null,
|
||||
var jumpTo: String? = null,
|
||||
var prev: String? = null,
|
||||
var next: String? = null,
|
||||
private var jumpTo: String? = null,
|
||||
private var prev: String? = null,
|
||||
private var next: String? = null,
|
||||
) {
|
||||
val isLocal
|
||||
get() = favCat == FAV_CAT_LOCAL
|
||||
@@ -35,6 +35,7 @@ data class FavListUrlBuilder(
|
||||
fun setIndex(index: String?, isNext: Boolean) {
|
||||
next = index.takeIf { isNext }
|
||||
prev = index.takeUnless { isNext }
|
||||
jumpTo = null
|
||||
}
|
||||
|
||||
fun build() = ehUrl(EhUrl.FAV_PATH) {
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import androidx.compose.ui.util.fastFlatMap
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryTagGroup
|
||||
import com.ehviewer.core.model.PowerStatus
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhFilter
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
private val LANGUAGES = arrayOf(
|
||||
"English",
|
||||
@@ -38,45 +41,27 @@ private val LANGUAGES = arrayOf(
|
||||
"Dutch",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GalleryDetail(
|
||||
val galleryInfo: BaseGalleryInfo = BaseGalleryInfo(),
|
||||
var apiUid: Long = -1L,
|
||||
var apiKey: String? = null,
|
||||
var torrentCount: Int = 0,
|
||||
var parent: String? = null,
|
||||
var newerVersions: List<BaseGalleryInfo> = emptyList(),
|
||||
var visible: String? = null,
|
||||
var language: String? = null,
|
||||
var size: String? = null,
|
||||
var favoriteCount: Int = 0,
|
||||
var ratingCount: Int = 0,
|
||||
val tagGroups: List<GalleryTagGroup>,
|
||||
var comments: GalleryCommentList,
|
||||
val previewList: List<GalleryPreview>,
|
||||
) : GalleryInfo by galleryInfo {
|
||||
suspend fun fillInfo() {
|
||||
val index = LANGUAGES.indexOf(language)
|
||||
if (index != -1) simpleLanguage = GalleryInfo.S_LANGS[index]
|
||||
simpleTags = tagGroups.fastFlatMap(GalleryTagGroup::tags).map { (text, power, _) ->
|
||||
if (power == PowerStatus.Weak) "_$text" else text
|
||||
}
|
||||
if (favoriteSlot == GalleryInfo.NOT_FAVORITED && EhDB.containLocalFavorites(gid)) {
|
||||
favoriteSlot = GalleryInfo.LOCAL_FAVORITED
|
||||
}
|
||||
suspend fun GalleryDetail.fillInfo() {
|
||||
val index = LANGUAGES.indexOf(language)
|
||||
if (index != -1) simpleLanguage = GalleryInfo.S_LANGS[index]
|
||||
simpleTags = tagGroups.fastFlatMap(GalleryTagGroup::tags).map { (text, power, _) ->
|
||||
if (power == PowerStatus.Weak) "_$text" else text
|
||||
}
|
||||
if (favoriteSlot == GalleryInfo.NOT_FAVORITED && EhDB.containLocalFavorites(gid)) {
|
||||
favoriteSlot = GalleryInfo.LOCAL_FAVORITED
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun filterComments() {
|
||||
comments = with(comments) {
|
||||
val scoreThreshold = Settings.commentThreshold.value
|
||||
copy(
|
||||
comments = comments.filter {
|
||||
it.uploader ||
|
||||
it.score > scoreThreshold &&
|
||||
!EhFilter.filterCommenter(it.user.orEmpty()) &&
|
||||
!EhFilter.filterComment(it.comment)
|
||||
},
|
||||
)
|
||||
}
|
||||
suspend fun GalleryDetail.filterComments() {
|
||||
comments = with(comments) {
|
||||
val scoreThreshold = Settings.commentThreshold.value
|
||||
copy(
|
||||
comments = comments.filter {
|
||||
it.uploader ||
|
||||
it.score > scoreThreshold &&
|
||||
!EhFilter.filterCommenter(it.user.orEmpty()) &&
|
||||
!EhFilter.filterComment(it.comment)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -15,25 +15,24 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import com.ehviewer.core.database.model.QuickSearch
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.addQueryParameter
|
||||
import com.hippo.ehviewer.client.addQueryParameterIfNotBlank
|
||||
import com.hippo.ehviewer.client.ehUrl
|
||||
import com.hippo.ehviewer.dao.QuickSearch
|
||||
import com.hippo.ehviewer.ui.main.AdvanceTable
|
||||
import io.ktor.http.Parameters
|
||||
import kotlin.text.toIntOrNull
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ListUrlBuilder(
|
||||
var mode: Int = MODE_NORMAL,
|
||||
private var prev: String? = null,
|
||||
var next: String? = null,
|
||||
// Reset to null after initial loading
|
||||
var jumpTo: String? = null,
|
||||
private var next: String? = null,
|
||||
private var jumpTo: String? = null,
|
||||
private var range: Int = 0,
|
||||
var category: Int = EhUtils.NONE,
|
||||
private var mKeyword: String? = null,
|
||||
@@ -49,11 +48,14 @@ data class ListUrlBuilder(
|
||||
next = index.takeIf { isNext }
|
||||
prev = index.takeUnless { isNext }
|
||||
range = 0
|
||||
jumpTo = null
|
||||
}
|
||||
|
||||
fun setJumpTo(to: Int) {
|
||||
jumpTo = to.takeUnless { it == 0 }?.toString()
|
||||
}
|
||||
var page: Int
|
||||
get() = (jumpTo?.toInt() ?: 0) + 1
|
||||
set(value) {
|
||||
jumpTo = (value - 1).takeUnless { it == 0 }?.toString()
|
||||
}
|
||||
|
||||
fun setRange(to: Int) {
|
||||
range = to
|
||||
@@ -62,6 +64,11 @@ data class ListUrlBuilder(
|
||||
jumpTo = null
|
||||
}
|
||||
|
||||
fun setSeek(date: String) {
|
||||
setIndex("2", true)
|
||||
jumpTo = date
|
||||
}
|
||||
|
||||
var keyword: String?
|
||||
get() = if (MODE_UPLOADER == mode) "uploader:$mKeyword" else mKeyword
|
||||
set(keyword) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.hippo.ehviewer.client.exception
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.ehviewer.core.i18n.R
|
||||
|
||||
sealed class FatalException : EhException {
|
||||
constructor(message: String) : super(message)
|
||||
constructor(@StringRes messageId: Int) : super(messageId)
|
||||
}
|
||||
|
||||
class IpBannedException(message: String) : FatalException(message)
|
||||
class InsufficientGpException : FatalException(R.string.insufficient_gp)
|
||||
class QuotaExceededException : FatalException(R.string.error_509)
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.hippo.ehviewer.client.exception
|
||||
|
||||
import com.ehviewer.core.i18n.R
|
||||
|
||||
class QuotaExceededException : EhException(R.string.error_509)
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.ehviewer.core.util.unescapeXml
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import com.hippo.ehviewer.util.unescapeXml
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.util.unescapeXml
|
||||
import com.hippo.ehviewer.client.EhUtils.getCategory
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.getThumbKey
|
||||
import com.hippo.ehviewer.client.parseAs
|
||||
import com.hippo.ehviewer.util.unescapeXml
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ package com.hippo.ehviewer.client.parser
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.hippo.ehviewer.client.data.GalleryCommentList
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryPreviewList
|
||||
import com.ehviewer.core.model.GalleryCommentList
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryPreviewList
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.ehviewer.core.util.unescapeXml
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import com.hippo.ehviewer.util.unescapeXml
|
||||
|
||||
object GalleryPageParser {
|
||||
private val PATTERN_IMAGE_URL = Regex("<img[^>]*src=\"([^\"]+)\" style")
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.hippo.ehviewer.util.toEpochMillis
|
||||
import com.hippo.ehviewer.util.toIntOrDefault
|
||||
import com.hippo.ehviewer.util.toLocalDateTime
|
||||
import com.ehviewer.core.util.toEpochMillis
|
||||
import com.ehviewer.core.util.toIntOrDefault
|
||||
import com.ehviewer.core.util.toLocalDateTime
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.format.char
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ package com.hippo.ehviewer.client.parser
|
||||
|
||||
import arrow.core.Either.Companion.catch
|
||||
import arrow.core.getOrElse
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup
|
||||
import com.ehviewer.core.model.GalleryTagGroup
|
||||
import com.hippo.ehviewer.client.exception.ParseException
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.SystemClock
|
||||
import androidx.core.graphics.createBitmap
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import com.ehviewer.core.util.logcat
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@@ -12,9 +12,9 @@ import coil3.asImage
|
||||
import coil3.intercept.Interceptor
|
||||
import coil3.request.ImageResult
|
||||
import coil3.request.SuccessResult
|
||||
import com.ehviewer.core.util.isAtLeastQ
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.image.copyBitmapToAHB
|
||||
import com.hippo.ehviewer.util.isAtLeastQ
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
|
||||
@@ -6,16 +6,16 @@ import coil3.intercept.Interceptor
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.ImageResult
|
||||
import coil3.request.SuccessResult
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.files.delete
|
||||
import com.ehviewer.core.files.isDirectory
|
||||
import com.ehviewer.core.files.isFile
|
||||
import com.ehviewer.core.files.sendTo
|
||||
import com.ehviewer.core.files.toUri
|
||||
import com.hippo.ehviewer.EhApplication.Companion.imageCache
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.client.getThumbKey
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.downloadLocation
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.files.delete
|
||||
import com.hippo.files.isDirectory
|
||||
import com.hippo.files.isFile
|
||||
import com.hippo.files.toUri
|
||||
|
||||
private val downloadInfoKey = Extras.Key<DownloadInfo?>(default = null)
|
||||
|
||||
@@ -35,7 +35,7 @@ object DownloadThumbInterceptor : Interceptor {
|
||||
info.thumbKey = thumbKey
|
||||
EhDB.putGalleryInfo(info.galleryInfo)
|
||||
}
|
||||
val dir = downloadLocation / info.dirname
|
||||
val dir = downloadLocation / info.dirname!!
|
||||
val format = thumbKey.substringAfterLast('.', "")
|
||||
check(format.isNotBlank())
|
||||
val thumb = dir / "thumb.$format"
|
||||
|
||||
@@ -7,10 +7,13 @@ import coil3.decode.BlackholeDecoder
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.size.SizeResolver
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryPreview
|
||||
import com.ehviewer.core.model.V2GalleryPreview
|
||||
import com.hippo.ehviewer.client.getThumbKey
|
||||
import com.hippo.ehviewer.client.getV2PreviewKey
|
||||
import com.hippo.ehviewer.client.thumbUrl
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.ktbuilder.execute
|
||||
|
||||
@@ -30,7 +33,7 @@ fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply {
|
||||
// Load in original size so the memory cache can be reused for preload requests
|
||||
fun ImageRequest.Builder.ehPreview(preview: GalleryPreview) = apply {
|
||||
with(preview) {
|
||||
val key = imageKey
|
||||
val key = if (this is V2GalleryPreview) getV2PreviewKey(url) else getThumbKey(url)
|
||||
data(url)
|
||||
size(SizeResolver.ORIGINAL)
|
||||
memoryCacheKey(key)
|
||||
|
||||
@@ -19,11 +19,11 @@ import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Clock
|
||||
|
||||
@@ -22,18 +22,29 @@ import androidx.compose.runtime.DisallowComposableCalls
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.util.size
|
||||
import arrow.fx.coroutines.parMapNotNull
|
||||
import com.ehviewer.core.data.model.asEntity
|
||||
import com.ehviewer.core.database.model.DownloadArtist
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.database.model.DownloadLabel
|
||||
import com.ehviewer.core.files.delete
|
||||
import com.ehviewer.core.files.find
|
||||
import com.ehviewer.core.files.toOkioPath
|
||||
import com.ehviewer.core.files.toUri
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.preferences.edit
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.mapNotNull
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadArtist
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.DownloadLabel
|
||||
import com.hippo.ehviewer.client.exception.FatalException
|
||||
import com.hippo.ehviewer.spider.COMIC_INFO_FILE
|
||||
import com.hippo.ehviewer.spider.SpeedTracker
|
||||
import com.hippo.ehviewer.spider.SpiderQueen
|
||||
@@ -45,13 +56,7 @@ import com.hippo.ehviewer.spider.readCompatFromPath
|
||||
import com.hippo.ehviewer.spider.toSimpleTags
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.insertWith
|
||||
import com.hippo.ehviewer.util.mapNotNull
|
||||
import com.hippo.ehviewer.util.runAssertingNotMainThread
|
||||
import com.hippo.files.delete
|
||||
import com.hippo.files.find
|
||||
import com.hippo.files.toOkioPath
|
||||
import com.hippo.files.toUri
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -70,9 +75,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
override val coroutineContext = Dispatchers.IO + Job()
|
||||
|
||||
// All download info list
|
||||
private val allInfoList = runAssertingNotMainThread {
|
||||
(EhDB.getAllDownloadInfo() as MutableList).apply { sortWith(comparator()) }
|
||||
}
|
||||
private val allInfoList = runAssertingNotMainThread { EhDB.getAllDownloadInfo() as MutableList }
|
||||
|
||||
val downloadInfoList: List<DownloadInfo>
|
||||
get() = allInfoList
|
||||
@@ -97,6 +100,21 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
|
||||
private val mutex = Mutex()
|
||||
|
||||
private val sortMutex = Mutex()
|
||||
private val sortMode
|
||||
get() = SortMode.from(Settings.downloadSortMode.value)
|
||||
|
||||
var isInitialized by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
init {
|
||||
launch {
|
||||
val mode = sortMode
|
||||
if (mode != SortMode.Default) sortDownloads(mode)
|
||||
isInitialized = true
|
||||
}
|
||||
}
|
||||
|
||||
fun containLabel(label: String?): Boolean {
|
||||
if (label == null) {
|
||||
return false
|
||||
@@ -178,11 +196,13 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
} else {
|
||||
// It is new download info
|
||||
info = DownloadInfo(galleryInfo, galleryInfo.downloadDirname())
|
||||
info = DownloadInfo(galleryInfo.asEntity(), galleryInfo.downloadDirname())
|
||||
info.label = label
|
||||
info.state = DownloadInfo.STATE_WAIT
|
||||
// Add to all download list and map
|
||||
allInfoList.insertWith(info, comparator())
|
||||
sortMutex.withLock {
|
||||
allInfoList.insertWith(info, sortMode.comparator())
|
||||
}
|
||||
mAllInfoMap[galleryInfo.gid] = info
|
||||
|
||||
// Add to wait list
|
||||
@@ -197,7 +217,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
ensureDownload()
|
||||
|
||||
// Add it to history
|
||||
EhDB.putHistoryInfo(info.galleryInfo)
|
||||
EhDB.putHistoryInfo(info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,10 +259,12 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}.collectAsState(transform(info)).value
|
||||
|
||||
suspend fun startAllDownload() {
|
||||
val updateList = allInfoList.filter {
|
||||
when (it.state) {
|
||||
DownloadInfo.STATE_NONE, DownloadInfo.STATE_FAILED -> true
|
||||
else -> false
|
||||
val updateList = sortMutex.withLock {
|
||||
allInfoList.filter {
|
||||
when (it.state) {
|
||||
DownloadInfo.STATE_NONE, DownloadInfo.STATE_FAILED -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updateList.isNotEmpty()) {
|
||||
@@ -256,8 +278,8 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addDownload(downloadInfoList: List<DownloadInfo>) {
|
||||
val comparator = comparator()
|
||||
suspend fun addDownload(downloadInfoList: List<DownloadInfo>) = sortMutex.withLock {
|
||||
val comparator = sortMode.comparator()
|
||||
downloadInfoList.forEach { info ->
|
||||
if (containDownloadInfo(info.gid)) return@forEach
|
||||
|
||||
@@ -287,11 +309,13 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
|
||||
suspend fun restoreDownload(galleryInfo: BaseGalleryInfo, dirname: String) {
|
||||
val info = DownloadInfo(galleryInfo, dirname)
|
||||
val info = DownloadInfo(galleryInfo.asEntity(), dirname)
|
||||
info.state = DownloadInfo.STATE_NONE
|
||||
|
||||
// Add to all download list and map
|
||||
allInfoList.insertWith(info, comparator())
|
||||
sortMutex.withLock {
|
||||
allInfoList.insertWith(info, sortMode.comparator())
|
||||
}
|
||||
mAllInfoMap[galleryInfo.gid] = info
|
||||
|
||||
// Save to
|
||||
@@ -350,7 +374,9 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
EhDB.removeDownloadInfo(info)
|
||||
|
||||
// Remove all list and map
|
||||
allInfoList.remove(info)
|
||||
sortMutex.withLock {
|
||||
allInfoList.remove(info)
|
||||
}
|
||||
mAllInfoMap.remove(info.gid)
|
||||
|
||||
// Update listener
|
||||
@@ -373,7 +399,9 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
mAllInfoMap.remove(gid)
|
||||
}
|
||||
EhDB.removeDownloadInfo(list)
|
||||
allInfoList.removeAll(list.toSet())
|
||||
sortMutex.withLock {
|
||||
allInfoList.removeAll(list.toSet())
|
||||
}
|
||||
|
||||
// Update listener
|
||||
list.forEach { mutableNotifyFlow.emit(it) }
|
||||
@@ -382,7 +410,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
ensureDownload()
|
||||
}
|
||||
|
||||
fun sortDownloads(mode: SortMode) {
|
||||
suspend fun sortDownloads(mode: SortMode) = sortMutex.withLock {
|
||||
allInfoList.sortWith(mode.comparator())
|
||||
}
|
||||
|
||||
@@ -501,9 +529,11 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
val new = exist.copy(label = to)
|
||||
labelList.add(index, new)
|
||||
EhDB.updateDownloadLabel(new)
|
||||
allInfoList.forEach {
|
||||
if (it.label == from) {
|
||||
it.label = to
|
||||
sortMutex.withLock {
|
||||
allInfoList.forEach {
|
||||
if (it.label == from) {
|
||||
it.label = to
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -519,21 +549,25 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
removeAt(index)
|
||||
}
|
||||
allInfoList.forEach {
|
||||
if (it.label == label) {
|
||||
it.label = null
|
||||
sortMutex.withLock {
|
||||
allInfoList.forEach {
|
||||
if (it.label == label) {
|
||||
it.label = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun readMetadataFromLocal() {
|
||||
val list = allInfoList.mapNotNull {
|
||||
val updateGallery = it.pages == 0 || it.simpleTags == null
|
||||
val updateArtist = it.artistInfoList.isEmpty()
|
||||
if (updateGallery || updateArtist) {
|
||||
Triple(it, updateGallery, updateArtist)
|
||||
} else {
|
||||
null
|
||||
val list = sortMutex.withLock {
|
||||
allInfoList.mapNotNull {
|
||||
val updateGallery = it.pages == 0 || it.simpleTags == null
|
||||
val updateArtist = it.artistInfoList.isEmpty()
|
||||
if (updateGallery || updateArtist) {
|
||||
Triple(it, updateGallery, updateArtist)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}.parMapNotNull(concurrency = 5) { (info, updateGallery, updateArtist) ->
|
||||
info.downloadDir?.run {
|
||||
@@ -574,13 +608,14 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
val galleryInfoList = mutableListOf<BaseGalleryInfo>()
|
||||
list.forEach { (info, artists) ->
|
||||
info?.let {
|
||||
galleryInfoList.add(it)
|
||||
}
|
||||
artists?.let { (gid, updateList) ->
|
||||
EhDB.putDownloadArtist(gid, updateList)
|
||||
val galleryInfoList = buildList {
|
||||
list.forEach { (info, artists) ->
|
||||
info?.let {
|
||||
add(it)
|
||||
}
|
||||
artists?.let { (gid, updateList) ->
|
||||
EhDB.putDownloadArtist(gid, updateList)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (galleryInfoList.isNotEmpty()) EhDB.updateGalleryInfo(galleryInfoList)
|
||||
@@ -598,9 +633,9 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGet509(index: Int) {
|
||||
override fun onFatal(error: FatalException) {
|
||||
launch {
|
||||
mDownloadListener?.onGet509()
|
||||
mDownloadListener?.onFatal(error)
|
||||
stopAllDownload()
|
||||
}
|
||||
}
|
||||
@@ -677,9 +712,9 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
|
||||
interface DownloadListener {
|
||||
/**
|
||||
* Get 509 error
|
||||
* Fatal error
|
||||
*/
|
||||
fun onGet509()
|
||||
fun onFatal(error: FatalException)
|
||||
|
||||
/**
|
||||
* Start download
|
||||
@@ -782,8 +817,6 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
|
||||
private const val TAG = "DownloadManager"
|
||||
|
||||
private fun comparator() = SortMode.from(Settings.downloadSortMode.value).comparator()
|
||||
}
|
||||
|
||||
var downloadLocation: Path
|
||||
|
||||
@@ -31,16 +31,20 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.savedstate.serialization.decodeFromSavedState
|
||||
import androidx.savedstate.serialization.encodeToSavedState
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.util.isAtLeastS
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.unsafeLazy
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.client.exception.FatalException
|
||||
import com.hippo.ehviewer.client.exception.InsufficientGpException
|
||||
import com.hippo.ehviewer.client.exception.IpBannedException
|
||||
import com.hippo.ehviewer.client.exception.QuotaExceededException
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
import com.hippo.ehviewer.util.isAtLeastS
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -61,7 +65,7 @@ class DownloadService :
|
||||
private val notifyManager by unsafeLazy { NotificationManagerCompat.from(this) }
|
||||
private val downloadingNotification by unsafeLazy { initDownloadingNotification() }
|
||||
private val downloadedNotification by lazy { initDownloadedNotification() }
|
||||
private val error509Notification by lazy { init509Notification() }
|
||||
private val fatalNotification by lazy { initFatalNotification() }
|
||||
private val channelId by unsafeLazy { "$packageName.download" }
|
||||
|
||||
override fun onCreate() {
|
||||
@@ -172,24 +176,27 @@ class DownloadService :
|
||||
.apply { launch { run() } }
|
||||
}
|
||||
|
||||
private fun init509Notification(): NotificationHandler {
|
||||
private fun initFatalNotification(): NotificationHandler {
|
||||
val builder = NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setSmallIcon(com.hippo.ehviewer.R.drawable.ic_baseline_warning_24)
|
||||
.setContentTitle(getString(R.string.stat_509_alert_title))
|
||||
.setContentText(getString(R.string.stat_509_alert_text))
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle().bigText(getString(R.string.stat_509_alert_text)),
|
||||
)
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
return NotificationHandler(this, notifyManager, builder, ID_509)
|
||||
return NotificationHandler(this, notifyManager, builder, ID_FATAL)
|
||||
.apply { launch { run() } }
|
||||
}
|
||||
|
||||
override fun onGet509() {
|
||||
error509Notification.run {
|
||||
override fun onFatal(error: FatalException) {
|
||||
val (title, text) = when (error) {
|
||||
is IpBannedException -> R.string.error_ip_banned to error.message
|
||||
is InsufficientGpException -> R.string.insufficient_funds to error.message
|
||||
is QuotaExceededException -> R.string.stat_509_alert_title to getString(R.string.stat_509_alert_text)
|
||||
}
|
||||
fatalNotification.run {
|
||||
builder.setWhen(System.currentTimeMillis())
|
||||
.setContentTitle(getString(title))
|
||||
.setContentText(text)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
|
||||
show()
|
||||
}
|
||||
}
|
||||
@@ -412,7 +419,7 @@ class DownloadService :
|
||||
const val ACTION_CLEAR_DOWNLOAD_SERVICE = "clear_download_service"
|
||||
private const val ID_DOWNLOADING = 1
|
||||
private const val ID_DOWNLOADED = 2
|
||||
private const val ID_509 = 3
|
||||
private const val ID_FATAL = 3
|
||||
private const val DELAY = 1000L // 1s
|
||||
private val sItemStateArray = LongSparseArray<Boolean>()
|
||||
private val sItemTitleArray = LongSparseArray<String>()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.hippo.ehviewer.download
|
||||
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.artists
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.database.model.artists
|
||||
|
||||
enum class DownloadsFilterMode(
|
||||
val flag: Int,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.hippo.ehviewer.download
|
||||
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
|
||||
data class SortMode(val field: Field, val order: Order, val groupByDownloadLabel: Boolean = false) {
|
||||
val flag
|
||||
@@ -11,7 +11,6 @@ data class SortMode(val field: Field, val order: Order, val groupByDownloadLabel
|
||||
GID(0),
|
||||
TITLE(1),
|
||||
TIME(2),
|
||||
LABEL(3),
|
||||
PAGES(4),
|
||||
}
|
||||
|
||||
@@ -23,13 +22,16 @@ data class SortMode(val field: Field, val order: Order, val groupByDownloadLabel
|
||||
fun comparator(): Comparator<DownloadInfo> {
|
||||
var comparator = when (field) {
|
||||
Field.GID -> compareBy { it.gid }
|
||||
Field.TITLE, Field.LABEL -> compareBy { EhUtils.getSuitableTitle(it) }
|
||||
Field.TITLE -> compareBy { EhUtils.getSuitableTitle(it) }
|
||||
Field.TIME -> compareBy { it.time }
|
||||
Field.PAGES -> compareBy<DownloadInfo> { it.pages }.thenBy { EhUtils.getSuitableTitle(it) }
|
||||
Field.PAGES -> compareBy<DownloadInfo> { it.pages }
|
||||
}
|
||||
if (order == Order.DESC) {
|
||||
comparator = comparator.reversed()
|
||||
}
|
||||
if (field == Field.PAGES) {
|
||||
comparator = comparator.thenByDescending { it.time }
|
||||
}
|
||||
if (groupByDownloadLabel) {
|
||||
comparator = compareBy<DownloadInfo> { it.label }.then(comparator)
|
||||
}
|
||||
@@ -38,15 +40,16 @@ data class SortMode(val field: Field, val order: Order, val groupByDownloadLabel
|
||||
|
||||
companion object {
|
||||
fun from(flag: Int): SortMode {
|
||||
val field = flag and 0xF shr 1
|
||||
val order = flag and 0x1
|
||||
val fieldValue = flag and 0xF shr 1
|
||||
val orderValue = flag and 0x1
|
||||
val groupByDownloadLabel = flag shr 4 == 1
|
||||
require(field in Field.entries.indices && order in Order.entries.indices)
|
||||
return SortMode(Field.entries[field], Order.entries[order], groupByDownloadLabel)
|
||||
val field = Field.entries.find { it.value == fieldValue } ?: Default.field
|
||||
val order = Order.entries.find { it.value == orderValue } ?: Default.order
|
||||
return SortMode(field, order, groupByDownloadLabel)
|
||||
}
|
||||
val Default = SortMode(Field.TIME, Order.DESC)
|
||||
val All = listOf(
|
||||
SortMode(Field.TIME, Order.DESC),
|
||||
Default,
|
||||
SortMode(Field.TIME, Order.ASC),
|
||||
SortMode(Field.GID, Order.DESC),
|
||||
SortMode(Field.GID, Order.ASC),
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
package com.hippo.ehviewer.gallery
|
||||
|
||||
import arrow.autoCloseScope
|
||||
import com.ehviewer.core.files.openFileDescriptor
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.Settings.archivePasswds
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.image.ImageSource
|
||||
import com.hippo.ehviewer.image.byteBufferSource
|
||||
import com.hippo.ehviewer.jni.closeArchive
|
||||
@@ -29,8 +33,6 @@ import com.hippo.ehviewer.jni.providePassword
|
||||
import com.hippo.ehviewer.jni.releaseByteBuffer
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.displayName
|
||||
import com.hippo.files.openFileDescriptor
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import moe.tarsin.kt.install
|
||||
import okio.Path
|
||||
@@ -40,7 +42,7 @@ typealias PasswdProvider = suspend (PasswdInvalidator) -> String
|
||||
|
||||
suspend inline fun <T> useArchivePageLoader(
|
||||
file: Path,
|
||||
gid: Long = 0,
|
||||
info: GalleryInfo? = null,
|
||||
startPage: Int = 0,
|
||||
hasAds: Boolean = false,
|
||||
crossinline passwdProvider: PasswdProvider,
|
||||
@@ -49,7 +51,7 @@ suspend inline fun <T> useArchivePageLoader(
|
||||
coroutineScope {
|
||||
val pfd = install(file.openFileDescriptor("r"))
|
||||
val size = install(
|
||||
{ openArchive(pfd.fd, pfd.statSize, gid == 0L || file.name.endsWith(".zip")) },
|
||||
{ openArchive(pfd.fd, pfd.statSize, info == null || file.name.endsWith(".zip")) },
|
||||
{ _, _ -> closeArchive() },
|
||||
)
|
||||
check(size > 0) { "Archive have no content!" }
|
||||
@@ -57,8 +59,14 @@ suspend inline fun <T> useArchivePageLoader(
|
||||
archivePasswds += passwdProvider(::providePassword)
|
||||
}
|
||||
val loader = install(
|
||||
object : PageLoader(this, gid, startPage, size, hasAds) {
|
||||
override val title by lazy { FileUtils.getNameFromFilename(file.displayName)!! }
|
||||
object : PageLoader(this, info, startPage, size, hasAds) {
|
||||
override val title by lazy {
|
||||
if (info != null) {
|
||||
EhUtils.getSuitableTitle(info)
|
||||
} else {
|
||||
FileUtils.getNameFromFilename(file.displayName)!!
|
||||
}
|
||||
}
|
||||
|
||||
override fun getImageExtension(index: Int) = getExtension(index)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.hippo.ehviewer.gallery
|
||||
|
||||
import arrow.autoCloseScope
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.spider.SpiderQueen
|
||||
import com.hippo.ehviewer.spider.SpiderQueen.Companion.obtainSpiderQueen
|
||||
import com.hippo.ehviewer.spider.SpiderQueen.Companion.releaseSpiderQueen
|
||||
@@ -24,7 +24,7 @@ suspend inline fun <T> useEhPageLoader(
|
||||
)
|
||||
queen.awaitReady()
|
||||
val loader = install(
|
||||
object : PageLoader(this, info.gid, startPage, queen.size, info.hasAds) {
|
||||
object : PageLoader(this, info, startPage, queen.size, info.hasAds) {
|
||||
override val title by lazy { EhUtils.getSuitableTitle(info) }
|
||||
|
||||
override fun getImageExtension(index: Int) = queen.getExtension(index)
|
||||
|
||||
@@ -4,6 +4,8 @@ import androidx.collection.SieveCache
|
||||
import androidx.collection.mutableIntObjectMapOf
|
||||
import arrow.fx.coroutines.ExitCase
|
||||
import arrow.fx.coroutines.bracketCase
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.util.isAtLeastO
|
||||
import com.ehviewer.core.util.withNonCancellableContext
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
@@ -13,7 +15,6 @@ import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.OSUtils
|
||||
import com.hippo.ehviewer.util.detectAds
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.hippo.ehviewer.util.isAtLeastO
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock
|
||||
import kotlin.concurrent.atomics.AtomicInt
|
||||
import kotlin.concurrent.read
|
||||
@@ -35,7 +36,7 @@ private val progressScope = CoroutineScope(Dispatchers.IO)
|
||||
private const val MAX_CACHE_SIZE = 512 * 1024 * 1024
|
||||
private const val MIN_CACHE_SIZE = 256 * 1024 * 1024
|
||||
|
||||
abstract class PageLoader(val scope: CoroutineScope, val gid: Long, startPage: Int, val size: Int, val hasAds: Boolean = false) : AutoCloseable {
|
||||
abstract class PageLoader(val scope: CoroutineScope, val info: GalleryInfo?, startPage: Int, val size: Int, val hasAds: Boolean = false) : AutoCloseable {
|
||||
var startPage = startPage.coerceIn(0, size - 1)
|
||||
|
||||
private val jobs = mutableIntObjectMapOf<Job>()
|
||||
@@ -111,14 +112,14 @@ abstract class PageLoader(val scope: CoroutineScope, val gid: Long, startPage: I
|
||||
|
||||
override fun close() {
|
||||
lock.write { cache.evictAll() }
|
||||
if (gid != 0L) {
|
||||
info?.run {
|
||||
progressScope.launch {
|
||||
EhDB.putReadProgress(gid, startPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract val title: String
|
||||
abstract val title: String
|
||||
|
||||
protected abstract fun getImageExtension(index: Int): String?
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ import coil3.size.Dimension
|
||||
import coil3.size.Precision
|
||||
import coil3.size.Size
|
||||
import coil3.size.SizeResolver
|
||||
import com.ehviewer.core.files.openFileDescriptor
|
||||
import com.ehviewer.core.files.toUri
|
||||
import com.ehviewer.core.util.isAtLeastP
|
||||
import com.ehviewer.core.util.isAtLeastU
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.coil.AnimatedWebPDrawable
|
||||
import com.hippo.ehviewer.coil.BitmapImageWithExtraInfo
|
||||
@@ -48,10 +52,6 @@ import com.hippo.ehviewer.jni.munmap
|
||||
import com.hippo.ehviewer.jni.rewriteGifSource
|
||||
import com.hippo.ehviewer.ktbuilder.execute
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
import com.hippo.ehviewer.util.isAtLeastP
|
||||
import com.hippo.ehviewer.util.isAtLeastU
|
||||
import com.hippo.files.openFileDescriptor
|
||||
import com.hippo.files.toUri
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.concurrent.atomics.AtomicInt
|
||||
import kotlin.concurrent.atomics.decrementAndFetch
|
||||
|
||||
@@ -5,8 +5,8 @@ import coil3.ImageLoader
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.imageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryPreview
|
||||
import com.hippo.ehviewer.coil.ehPreview
|
||||
import com.hippo.ehviewer.coil.ehUrl
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.hippo.ehviewer.ktor
|
||||
|
||||
import com.hippo.ehviewer.util.isAtLeastQ
|
||||
import com.ehviewer.core.util.isAtLeastQ
|
||||
import io.ktor.client.engine.okhttp.OkHttpConfig
|
||||
import okhttp3.AsyncDns
|
||||
import okhttp3.android.AndroidAsyncDns
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.hippo.ehviewer.legacy
|
||||
|
||||
import com.ehviewer.core.files.read
|
||||
import com.hippo.ehviewer.spider.SpiderInfo
|
||||
import com.hippo.files.read
|
||||
import kotlinx.io.readLineStrict
|
||||
import okio.Path
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
package com.hippo.ehviewer.spider
|
||||
|
||||
import com.ehviewer.core.database.util.SimpleTagsConverter
|
||||
import com.ehviewer.core.files.read
|
||||
import com.ehviewer.core.files.write
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryTag
|
||||
import com.ehviewer.core.model.PowerStatus
|
||||
import com.ehviewer.core.model.TagNamespace
|
||||
import com.ehviewer.core.model.TagNamespace.Artist
|
||||
import com.ehviewer.core.model.TagNamespace.Character
|
||||
import com.ehviewer.core.model.TagNamespace.Cosplayer
|
||||
import com.ehviewer.core.model.TagNamespace.Female
|
||||
import com.ehviewer.core.model.TagNamespace.Group
|
||||
import com.ehviewer.core.model.TagNamespace.Location
|
||||
import com.ehviewer.core.model.TagNamespace.Male
|
||||
import com.ehviewer.core.model.TagNamespace.Mixed
|
||||
import com.ehviewer.core.model.TagNamespace.Other
|
||||
import com.ehviewer.core.model.TagNamespace.Parody
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryTag
|
||||
import com.hippo.ehviewer.client.data.PowerStatus
|
||||
import com.hippo.ehviewer.client.data.SimpleTagsConverter
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Artist
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Character
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Cosplayer
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Female
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Group
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Location
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Male
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Mixed
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Other
|
||||
import com.hippo.ehviewer.client.data.TagNamespace.Parody
|
||||
import com.hippo.files.read
|
||||
import com.hippo.files.write
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -20,18 +20,28 @@ import arrow.fx.coroutines.autoCloseable
|
||||
import arrow.fx.coroutines.closeable
|
||||
import arrow.fx.coroutines.parMap
|
||||
import arrow.fx.coroutines.resourceScope
|
||||
import com.ehviewer.core.database.model.DownloadArtist
|
||||
import com.ehviewer.core.files.delete
|
||||
import com.ehviewer.core.files.exists
|
||||
import com.ehviewer.core.files.find
|
||||
import com.ehviewer.core.files.isDirectory
|
||||
import com.ehviewer.core.files.list
|
||||
import com.ehviewer.core.files.mkdirs
|
||||
import com.ehviewer.core.files.moveTo
|
||||
import com.ehviewer.core.files.openFileDescriptor
|
||||
import com.ehviewer.core.files.sendTo
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.EhApplication.Companion.imageCache as sCache
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhUtils.getSuitableTitle
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.ehRequest
|
||||
import com.hippo.ehviewer.client.getImageKey
|
||||
import com.hippo.ehviewer.coil.read
|
||||
import com.hippo.ehviewer.coil.suspendEdit
|
||||
import com.hippo.ehviewer.dao.DownloadArtist
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.downloadLocation
|
||||
import com.hippo.ehviewer.download.tempDownloadDir
|
||||
@@ -39,17 +49,7 @@ import com.hippo.ehviewer.image.PathSource
|
||||
import com.hippo.ehviewer.jni.archiveFdBatch
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.copyTo
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.ehviewer.util.sha1
|
||||
import com.hippo.files.delete
|
||||
import com.hippo.files.exists
|
||||
import com.hippo.files.find
|
||||
import com.hippo.files.isDirectory
|
||||
import com.hippo.files.list
|
||||
import com.hippo.files.mkdirs
|
||||
import com.hippo.files.moveTo
|
||||
import com.hippo.files.openFileDescriptor
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.client.statement.bodyAsChannel
|
||||
import io.ktor.client.statement.request
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.hippo.ehviewer.spider
|
||||
|
||||
import com.ehviewer.core.files.read
|
||||
import com.ehviewer.core.files.write
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.coil.edit
|
||||
import com.hippo.ehviewer.coil.read
|
||||
import com.hippo.ehviewer.ktbuilder.diskCache
|
||||
import com.hippo.ehviewer.legacy.readLegacySpiderInfo
|
||||
import com.hippo.files.read
|
||||
import com.hippo.files.write
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.io.readByteArray
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.cbor.Cbor
|
||||
|
||||
@@ -20,23 +20,20 @@ import androidx.annotation.IntDef
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.partially1
|
||||
import com.ehviewer.core.files.find
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryPreview
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUrl.getGalleryDetailUrl
|
||||
import com.hippo.ehviewer.client.EhUrl.getGalleryMultiPageViewerUrl
|
||||
import com.hippo.ehviewer.client.EhUrl.referer
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
||||
import com.hippo.ehviewer.client.ehRequest
|
||||
import com.hippo.ehviewer.client.exception.FatalException
|
||||
import com.hippo.ehviewer.client.exception.QuotaExceededException
|
||||
import com.hippo.ehviewer.client.fetchUsingAsText
|
||||
import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.hippo.files.find
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlin.concurrent.atomics.AtomicInt
|
||||
import kotlin.concurrent.atomics.decrementAndFetch
|
||||
import kotlin.concurrent.atomics.incrementAndFetch
|
||||
@@ -91,9 +88,9 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyGet509(index: Int) {
|
||||
fun notifyFatal(error: FatalException) {
|
||||
synchronized(mSpiderListeners) {
|
||||
mSpiderListeners.forEach { it.onGet509(index) }
|
||||
mSpiderListeners.forEach { it.onFatal(error) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +328,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
}
|
||||
|
||||
private suspend fun readSpiderInfoFromInternet() = EhEngine.getPreviewList(
|
||||
getGalleryDetailUrl(galleryInfo.gid, galleryInfo.token, 0, false),
|
||||
getGalleryDetailUrl(galleryInfo.gid, galleryInfo.token),
|
||||
).run {
|
||||
val spiderInfo = SpiderInfo(galleryInfo.gid, galleryInfo.token, total)
|
||||
readPreviews(previews, 0, spiderInfo)
|
||||
@@ -342,17 +339,11 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
|
||||
suspend fun getPTokenFromMultiPageViewer(index: Int): String? {
|
||||
if (!isMpvAvailable) return null
|
||||
val url = getGalleryMultiPageViewerUrl(
|
||||
galleryInfo.gid,
|
||||
galleryInfo.token,
|
||||
)
|
||||
return runSuspendCatching {
|
||||
ehRequest(url, referer).fetchUsingAsText {
|
||||
GalleryMultiPageViewerPTokenParser.parse(this).forEachIndexed { index, s ->
|
||||
spiderInfo.pTokenMap[index] = s
|
||||
}
|
||||
spiderInfo.pTokenMap[index]
|
||||
EhEngine.getPTokenListFromMpv(galleryInfo.gid, galleryInfo.token).forEachIndexed { index, s ->
|
||||
spiderInfo.pTokenMap[index] = s
|
||||
}
|
||||
spiderInfo.pTokenMap[index]
|
||||
}.getOrElse {
|
||||
logcat(it)
|
||||
null
|
||||
@@ -365,12 +356,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val url = getGalleryDetailUrl(
|
||||
galleryInfo.gid,
|
||||
galleryInfo.token,
|
||||
previewIndex,
|
||||
false,
|
||||
)
|
||||
val url = getGalleryDetailUrl(galleryInfo.gid, galleryInfo.token, previewIndex)
|
||||
return runSuspendCatching {
|
||||
EhEngine.getPreviewList(url).run {
|
||||
readPreviews(previews, previewIndex, spiderInfo)
|
||||
@@ -428,7 +414,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
annotation class State
|
||||
interface OnSpiderListener {
|
||||
fun onGetPages(pages: Int) {}
|
||||
fun onGet509(index: Int) {}
|
||||
fun onFatal(error: FatalException) {}
|
||||
fun onPageDownload(index: Int, contentLength: Long, receivedSize: Long, bytesRead: Int) {}
|
||||
fun onPageSuccess(index: Int, finished: Int, downloaded: Int, total: Int) {}
|
||||
fun onPageFailure(index: Int, error: String?, finished: Int, downloaded: Int, total: Int) {}
|
||||
@@ -555,7 +541,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
if (!force && index in spiderDen) {
|
||||
return updatePageState(index, STATE_FINISHED)
|
||||
}
|
||||
pToken = getPToken(index) ?: return updatePageState(index, STATE_FAILED, PTOKEN_FAILED_MESSAGE)
|
||||
pToken = getPToken(index) ?: return updatePageState(index, STATE_FAILED, pTokenFailedMessage)
|
||||
previousPToken = getPToken(index - 1)
|
||||
|
||||
// The lock for delay should be acquired before anything else to maintain FIFO order
|
||||
@@ -650,10 +636,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
when (it) {
|
||||
is QuotaExceededException -> notifyGet509(index)
|
||||
// TODO: Check IP ban
|
||||
}
|
||||
if (it is FatalException) notifyFatal(it)
|
||||
error = it.displayString()
|
||||
if (error == "Invalid page.") {
|
||||
pTokenLock.withLock {
|
||||
@@ -664,13 +647,13 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
updatePageState(index, STATE_FAILED, error)
|
||||
}
|
||||
}
|
||||
private val pTokenFailedMessage = appCtx.getString(R.string.error_get_ptoken_error)
|
||||
}
|
||||
|
||||
private val PTOKEN_FAILED_MESSAGE = appCtx.getString(R.string.error_get_ptoken_error)
|
||||
private val URL_509_PATTERN = Regex("\\.org/.+/509s?\\.gif")
|
||||
private val Url509Regex = Regex("https://(?:ehgt\\.org/|exhentai\\.org/im)g/509s?\\.gif")
|
||||
private const val FORCE_RETRY = "Force retry"
|
||||
private const val WORKER_DEBUG_TAG = "SpiderQueenWorker"
|
||||
|
||||
private fun check509(url: String) {
|
||||
if (URL_509_PATTERN in url) throw QuotaExceededException()
|
||||
if (Url509Regex.matches(url)) throw QuotaExceededException()
|
||||
}
|
||||
@@ -35,19 +35,27 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.files.delete
|
||||
import com.ehviewer.core.files.exists
|
||||
import com.ehviewer.core.files.isDirectory
|
||||
import com.ehviewer.core.files.write
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryInfo.Companion.LOCAL_FAVORITED
|
||||
import com.ehviewer.core.model.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import com.ehviewer.core.ui.component.LabeledCheckbox
|
||||
import com.ehviewer.core.util.isAtLeastT
|
||||
import com.ehviewer.core.util.mapToLongArray
|
||||
import com.ehviewer.core.util.toEpochMillis
|
||||
import com.ehviewer.core.util.toLocalDateTime
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
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.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadService
|
||||
import com.hippo.ehviewer.download.downloadDir
|
||||
@@ -65,16 +73,8 @@ import com.hippo.ehviewer.ui.tools.awaitSelectItemWithIcon
|
||||
import com.hippo.ehviewer.ui.tools.awaitSelectItemWithIconAndTextField
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.util.bgWork
|
||||
import com.hippo.ehviewer.util.isAtLeastT
|
||||
import com.hippo.ehviewer.util.mapToLongArray
|
||||
import com.hippo.ehviewer.util.requestPermission
|
||||
import com.hippo.ehviewer.util.restartApplication
|
||||
import com.hippo.ehviewer.util.toEpochMillis
|
||||
import com.hippo.ehviewer.util.toLocalDateTime
|
||||
import com.hippo.files.delete
|
||||
import com.hippo.files.exists
|
||||
import com.hippo.files.isDirectory
|
||||
import com.hippo.files.write
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlin.time.Clock
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -177,7 +177,7 @@ suspend fun startDownload(forceDefault: Boolean, vararg galleryInfos: BaseGaller
|
||||
}
|
||||
|
||||
context(_: DialogState)
|
||||
suspend fun modifyFavorites(galleryInfo: BaseGalleryInfo): Boolean {
|
||||
suspend fun modifyFavorites(galleryInfo: GalleryInfo): Boolean {
|
||||
val localFavorited = EhDB.containLocalFavorites(galleryInfo.gid)
|
||||
if (Settings.hasSignedIn.value) {
|
||||
val isFavorited = galleryInfo.favoriteSlot != NOT_FAVORITED
|
||||
@@ -215,7 +215,7 @@ suspend fun modifyFavorites(galleryInfo: BaseGalleryInfo): Boolean {
|
||||
}
|
||||
|
||||
private suspend fun doModifyFavorites(
|
||||
galleryInfo: BaseGalleryInfo,
|
||||
galleryInfo: GalleryInfo,
|
||||
slot: Int = NOT_FAVORITED,
|
||||
localFavorited: Boolean = true,
|
||||
note: String = "",
|
||||
@@ -263,7 +263,7 @@ private suspend fun doModifyFavorites(
|
||||
add
|
||||
}
|
||||
|
||||
suspend fun removeFromFavorites(galleryInfo: BaseGalleryInfo) = doModifyFavorites(
|
||||
suspend fun removeFromFavorites(galleryInfo: GalleryInfo) = doModifyFavorites(
|
||||
galleryInfo = galleryInfo,
|
||||
localFavorited = EhDB.containLocalFavorites(galleryInfo.gid),
|
||||
)
|
||||
@@ -419,7 +419,7 @@ suspend fun showMoveDownloadLabelList(list: Collection<DownloadInfo>): String? {
|
||||
}
|
||||
|
||||
context(_: DialogState)
|
||||
suspend fun awaitSelectDate(): String? {
|
||||
suspend fun awaitSelectDate(): String {
|
||||
val initial = LocalDate(2007, 3, 21)
|
||||
val yesterday = Clock.System.todayIn(TimeZone.UTC).minus(1, DateTimeUnit.DAY)
|
||||
val initialMillis = initial.toEpochMillis()
|
||||
@@ -432,8 +432,7 @@ suspend fun awaitSelectDate(): String? {
|
||||
override fun isSelectableDate(utcTimeMillis: Long): Boolean = utcTimeMillis in dateRange
|
||||
},
|
||||
)
|
||||
val date = dateMillis?.run { toLocalDateTime().date.toString() }
|
||||
return date
|
||||
return dateMillis.toLocalDateTime().date.toString()
|
||||
}
|
||||
|
||||
context(_: Context, _: DialogState)
|
||||
|
||||
@@ -20,8 +20,8 @@ import android.view.View
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.navigation.ActivityNavigator
|
||||
import com.ehviewer.core.util.isAtLeastR
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.util.isAtLeastR
|
||||
import eu.kanade.tachiyomi.util.view.setSecureScreen
|
||||
|
||||
abstract class EhActivity : AppCompatActivity() {
|
||||
|
||||
@@ -25,11 +25,11 @@ import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryInfo.Companion.LOCAL_FAVORITED
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
|
||||
import com.hippo.ehviewer.client.thumbUrl
|
||||
import com.hippo.ehviewer.ui.screen.navWithUrl
|
||||
import com.hippo.ehviewer.util.addTextToClipboard
|
||||
|
||||
@@ -102,6 +102,8 @@ import androidx.core.net.toUri
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.ehviewer.core.files.isDirectory
|
||||
import com.ehviewer.core.files.toOkioPath
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.ui.component.LabeledCheckbox
|
||||
import com.ehviewer.core.ui.component.LocalSideSheetState
|
||||
@@ -110,7 +112,10 @@ import com.ehviewer.core.ui.icons.EhIcons
|
||||
import com.ehviewer.core.ui.icons.filled.Subscriptions
|
||||
import com.ehviewer.core.ui.util.LocalSnackBarFabPadding
|
||||
import com.ehviewer.core.ui.util.LocalWindowSizeClass
|
||||
import com.ehviewer.core.util.isAtLeastQ
|
||||
import com.ehviewer.core.util.isAtLeastS
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.hippo.ehviewer.EhApplication.Companion.initialized
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
|
||||
@@ -143,11 +148,7 @@ import com.hippo.ehviewer.util.calculateFraction
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.hippo.ehviewer.util.getParcelableExtraCompat
|
||||
import com.hippo.ehviewer.util.getUrlFromClipboard
|
||||
import com.hippo.ehviewer.util.isAtLeastQ
|
||||
import com.hippo.ehviewer.util.isAtLeastS
|
||||
import com.hippo.ehviewer.util.sha1
|
||||
import com.hippo.files.isDirectory
|
||||
import com.hippo.files.toOkioPath
|
||||
import com.ramcosta.composedestinations.DestinationsNavHost
|
||||
import com.ramcosta.composedestinations.spec.Direction
|
||||
import com.ramcosta.composedestinations.utils.currentDestinationAsState
|
||||
@@ -200,7 +201,7 @@ class MainActivity : EhActivity() {
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
installSplashScreen().setKeepOnScreenCondition { !initialized }
|
||||
super.onCreate(savedInstanceState)
|
||||
setMD3Content {
|
||||
val configuration = LocalConfiguration.current
|
||||
|
||||
@@ -14,10 +14,10 @@ import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import com.ehviewer.core.ui.util.ProvideVectorPainterCache
|
||||
import com.hippo.ehviewer.ui.theme.EhTheme
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.LocalGlobalDialogState
|
||||
import com.hippo.ehviewer.ui.tools.ProvideVectorPainterCache
|
||||
import com.ramcosta.composedestinations.animations.NavHostAnimatedDestinationStyle
|
||||
import me.zhanghai.compose.preference.ProvidePreferenceTheme
|
||||
import me.zhanghai.compose.preference.preferenceTheme
|
||||
|
||||
@@ -21,7 +21,7 @@ import android.widget.Toast
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.core.net.toUri
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import coil3.decode.DecodeUtils
|
||||
import coil3.imageLoader
|
||||
import coil3.request.crossfade
|
||||
import coil3.size.Scale
|
||||
import com.ehviewer.core.util.toIntOrDefault
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
import com.hippo.ehviewer.util.toIntOrDefault
|
||||
import kotlin.math.roundToInt
|
||||
import splitties.init.appCtx
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class LinkifyTextView(
|
||||
val x = event.x + scrollX - totalPaddingLeft
|
||||
val y = event.y + scrollY - totalPaddingTop
|
||||
val line = layout.getLineForVertical(y.toInt())
|
||||
if (x > layout.getLineMax(line)) return@let
|
||||
val off = layout.getOffsetForHorizontal(line, x)
|
||||
val spans = text.getSpans<URLSpan>(off, off)
|
||||
if (spans.isNotEmpty()) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.hippo.ehviewer.ui.login
|
||||
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -60,6 +60,7 @@ import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.ui.util.LocalWindowSizeClass
|
||||
import com.ehviewer.core.ui.util.ifTrueThen
|
||||
import com.ehviewer.core.ui.util.isExpanded
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.launchIO
|
||||
@@ -73,7 +74,6 @@ import com.hippo.ehviewer.ui.destinations.WebViewSignInScreenDestination
|
||||
import com.hippo.ehviewer.ui.openBrowser
|
||||
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.jamal.composeprefs3.ui.ifTrueThen
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
@@ -39,15 +39,15 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.ui.component.CrystalCard
|
||||
import com.ehviewer.core.ui.component.GalleryListCardRating
|
||||
import com.ehviewer.core.ui.util.TransitionsVisibilityScope
|
||||
import com.ehviewer.core.ui.util.listThumbGenerator
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.ui.tools.GalleryListCardRating
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -18,11 +18,11 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import coil3.compose.AsyncImagePainter
|
||||
import coil3.compose.rememberAsyncImagePainter
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.ui.util.SETNodeGenerator
|
||||
import com.ehviewer.core.ui.util.SharedElementBox
|
||||
import com.ehviewer.core.ui.util.TransitionsVisibilityScope
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
import com.hippo.ehviewer.ui.tools.shouldCrop
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.text.parseAsHtml
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.hippo.ehviewer.client.data.GalleryComment
|
||||
import com.ehviewer.core.model.GalleryComment
|
||||
import com.hippo.ehviewer.ui.legacy.CoilImageGetter
|
||||
import com.hippo.ehviewer.ui.legacy.LinkifyTextView
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
|
||||
@@ -33,13 +33,13 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.ui.icons.EhIcons
|
||||
import com.ehviewer.core.ui.icons.big.SadAndroid
|
||||
import com.ehviewer.core.ui.util.TransitionsVisibilityScope
|
||||
import com.ehviewer.core.ui.util.detailThumbGenerator
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
|
||||
@Composable
|
||||
fun GalleryDetailHeaderInfoCard(
|
||||
|
||||
@@ -38,17 +38,17 @@ import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import com.ehviewer.core.ui.component.CrystalCard
|
||||
import com.ehviewer.core.ui.component.ElevatedCard
|
||||
import com.ehviewer.core.ui.component.GalleryListCardRating
|
||||
import com.ehviewer.core.ui.util.SharedElementBox
|
||||
import com.ehviewer.core.ui.util.TransitionsVisibilityScope
|
||||
import com.ehviewer.core.ui.util.listThumbGenerator
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.ui.tools.GalleryListCardRating
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -32,7 +32,6 @@ import androidx.compose.material3.pulltorefresh.pullToRefresh
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -45,20 +44,19 @@ import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.itemContentType
|
||||
import androidx.paging.compose.itemKey
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.ui.component.FastScrollLazyVerticalGrid
|
||||
import com.ehviewer.core.ui.component.FastScrollLazyVerticalStaggeredGrid
|
||||
import com.ehviewer.core.ui.icons.EhIcons
|
||||
import com.ehviewer.core.ui.icons.big.SadAndroid
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.exception.NoHitsFoundException
|
||||
import com.hippo.ehviewer.coil.PrefetchAround
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
@@ -69,15 +67,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
@Stable
|
||||
operator fun PaddingValues.plus(r: PaddingValues) = object : PaddingValues {
|
||||
val l = this@plus
|
||||
override fun calculateBottomPadding() = l.calculateBottomPadding() + r.calculateBottomPadding()
|
||||
override fun calculateLeftPadding(layoutDirection: LayoutDirection) = l.calculateLeftPadding(layoutDirection) + r.calculateLeftPadding(layoutDirection)
|
||||
override fun calculateRightPadding(layoutDirection: LayoutDirection) = l.calculateRightPadding(layoutDirection) + r.calculateRightPadding(layoutDirection)
|
||||
override fun calculateTopPadding() = l.calculateTopPadding() + r.calculateTopPadding()
|
||||
}
|
||||
|
||||
@Composable
|
||||
context(_: CoroutineScope, _: Context)
|
||||
fun GalleryList(
|
||||
|
||||
@@ -23,9 +23,9 @@ import androidx.compose.ui.unit.IntSize
|
||||
import coil3.BitmapImage
|
||||
import coil3.compose.AsyncImagePainter.State
|
||||
import coil3.compose.rememberAsyncImagePainter
|
||||
import com.ehviewer.core.model.GalleryPreview
|
||||
import com.ehviewer.core.model.V2GalleryPreview
|
||||
import com.ehviewer.core.ui.component.CrystalCard
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
||||
import com.hippo.ehviewer.client.data.V2GalleryPreview
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
import com.hippo.ehviewer.ui.tools.shouldCrop
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
@@ -19,14 +20,15 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.ehviewer.core.model.GalleryTagGroup
|
||||
import com.ehviewer.core.model.PowerStatus
|
||||
import com.ehviewer.core.model.TagNamespace
|
||||
import com.ehviewer.core.model.VoteStatus
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup
|
||||
import com.hippo.ehviewer.client.data.PowerStatus
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.client.data.VoteStatus
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.ui.tools.includeFontPadding
|
||||
|
||||
@@ -59,6 +61,7 @@ fun GalleryTags(
|
||||
BaseRoundText(
|
||||
text = translation,
|
||||
weak = power == PowerStatus.Weak,
|
||||
solid = power == PowerStatus.Solid && showVote,
|
||||
modifier = Modifier.combinedClickable(
|
||||
onClick = { onTagClick(tag) },
|
||||
onLongClick = {
|
||||
@@ -88,6 +91,7 @@ private fun BaseRoundText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
weak: Boolean = false,
|
||||
solid: Boolean = false,
|
||||
isGroup: Boolean = false,
|
||||
) {
|
||||
val bgColor = if (isGroup) {
|
||||
@@ -103,8 +107,9 @@ private fun BaseRoundText(
|
||||
Text(
|
||||
text = text,
|
||||
modifier = modifier.padding(horizontal = 12.dp, vertical = 4.dp).width(IntrinsicSize.Max),
|
||||
color = MaterialTheme.colorScheme.onSurface.let { if (weak) it.copy(0.5F) else it },
|
||||
color = LocalContentColor.current.let { if (weak) it.copy(0.5F) else it },
|
||||
style = MaterialTheme.typography.labelLarge.includeFontPadding,
|
||||
textDecoration = if (solid) TextDecoration.Underline else null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,17 +34,17 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import arrow.core.raise.ensure
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.GalleryInfo.Companion.S_LANG_TAGS
|
||||
import com.ehviewer.core.ui.component.DropdownFilterChip
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.toIntOrDefault
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.S_LANG_TAGS
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.awaitResult
|
||||
import com.hippo.ehviewer.util.toIntOrDefault
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
private val categoryTable = arrayOf(
|
||||
|
||||
@@ -88,7 +88,7 @@ fun ColorFilterSetting() = Column(modifier = Modifier.verticalScroll(rememberScr
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_color_filter_mode),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.color_filter_modes),
|
||||
values = arrayOf("0", "1", "2", "3", "4", "5"),
|
||||
values = listOf(0, 1, 2, 3, 4, 5),
|
||||
field = Settings.colorFilterMode.asMutableState(),
|
||||
)
|
||||
SwitchChoice(
|
||||
|
||||
@@ -8,15 +8,17 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
|
||||
fun Modifier.keyEventHandler(
|
||||
enabled: () -> Boolean,
|
||||
reverse: () -> Boolean,
|
||||
volumeKeysEnabled: () -> Boolean,
|
||||
volumeKeysInverted: () -> Boolean,
|
||||
movePrevious: () -> Unit,
|
||||
moveNext: () -> Unit,
|
||||
) = onPreviewKeyEvent {
|
||||
if (enabled() && it.type == KeyEventType.KeyDown) {
|
||||
if (it.type == KeyEventType.KeyDown) {
|
||||
when (it.key) {
|
||||
Key.DirectionUp, Key.PageUp, Key.VolumeUp -> if (reverse()) moveNext() else movePrevious()
|
||||
Key.DirectionDown, Key.PageDown, Key.VolumeDown -> if (reverse()) movePrevious() else moveNext()
|
||||
Key.DirectionUp, Key.PageUp -> movePrevious()
|
||||
Key.DirectionDown, Key.PageDown -> moveNext()
|
||||
Key.VolumeUp if volumeKeysEnabled() -> if (volumeKeysInverted()) moveNext() else movePrevious()
|
||||
Key.VolumeDown if volumeKeysEnabled() -> if (volumeKeysInverted()) movePrevious() else moveNext()
|
||||
else -> return@onPreviewKeyEvent false
|
||||
}
|
||||
true
|
||||
|
||||
@@ -13,21 +13,21 @@ import android.webkit.MimeTypeMap
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.core.content.FileProvider
|
||||
import com.ehviewer.core.files.toOkioPath
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.util.isAtLeastQ
|
||||
import com.ehviewer.core.util.isAtLeastT
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.BuildConfig.APPLICATION_ID
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.gallery.Page
|
||||
import com.hippo.ehviewer.gallery.PageLoader
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.awaitActivityResult
|
||||
import com.hippo.ehviewer.util.displayPath
|
||||
import com.hippo.ehviewer.util.isAtLeastQ
|
||||
import com.hippo.ehviewer.util.isAtLeastT
|
||||
import com.hippo.ehviewer.util.requestPermission
|
||||
import com.hippo.files.toOkioPath
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import java.io.File
|
||||
import kotlin.time.Clock
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
@@ -39,6 +39,7 @@ import coil3.compose.SubcomposeAsyncImage
|
||||
import coil3.compose.SubcomposeAsyncImageContent
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.unreachable
|
||||
import com.google.accompanist.drawablepainter.DrawablePainter
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
@@ -52,7 +53,6 @@ import com.hippo.ehviewer.util.AdsPlaceholderFile
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.CombinedCircularProgressIndicator
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import moe.tarsin.kt.unreachable
|
||||
|
||||
@Composable
|
||||
fun PagerItem(
|
||||
@@ -109,11 +109,11 @@ fun PagerItem(
|
||||
// DrawablePainter <: RememberObserver
|
||||
painter = remember(painter) { painter },
|
||||
contentDescription = null,
|
||||
modifier = contentModifier.fillMaxSize().thenIf(drawable is Animatable) {
|
||||
modifier = Modifier.thenIf(drawable is Animatable) {
|
||||
onVisibilityChanged(minDurationMs = 33, minFractionVisible = 0.5f) {
|
||||
drawable!!.setVisible(it, false)
|
||||
}
|
||||
},
|
||||
}.then(contentModifier).fillMaxSize(),
|
||||
contentScale = contentScale,
|
||||
colorFilter = when {
|
||||
grayScale && invert -> grayScaleAndInvertFilter
|
||||
|
||||
@@ -42,10 +42,14 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import me.saket.telephoto.zoomable.OverzoomEffect
|
||||
import me.saket.telephoto.zoomable.Viewport
|
||||
import me.saket.telephoto.zoomable.ZoomLimit
|
||||
import me.saket.telephoto.zoomable.ZoomSpec
|
||||
import me.saket.telephoto.zoomable.ZoomableContentLocation
|
||||
import me.saket.telephoto.zoomable.ZoomableState
|
||||
import me.saket.telephoto.zoomable.rememberZoomableState
|
||||
import me.saket.telephoto.zoomable.spatial.CoordinateSpace
|
||||
import me.saket.telephoto.zoomable.zoomable
|
||||
|
||||
@Composable
|
||||
@@ -171,7 +175,9 @@ private fun PageContainer(
|
||||
val zoomFraction = snapshotFlow { zoomableState.zoomFraction }.first { it != null }
|
||||
if (zoomFraction == 0f) {
|
||||
delay(500)
|
||||
val contentSize = zoomableState.transformedContentBounds.size
|
||||
val contentSize = with(zoomableState.coordinateSystem) {
|
||||
unscaledContentBounds(false).sizeIn(CoordinateSpace.Viewport)
|
||||
}
|
||||
val scale = ContentScale.FillHeight.computeScaleFactor(contentSize, layoutSize)
|
||||
val targetScale = scale.scaleX.coerceAtMost(zoomableState.zoomSpec.maximum.factor)
|
||||
val offset = alignment.align(0, layoutSize.width.toInt(), LayoutDirection.Ltr)
|
||||
@@ -258,7 +264,11 @@ private inline fun ZoomableState?.canPan(getRemaining: (Rect) -> Float): Boolean
|
||||
contract {
|
||||
returns(true) implies (this@canPan != null)
|
||||
}
|
||||
return this != null && Settings.navigateToPan.value && getRemaining(transformedContentBounds) > 1f
|
||||
return this != null && Settings.navigateToPan.value &&
|
||||
getRemaining(with(coordinateSystem) { contentBounds(false).rectIn(CoordinateSpace.Viewport) }) > 1f
|
||||
}
|
||||
|
||||
private val PagerZoomSpec = ZoomSpec(maxZoomFactor = 5f)
|
||||
private val PagerZoomSpec = ZoomSpec(
|
||||
maximum = ZoomLimit(factor = 5f),
|
||||
minimum = ZoomLimit(factor = 1f, overzoomEffect = OverzoomEffect.Disabled),
|
||||
)
|
||||
@@ -9,19 +9,20 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.integerArrayResource
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.util.isAtLeastP
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
import com.hippo.ehviewer.util.isAtLeastP
|
||||
|
||||
@Composable
|
||||
fun ReaderGeneralSetting() = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_reader_theme),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.reader_themes),
|
||||
values = stringArrayResource(id = com.hippo.ehviewer.R.array.reader_themes_values),
|
||||
values = integerArrayResource(id = com.hippo.ehviewer.R.array.reader_themes_values).toList(),
|
||||
field = Settings.readerTheme.asMutableState(),
|
||||
)
|
||||
SwitchChoice(
|
||||
@@ -63,8 +64,15 @@ fun ReaderGeneralSetting() = Column(modifier = Modifier.verticalScroll(rememberS
|
||||
title = stringResource(id = R.string.pref_page_transitions),
|
||||
field = Settings.pageTransitions.asMutableState(),
|
||||
)
|
||||
val volume = Settings.readWithVolumeKeys.asMutableState()
|
||||
SwitchChoice(
|
||||
title = stringResource(id = R.string.settings_read_reverse_controls),
|
||||
field = Settings.readerReverseControls.asMutableState(),
|
||||
title = stringResource(id = R.string.settings_read_volume_page),
|
||||
field = volume,
|
||||
)
|
||||
AnimatedVisibility(visible = volume.value) {
|
||||
SwitchChoice(
|
||||
title = stringResource(id = R.string.settings_read_reverse_volume),
|
||||
field = Settings.readWithVolumeKeysInverted.asMutableState(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,40 +12,37 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.integerArrayResource
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
|
||||
@Composable
|
||||
fun ReaderModeSetting() = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
|
||||
fun ReaderModeSetting(isWebtoon: Boolean) = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_category_reading_mode),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.viewers_selector),
|
||||
values = arrayOf("0", "1", "2", "3", "4", "5"),
|
||||
values = ReadingModeType.entries.map { it.prefValue },
|
||||
field = Settings.readingMode.asMutableState(),
|
||||
)
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.rotation_type),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.rotation_type),
|
||||
values = arrayOf("0", "8", "16", "24", "32", "40", "48"),
|
||||
values = OrientationType.entries.map { it.prefValue },
|
||||
field = Settings.orientationMode.asMutableState(),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
val isPager by Settings.readingMode.collectAsState { value ->
|
||||
ReadingModeType.isPagerType(value)
|
||||
}
|
||||
Crossfade(targetState = isPager, label = "Pager") { pager ->
|
||||
if (pager) {
|
||||
PagerSetting()
|
||||
} else {
|
||||
Crossfade(targetState = isWebtoon, label = "Setting") { webtoon ->
|
||||
if (webtoon) {
|
||||
WebtoonSetting()
|
||||
} else {
|
||||
PagerSetting()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +58,7 @@ private fun PagerSetting() = Column {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_viewer_nav),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.pager_nav),
|
||||
values = arrayOf("0", "1", "2", "3", "4", "5"),
|
||||
values = listOf(0, 1, 2, 3, 4, 5),
|
||||
field = pagerNav,
|
||||
)
|
||||
AnimatedVisibility(visible = pagerNav.value != 5) {
|
||||
@@ -69,7 +66,7 @@ private fun PagerSetting() = Column {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_read_with_tapping_inverted),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.invert_tapping_mode),
|
||||
values = arrayOf("0", "1", "2", "3"),
|
||||
values = listOf(0, 1, 2, 3),
|
||||
field = Settings.readerPagerNavInverted.asMutableState(),
|
||||
)
|
||||
SwitchChoice(
|
||||
@@ -82,7 +79,7 @@ private fun PagerSetting() = Column {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_image_scale_type),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.image_scale_type),
|
||||
values = arrayOf("1", "2", "3", "4", "5", "6"),
|
||||
values = listOf(1, 2, 3, 4, 5, 6),
|
||||
field = scaleType,
|
||||
)
|
||||
AnimatedVisibility(visible = scaleType.value == 1) {
|
||||
@@ -94,7 +91,7 @@ private fun PagerSetting() = Column {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_zoom_start),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.zoom_start),
|
||||
values = arrayOf("1", "2", "3", "4"),
|
||||
values = listOf(1, 2, 3, 4),
|
||||
field = Settings.zoomStart.asMutableState(),
|
||||
)
|
||||
SwitchChoice(
|
||||
@@ -114,21 +111,21 @@ private fun WebtoonSetting() = Column {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_viewer_nav),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.webtoon_nav),
|
||||
values = arrayOf("0", "1", "2", "3", "4", "5"),
|
||||
values = listOf(0, 1, 2, 3, 4, 5),
|
||||
field = webtoonNav,
|
||||
)
|
||||
AnimatedVisibility(visible = webtoonNav.value != 5) {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_read_with_tapping_inverted),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.invert_tapping_mode),
|
||||
values = arrayOf("0", "1", "2", "3"),
|
||||
values = listOf(0, 1, 2, 3),
|
||||
field = Settings.readerWebtoonNavInverted.asMutableState(),
|
||||
)
|
||||
}
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_webtoon_side_padding),
|
||||
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.webtoon_side_padding),
|
||||
values = stringArrayResource(id = com.hippo.ehviewer.R.array.webtoon_side_padding_values),
|
||||
values = integerArrayResource(id = com.hippo.ehviewer.R.array.webtoon_side_padding_values).toList(),
|
||||
field = Settings.webtoonSidePadding.asMutableState(),
|
||||
)
|
||||
SwitchChoice(
|
||||
|
||||
@@ -13,14 +13,11 @@ import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.displayCutout
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.layout.windowInsetsTopHeight
|
||||
import androidx.compose.foundation.lazy.layout.LazyLayoutCacheWindow
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
@@ -53,22 +50,21 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.keepScreenOn
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import arrow.core.raise.ensure
|
||||
import arrow.core.right
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.ui.util.Await
|
||||
import com.ehviewer.core.ui.util.asyncInVM
|
||||
import com.ehviewer.core.ui.util.rememberSystemUiController
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.launchIO
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.ehviewer.core.util.unreachable
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.archiveFile
|
||||
@@ -82,7 +78,6 @@ import com.hippo.ehviewer.gallery.useEhPageLoader
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.Screen
|
||||
import com.hippo.ehviewer.ui.theme.EhTheme
|
||||
import com.hippo.ehviewer.ui.theme.scrim
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.awaitInputText
|
||||
import com.hippo.ehviewer.ui.tools.dialog
|
||||
@@ -104,7 +99,6 @@ import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.tarsin.kt.unreachable
|
||||
import moe.tarsin.string
|
||||
import okio.Path.Companion.toPath
|
||||
|
||||
@@ -132,26 +126,24 @@ fun AnimatedVisibilityScope.ReaderScreen(args: ReaderScreenArgs, navigator: Dest
|
||||
val uiController = rememberSystemUiController()
|
||||
DisposableEffect(uiController) {
|
||||
val lightStatusBar = uiController.statusBarDarkContentEnabled
|
||||
uiController.statusBarDarkContentEnabled = bgColor == Color.White
|
||||
onDispose {
|
||||
uiController.statusBarDarkContentEnabled = lightStatusBar
|
||||
}
|
||||
}
|
||||
LaunchedEffect(uiController) {
|
||||
snapshotFlow { bgColor }.collect {
|
||||
uiController.statusBarDarkContentEnabled = it == Color.White
|
||||
}
|
||||
}
|
||||
|
||||
Await(
|
||||
block = asyncInVM(args) { alive ->
|
||||
suspendCancellableCoroutine { cont ->
|
||||
alive.launchIO {
|
||||
catch {
|
||||
usePageLoader(args) { loader ->
|
||||
cont.resume(loader.right())
|
||||
awaitCancellation()
|
||||
}
|
||||
}.let { left -> cont.resume(left) }
|
||||
with(alive) {
|
||||
launchIO {
|
||||
catch {
|
||||
usePageLoader(args) { loader ->
|
||||
cont.resume(loader.right())
|
||||
awaitCancellation()
|
||||
}
|
||||
}.let { left -> cont.resume(left) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}.value.run {
|
||||
@@ -183,7 +175,7 @@ fun AnimatedVisibilityScope.ReaderScreen(args: ReaderScreenArgs, navigator: Dest
|
||||
}
|
||||
|
||||
@Composable
|
||||
context(activity: MainActivity, _: SnackbarHostState, _: DialogState, _: CoroutineScope)
|
||||
context(activity: MainActivity, _: SnackbarHostState, _: DialogState, _: CoroutineScope, _: DestinationsNavigator)
|
||||
fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
LaunchedEffect(Unit) {
|
||||
val orientation = activity.requestedOrientation
|
||||
@@ -198,18 +190,28 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
}
|
||||
}
|
||||
}
|
||||
val webtoon = remember(info) {
|
||||
// Tags in database may or may not have the prefix "other:"
|
||||
info?.simpleTags?.any { it.endsWith("webtoon") } == true
|
||||
}
|
||||
val showSeekbar by Settings.showReaderSeekbar.collectAsState()
|
||||
val readingMode by Settings.readingMode.collectAsState { ReadingModeType.fromPreference(it) }
|
||||
val reverseControls by Settings.readerReverseControls.collectAsState()
|
||||
val readingMode by Settings.readingMode.collectAsState {
|
||||
when (val mode = ReadingModeType.fromPreference(it)) {
|
||||
ReadingModeType.DEFAULT -> if (webtoon) ReadingModeType.WEBTOON else ReadingModeType.RIGHT_TO_LEFT
|
||||
else -> mode
|
||||
}
|
||||
}
|
||||
val volumeKeysEnabled by Settings.readWithVolumeKeys.collectAsState()
|
||||
val volumeKeysInverted by Settings.readWithVolumeKeysInverted.collectAsState()
|
||||
val fullscreen by Settings.fullscreen.collectAsState()
|
||||
val cutoutShort by Settings.cutoutShort.collectAsState()
|
||||
val keepScreenOn by Settings.keepScreenOn.collectAsState()
|
||||
val uiController = rememberSystemUiController()
|
||||
DisposableEffect(uiController) {
|
||||
uiController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
uiController.showTransientSystemBarsBySwipe = true
|
||||
onDispose {
|
||||
uiController.isSystemBarsVisible = true
|
||||
uiController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
||||
uiController.showTransientSystemBarsBySwipe = false
|
||||
}
|
||||
}
|
||||
val lazyListState = rememberLazyListState(LazyLayoutCacheWindow(SCROLL_FRACTION, SCROLL_FRACTION), pageLoader.startPage)
|
||||
@@ -220,8 +222,8 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
Box(
|
||||
Modifier.keyEventHandler(
|
||||
enabled = { !appbarVisible },
|
||||
reverse = { reverseControls },
|
||||
volumeKeysEnabled = { volumeKeysEnabled && !appbarVisible },
|
||||
volumeKeysInverted = { volumeKeysInverted },
|
||||
movePrevious = { launch { if (isWebtoon) lazyListState.scrollUp() else pagerState.moveToPrevious() } },
|
||||
moveNext = { launch { if (isWebtoon) lazyListState.scrollDown() else pagerState.moveToNext() } },
|
||||
).focusRequester(focusRequester).focusable().thenIf(keepScreenOn) { keepScreenOn() },
|
||||
@@ -229,18 +231,14 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
val bgColor by collectBackgroundColorAsState()
|
||||
syncState.Sync(isWebtoon) { appbarVisible = false }
|
||||
if (fullscreen) {
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { appbarVisible }.collect {
|
||||
uiController.isSystemBarsVisible = it
|
||||
}
|
||||
val bgColor by collectBackgroundColorAsState()
|
||||
val isDarkTheme = isSystemInDarkTheme()
|
||||
LaunchedEffect(isDarkTheme) {
|
||||
snapshotFlow { appbarVisible }.collect {
|
||||
uiController.isSystemBarsVisible = it || !fullscreen
|
||||
uiController.statusBarDarkContentEnabled = if (it) !isDarkTheme else bgColor == Color.White
|
||||
}
|
||||
Box(
|
||||
Modifier.windowInsetsTopHeight(WindowInsets.statusBars).fillMaxWidth()
|
||||
.align(Alignment.TopCenter).background(bgColor.scrim()).zIndex(1f),
|
||||
)
|
||||
}
|
||||
var showNavigationOverlay by remember {
|
||||
val showOnStart = Settings.showNavigationOverlayNewUser.value || Settings.showNavigationOverlayOnStart.value
|
||||
@@ -342,6 +340,7 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
}
|
||||
ReaderAppBars(
|
||||
visible = appbarVisible,
|
||||
title = pageLoader.title,
|
||||
isRtl = readingMode == ReadingModeType.RIGHT_TO_LEFT,
|
||||
showSeekBar = showSeekbar,
|
||||
currentPage = syncState.sliderValue,
|
||||
@@ -364,7 +363,7 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
dragHandle = null,
|
||||
contentWindowInsets = { WindowInsets() },
|
||||
) {
|
||||
SettingsPager(modifier = Modifier.fillMaxSize()) { page ->
|
||||
SettingsPager(isWebtoon = isWebtoon, modifier = Modifier.fillMaxSize()) { page ->
|
||||
isColorFilter = page == 2
|
||||
appbarVisible = !isColorFilter
|
||||
}
|
||||
@@ -372,7 +371,6 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -384,7 +382,7 @@ suspend inline fun <T> usePageLoader(args: ReaderScreenArgs, crossinline block:
|
||||
val page = args.page.takeUnless { it == -1 } ?: EhDB.getReadProgress(info.gid)
|
||||
val archive = DownloadManager.getDownloadInfo(info.gid)?.archiveFile
|
||||
if (archive != null) {
|
||||
useArchivePageLoader(archive, info.gid, page, info.hasAds, { error("Managed Archive have password???") }, block)
|
||||
useArchivePageLoader(archive, info, page, info.hasAds, { error("Managed Archive have password???") }, block)
|
||||
} else {
|
||||
useEhPageLoader(info, page, block)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ private val tabs = intArrayOf(
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SettingsPager(modifier: Modifier = Modifier, onPageSelected: (Int) -> Unit) {
|
||||
fun SettingsPager(isWebtoon: Boolean, modifier: Modifier = Modifier, onPageSelected: (Int) -> Unit) {
|
||||
val pagerState = rememberPagerState { tabs.size }
|
||||
LaunchedEffect(onPageSelected) {
|
||||
snapshotFlow { pagerState.currentPage }.collect { page ->
|
||||
@@ -53,7 +53,7 @@ fun SettingsPager(modifier: Modifier = Modifier, onPageSelected: (Int) -> Unit)
|
||||
) { page ->
|
||||
ProvideTextStyle(value = MaterialTheme.typography.labelLarge) {
|
||||
when (page) {
|
||||
0 -> ReaderModeSetting()
|
||||
0 -> ReaderModeSetting(isWebtoon)
|
||||
1 -> ReaderGeneralSetting()
|
||||
2 -> ColorFilterSetting()
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import eu.kanade.tachiyomi.ui.reader.viewer.NavigationRegions
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.getAction
|
||||
import kotlinx.coroutines.launch
|
||||
import me.saket.telephoto.zoomable.OverzoomEffect
|
||||
import me.saket.telephoto.zoomable.ZoomLimit
|
||||
import me.saket.telephoto.zoomable.ZoomSpec
|
||||
import me.saket.telephoto.zoomable.rememberZoomableState
|
||||
import me.saket.telephoto.zoomable.zoomable
|
||||
@@ -90,4 +92,7 @@ fun WebtoonViewer(
|
||||
}
|
||||
}
|
||||
|
||||
private val WebtoonZoomSpec = ZoomSpec(maxZoomFactor = 3f)
|
||||
private val WebtoonZoomSpec = ZoomSpec(
|
||||
maximum = ZoomLimit(factor = 3f),
|
||||
minimum = ZoomLimit(factor = 1f, overzoomEffect = OverzoomEffect.Disabled),
|
||||
)
|
||||
@@ -28,10 +28,10 @@ import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.ui.component.Slider
|
||||
|
||||
@Composable
|
||||
fun SpinnerChoice(title: String, entries: Array<String>, values: Array<String>, field: MutableState<Int>) {
|
||||
fun SpinnerChoice(title: String, entries: Array<String>, values: List<Int>, field: MutableState<Int>) {
|
||||
var value by field
|
||||
var dropdown by remember { mutableStateOf(false) }
|
||||
val data = remember { entries zip values.map { v -> v.toInt() } }
|
||||
val data = remember { entries zip values }
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().height(48.dp).clickable { dropdown = true }.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.hippo.ehviewer.ui.screen
|
||||
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.util.containsIgnoreCase
|
||||
import com.hippo.ehviewer.download.DownloadsFilterMode
|
||||
import com.hippo.ehviewer.util.containsIgnoreCase
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -5,8 +5,10 @@ import android.view.ViewConfiguration
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -43,6 +45,7 @@ import androidx.compose.material.icons.filled.Reorder
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Shuffle
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularWavyProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -82,7 +85,9 @@ import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import arrow.core.partially1
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.TagNamespace
|
||||
import com.ehviewer.core.ui.component.FAB_ANIMATE_TIME
|
||||
import com.ehviewer.core.ui.component.FabLayout
|
||||
import com.ehviewer.core.ui.component.FastScrollLazyColumn
|
||||
@@ -91,12 +96,16 @@ import com.ehviewer.core.ui.component.LocalSideSheetState
|
||||
import com.ehviewer.core.ui.component.ProvideSideSheetContent
|
||||
import com.ehviewer.core.ui.icons.EhIcons
|
||||
import com.ehviewer.core.ui.icons.big.Download
|
||||
import com.ehviewer.core.ui.util.Await
|
||||
import com.ehviewer.core.ui.util.HapticFeedbackType
|
||||
import com.ehviewer.core.ui.util.asyncState
|
||||
import com.ehviewer.core.ui.util.ifTrueThen
|
||||
import com.ehviewer.core.ui.util.rememberHapticFeedback
|
||||
import com.ehviewer.core.ui.util.rememberInVM
|
||||
import com.ehviewer.core.ui.util.takeAndClear
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.launchIO
|
||||
import com.ehviewer.core.util.mapToLongArray
|
||||
import com.ehviewer.core.util.onEachLatest
|
||||
import com.ehviewer.core.util.withNonCancellableContext
|
||||
import com.ehviewer.core.util.withUIContext
|
||||
@@ -104,11 +113,8 @@ import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.DownloadManager.downloadInfoList
|
||||
import com.hippo.ehviewer.download.DownloadService
|
||||
import com.hippo.ehviewer.download.DownloadsFilterMode
|
||||
import com.hippo.ehviewer.download.SortMode
|
||||
@@ -117,20 +123,14 @@ import com.hippo.ehviewer.ui.Screen
|
||||
import com.hippo.ehviewer.ui.confirmRemoveDownloadRange
|
||||
import com.hippo.ehviewer.ui.main.DownloadCard
|
||||
import com.hippo.ehviewer.ui.main.GalleryInfoGridItem
|
||||
import com.hippo.ehviewer.ui.main.plus
|
||||
import com.hippo.ehviewer.ui.navToReader
|
||||
import com.hippo.ehviewer.ui.showMoveDownloadLabelList
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.HapticFeedbackType
|
||||
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
|
||||
import com.hippo.ehviewer.ui.tools.awaitInputText
|
||||
import com.hippo.ehviewer.ui.tools.awaitSelectAction
|
||||
import com.hippo.ehviewer.ui.tools.awaitSelectItemWithCheckBox
|
||||
import com.hippo.ehviewer.ui.tools.awaitSingleChoice
|
||||
import com.hippo.ehviewer.ui.tools.rememberHapticFeedback
|
||||
import com.hippo.ehviewer.util.mapToLongArray
|
||||
import com.hippo.ehviewer.util.takeAndClear
|
||||
import com.jamal.composeprefs3.ui.ifTrueThen
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
@@ -148,6 +148,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
|
||||
val filterMode by Settings.downloadFilterMode.collectAsState { DownloadsFilterMode.from(it) }
|
||||
var filterState by rememberSerializable { mutableStateOf(DownloadsFilterState(filterMode, Settings.recentDownloadLabel.value)) }
|
||||
var invalidateKey by rememberSaveable { mutableStateOf(false) }
|
||||
var isLoading by rememberSaveable { mutableStateOf(true) }
|
||||
var searchBarExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
var searchBarOffsetY by remember { mutableIntStateOf(0) }
|
||||
val animateItems by Settings.animateItems.collectAsState()
|
||||
@@ -183,10 +184,19 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
|
||||
},
|
||||
)
|
||||
val hint = stringResource(R.string.search_bar_hint, title)
|
||||
val list = remember(filterState, invalidateKey) {
|
||||
downloadInfoList.filterTo(mutableStateListOf()) { info ->
|
||||
filterState.take(info)
|
||||
val list = if (DownloadManager.isInitialized) {
|
||||
remember(filterState, invalidateKey) {
|
||||
DownloadManager.downloadInfoList.filterTo(mutableStateListOf()) { info ->
|
||||
filterState.take(info)
|
||||
}.also {
|
||||
launch {
|
||||
delay(200)
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
remember { mutableStateListOf() }
|
||||
}
|
||||
|
||||
val newLabel = stringResource(R.string.new_label_title)
|
||||
@@ -562,7 +572,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
|
||||
}
|
||||
|
||||
fun onItemClick(info: DownloadInfo) {
|
||||
launchIO { EhDB.putHistoryInfo(info.galleryInfo) }
|
||||
launchIO { EhDB.putHistoryInfo(info) }
|
||||
navToReader(info.galleryInfo)
|
||||
}
|
||||
|
||||
@@ -632,24 +642,26 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
|
||||
}
|
||||
}
|
||||
|
||||
Await({ delay(200) }) {
|
||||
if (list.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(realPadding).fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = EhIcons.Big.Default.Download,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.no_download_info),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
)
|
||||
}
|
||||
if (isLoading) {
|
||||
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface), contentAlignment = Alignment.Center) {
|
||||
CircularWavyProgressIndicator()
|
||||
}
|
||||
} else if (list.isEmpty()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(realPadding).fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = EhIcons.Big.Default.Download,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.no_download_info),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -689,9 +701,12 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
|
||||
oldMode.groupByDownloadLabel,
|
||||
)
|
||||
val mode = SortMode.All[selected].copy(groupByDownloadLabel = checked)
|
||||
DownloadManager.sortDownloads(mode)
|
||||
sortMode = mode.flag
|
||||
invalidateKey = !invalidateKey
|
||||
if (mode != oldMode) {
|
||||
sortMode = mode.flag
|
||||
isLoading = true
|
||||
DownloadManager.sortDownloads(mode)
|
||||
invalidateKey = !invalidateKey
|
||||
}
|
||||
}
|
||||
onClick(Icons.Default.FilterList) {
|
||||
val downloadStates = contextOf<Context>().resources.getStringArray(com.hippo.ehviewer.R.array.download_state).toList()
|
||||
|
||||
@@ -54,6 +54,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.ui.component.FAB_ANIMATE_TIME
|
||||
import com.ehviewer.core.ui.component.FabLayout
|
||||
import com.ehviewer.core.ui.component.LocalSideSheetState
|
||||
@@ -61,14 +62,15 @@ import com.ehviewer.core.ui.component.ProvideSideSheetContent
|
||||
import com.ehviewer.core.ui.icons.EhIcons
|
||||
import com.ehviewer.core.ui.icons.filled.GoTo
|
||||
import com.ehviewer.core.ui.util.asyncState
|
||||
import com.ehviewer.core.ui.util.takeAndClear
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.mapToLongArray
|
||||
import com.ehviewer.core.util.onEachLatest
|
||||
import com.ehviewer.core.util.withUIContext
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.ui.DrawerHandle
|
||||
@@ -81,8 +83,6 @@ import com.hippo.ehviewer.ui.main.GalleryList
|
||||
import com.hippo.ehviewer.ui.startDownload
|
||||
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
|
||||
import com.hippo.ehviewer.ui.tools.awaitSelectItem
|
||||
import com.hippo.ehviewer.util.mapToLongArray
|
||||
import com.hippo.ehviewer.util.takeAndClear
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
@@ -313,7 +313,7 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator, v
|
||||
}
|
||||
onClick(EhIcons.Default.GoTo) {
|
||||
val date = awaitSelectDate()
|
||||
refresh(urlBuilder.copy(jumpTo = date))
|
||||
refresh(urlBuilder.copy(jumpTo = date, prev = null, next = "2"))
|
||||
}
|
||||
onClick(Icons.Default.Refresh) {
|
||||
refresh()
|
||||
|
||||
@@ -13,19 +13,24 @@ import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.paging.filter
|
||||
import androidx.paging.map
|
||||
import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import com.hippo.ehviewer.ui.tools.foldToLoadResult
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
class FavoritesViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
|
||||
private val mutex = Mutex()
|
||||
|
||||
val urlBuilder by savedStateHandle.saved(MutableStateSerializer()) {
|
||||
mutableStateOf(FavListUrlBuilder(favCat = Settings.recentFavCat))
|
||||
}
|
||||
@@ -41,44 +46,40 @@ class FavoritesViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
|
||||
} else {
|
||||
EhDB.searchLocalFav(keywordNow)
|
||||
}
|
||||
}
|
||||
}.flow.map { data -> data.map<_, BaseGalleryInfo> { it } }
|
||||
} else {
|
||||
Pager(PagingConfig(25)) {
|
||||
Pager(PagingConfig(DEFAULT_PAGE_SIZE, prefetchDistance = 20)) {
|
||||
object : PagingSource<String, BaseGalleryInfo>() {
|
||||
override fun getRefreshKey(state: PagingState<String, BaseGalleryInfo>): String? = null
|
||||
override suspend fun load(params: LoadParams<String>) = withIOContext {
|
||||
val urlBuilder = urlBuilder.value
|
||||
when (params) {
|
||||
is LoadParams.Prepend -> urlBuilder.setIndex(params.key, isNext = false)
|
||||
is LoadParams.Append -> urlBuilder.setIndex(params.key, isNext = true)
|
||||
is LoadParams.Refresh -> {
|
||||
val key = params.key
|
||||
if (key.isNullOrBlank()) {
|
||||
if (urlBuilder.jumpTo != null) {
|
||||
urlBuilder.next ?: urlBuilder.setIndex("2", true)
|
||||
}
|
||||
} else {
|
||||
urlBuilder.setIndex(key, false)
|
||||
val url = mutex.withLock {
|
||||
with(urlBuilder.value) {
|
||||
when (params) {
|
||||
is LoadParams.Prepend -> setIndex(params.key, isNext = false)
|
||||
is LoadParams.Append -> setIndex(params.key, isNext = true)
|
||||
is LoadParams.Refresh -> params.key?.let { setIndex(it, false) }
|
||||
}
|
||||
build()
|
||||
}
|
||||
}
|
||||
runSuspendCatching {
|
||||
EhEngine.getFavorites(urlBuilder.build())
|
||||
EhEngine.getFavorites(url)
|
||||
}.foldToLoadResult { result ->
|
||||
Settings.favCat = result.catArray.toTypedArray()
|
||||
Settings.favCount = result.countArray.toIntArray()
|
||||
Settings.favCloudCount = result.countArray.sum()
|
||||
urlBuilder.jumpTo = null
|
||||
LoadResult.Page(result.galleryInfoList, result.prev, result.next)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.flow.map { data ->
|
||||
// https://github.com/FooIbar/EhViewer/issues/1190
|
||||
// Workaround for duplicate items when sorting by favorited time
|
||||
val gidSet = MutableLongSet(DEFAULT_PAGE_SIZE)
|
||||
data.filter { gidSet.add(it.gid) }
|
||||
}
|
||||
}.flow.map { pagingData ->
|
||||
// https://github.com/FooIbar/EhViewer/issues/1190
|
||||
// Workaround for duplicate items when sorting by favorited time
|
||||
val gidSet = MutableLongSet(50)
|
||||
pagingData.filter { gidSet.add(it.gid) }
|
||||
}
|
||||
}.cachedIn(viewModelScope)
|
||||
}
|
||||
|
||||
private const val DEFAULT_PAGE_SIZE = 50
|
||||
@@ -11,6 +11,7 @@ import android.text.style.URLSpan
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -25,6 +26,7 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
@@ -82,22 +84,24 @@ import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.getSpans
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.core.text.parseAsHtml
|
||||
import com.ehviewer.core.database.model.Filter
|
||||
import com.ehviewer.core.database.model.FilterMode
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.GalleryComment
|
||||
import com.ehviewer.core.ui.util.animateFloatMergePredictiveBackAsState
|
||||
import com.ehviewer.core.ui.util.snackBarPadding
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.isAtLeastP
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.launchIO
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.withUIContext
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhFilter.remember
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.data.GalleryComment
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
import com.hippo.ehviewer.dao.FilterMode
|
||||
import com.hippo.ehviewer.ui.Screen
|
||||
import com.hippo.ehviewer.ui.jumpToReaderByPage
|
||||
import com.hippo.ehviewer.ui.main.GalleryCommentCard
|
||||
@@ -116,7 +120,6 @@ import com.hippo.ehviewer.util.displayString
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlin.math.roundToInt
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import moe.tarsin.navigate
|
||||
@@ -193,7 +196,7 @@ fun AnimatedVisibilityScope.GalleryCommentsScreen(gid: Long, navigator: Destinat
|
||||
val density = LocalDensity.current
|
||||
|
||||
suspend fun refreshComment(showAll: Boolean) {
|
||||
val url = EhUrl.getGalleryDetailUrl(galleryDetail.gid, galleryDetail.token, 0, showAll)
|
||||
val url = EhUrl.getGalleryDetailUrl(galleryDetail.gid, galleryDetail.token, allComment = showAll)
|
||||
val detail = EhEngine.getGalleryDetail(url)
|
||||
comments = detail.comments
|
||||
}
|
||||
@@ -216,7 +219,7 @@ fun AnimatedVisibilityScope.GalleryCommentsScreen(gid: Long, navigator: Destinat
|
||||
suspend fun sendComment() {
|
||||
commenting = false
|
||||
withUIContext { focusManager.clearFocus() }
|
||||
val url = EhUrl.getGalleryDetailUrl(galleryDetail.gid, galleryDetail.token, 0, false)
|
||||
val url = EhUrl.getGalleryDetailUrl(galleryDetail.gid, galleryDetail.token)
|
||||
userComment.runSuspendCatching {
|
||||
val bbcode = annotatedString.normalizeSpan().toBBCode()
|
||||
logcat("sendComment") { bbcode }
|
||||
@@ -394,7 +397,7 @@ fun AnimatedVisibilityScope.GalleryCommentsScreen(gid: Long, navigator: Destinat
|
||||
}
|
||||
if (!comment.voteState.isNullOrEmpty()) {
|
||||
onSelect(checkVoteStatus) {
|
||||
showCommentVoteStatus(comment.voteState)
|
||||
showCommentVoteStatus(comment.voteState!!)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -455,6 +458,11 @@ fun AnimatedVisibilityScope.GalleryCommentsScreen(gid: Long, navigator: Destinat
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isAtLeastP) {
|
||||
// Workaround for crash when 0-sized TextField is focused
|
||||
// https://issuetracker.google.com/440964236
|
||||
Box(Modifier.size(1.dp).focusable())
|
||||
}
|
||||
Surface(
|
||||
modifier = Modifier.align(Alignment.BottomCenter).layout { measurable, constraints ->
|
||||
val origin = measurable.measure(constraints)
|
||||
|
||||
@@ -68,21 +68,39 @@ import androidx.paging.compose.itemKey
|
||||
import arrow.core.partially1
|
||||
import arrow.fx.coroutines.parMap
|
||||
import arrow.fx.coroutines.parZip
|
||||
import com.ehviewer.core.data.model.asGalleryDetail
|
||||
import com.ehviewer.core.data.model.findBaseInfo
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.database.model.Filter
|
||||
import com.ehviewer.core.database.model.FilterMode
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.GalleryComment
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.model.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import com.ehviewer.core.model.GalleryPreview
|
||||
import com.ehviewer.core.model.GalleryTagGroup
|
||||
import com.ehviewer.core.model.TagNamespace
|
||||
import com.ehviewer.core.model.V2GalleryPreview
|
||||
import com.ehviewer.core.model.VoteStatus
|
||||
import com.ehviewer.core.ui.component.CrystalCard
|
||||
import com.ehviewer.core.ui.component.FastScrollLazyVerticalGrid
|
||||
import com.ehviewer.core.ui.component.FilledTertiaryIconButton
|
||||
import com.ehviewer.core.ui.component.FilledTertiaryIconToggleButton
|
||||
import com.ehviewer.core.ui.component.GalleryDetailRating
|
||||
import com.ehviewer.core.ui.component.GalleryRatingBar
|
||||
import com.ehviewer.core.ui.icons.EhIcons
|
||||
import com.ehviewer.core.ui.icons.filled.Magnet
|
||||
import com.ehviewer.core.ui.util.LocalWindowSizeClass
|
||||
import com.ehviewer.core.ui.util.TransitionsVisibilityScope
|
||||
import com.ehviewer.core.ui.util.flattenForEach
|
||||
import com.ehviewer.core.ui.util.isExpanded
|
||||
import com.ehviewer.core.ui.util.rememberInVM
|
||||
import com.ehviewer.core.util.async
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.launchIO
|
||||
import com.ehviewer.core.util.launchUI
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.ehviewer.core.util.withUIContext
|
||||
import com.hippo.ehviewer.EhDB
|
||||
@@ -91,26 +109,12 @@ import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.EhFilter.remember
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryComment
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.client.data.V2GalleryPreview
|
||||
import com.hippo.ehviewer.client.data.VoteStatus
|
||||
import com.hippo.ehviewer.client.data.asGalleryDetail
|
||||
import com.hippo.ehviewer.client.data.findBaseInfo
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
import com.hippo.ehviewer.client.exception.NoHathClientException
|
||||
import com.hippo.ehviewer.coil.PrefetchAround
|
||||
import com.hippo.ehviewer.coil.justDownload
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
import com.hippo.ehviewer.dao.FilterMode
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.ktbuilder.executeIn
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
@@ -132,8 +136,6 @@ import com.hippo.ehviewer.ui.navToReader
|
||||
import com.hippo.ehviewer.ui.openBrowser
|
||||
import com.hippo.ehviewer.ui.startDownload
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.GalleryDetailRating
|
||||
import com.hippo.ehviewer.ui.tools.GalleryRatingBar
|
||||
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
|
||||
import com.hippo.ehviewer.ui.tools.awaitResult
|
||||
import com.hippo.ehviewer.ui.tools.awaitSelectAction
|
||||
@@ -148,9 +150,7 @@ import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.util.addTextToClipboard
|
||||
import com.hippo.ehviewer.util.bgWork
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.hippo.ehviewer.util.flattenForEach
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.http.encodeURLParameter
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.math.roundToInt
|
||||
@@ -498,7 +498,7 @@ fun BelowHeader(galleryDetail: GalleryDetail, voteTag: VoteTag) {
|
||||
launchIO {
|
||||
favoritesLock.mutate {
|
||||
runSuspendCatching {
|
||||
modifyFavorites(galleryDetail.galleryInfo)
|
||||
modifyFavorites(galleryDetail)
|
||||
}.onSuccess { add ->
|
||||
if (add) {
|
||||
snackbar(addSucceed)
|
||||
@@ -809,7 +809,7 @@ private fun GalleryDetail.collectPreviewItems() = rememberInVM(previewList) {
|
||||
runSuspendCatching {
|
||||
(up..end).filterNot { it in previewPagesMap }.map { it / pageSize }.toSet()
|
||||
.parMap(concurrency = Settings.multiThreadDownload.value) { page ->
|
||||
val url = EhUrl.getGalleryDetailUrl(gid, token, page, false)
|
||||
val url = EhUrl.getGalleryDetailUrl(gid, token, page)
|
||||
EhEngine.getPreviewList(url).previews
|
||||
}.flattenForEach {
|
||||
previewPagesMap[it.position] = it
|
||||
|
||||
@@ -34,12 +34,20 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.util.fastJoinToString
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.ehviewer.core.data.model.findBaseInfo
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.files.delete
|
||||
import com.ehviewer.core.files.toOkioPath
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.model.GalleryDetail
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.ui.util.LocalWindowSizeClass
|
||||
import com.ehviewer.core.ui.util.isExpanded
|
||||
import com.ehviewer.core.ui.util.launchInVM
|
||||
import com.ehviewer.core.ui.util.rememberInVM
|
||||
import com.ehviewer.core.util.launchIO
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.hippo.ehviewer.EhApplication.Companion.imageCache
|
||||
import com.hippo.ehviewer.EhDB
|
||||
@@ -47,13 +55,9 @@ import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
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.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.findBaseInfo
|
||||
import com.hippo.ehviewer.client.data.fillInfo
|
||||
import com.hippo.ehviewer.client.getImageKey
|
||||
import com.hippo.ehviewer.coil.justDownload
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.ktbuilder.executeIn
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
@@ -70,12 +74,9 @@ import com.hippo.ehviewer.util.AppHelper
|
||||
import com.hippo.ehviewer.util.awaitActivityResult
|
||||
import com.hippo.ehviewer.util.bgWork
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.hippo.files.delete
|
||||
import com.hippo.files.toOkioPath
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import moe.tarsin.snackbar
|
||||
@@ -109,7 +110,7 @@ fun AnimatedVisibilityScope.GalleryDetailScreen(args: GalleryDetailScreenArgs, n
|
||||
is TokenArgs -> args.gid to args.token
|
||||
}
|
||||
}
|
||||
val galleryDetailUrl = remember(gid, token) { EhUrl.getGalleryDetailUrl(gid, token, 0, false) }
|
||||
val galleryDetailUrl = remember(gid, token) { EhUrl.getGalleryDetailUrl(gid, token) }
|
||||
contextOf<MainActivity>().ProvideAssistContent(galleryDetailUrl)
|
||||
|
||||
var galleryInfo by rememberInVM {
|
||||
@@ -137,12 +138,12 @@ fun AnimatedVisibilityScope.GalleryDetailScreen(args: GalleryDetailScreenArgs, n
|
||||
withIOContext { EhEngine.getGalleryDetail(galleryDetailUrl) }
|
||||
}.onSuccess { galleryDetail ->
|
||||
detailCache[galleryDetail.gid] = galleryDetail
|
||||
}.onFailure {
|
||||
galleryInfo?.let { info -> EhDB.putHistoryInfo(info.findBaseInfo()) }
|
||||
getDetailError = it.displayString()
|
||||
}.onFailure { e ->
|
||||
galleryInfo?.let { EhDB.putHistoryInfo(it) }
|
||||
getDetailError = e.displayString()
|
||||
}.getOrNull()
|
||||
galleryDetail?.let {
|
||||
EhDB.putHistoryInfo(it.galleryInfo)
|
||||
EhDB.putHistoryInfo(it)
|
||||
galleryInfo = it
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ import androidx.paging.LoadState
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import arrow.core.raise.ensure
|
||||
import arrow.core.raise.ensureNotNull
|
||||
import com.ehviewer.core.database.model.QuickSearch
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.ui.component.FAB_ANIMATE_TIME
|
||||
import com.ehviewer.core.ui.component.FabLayout
|
||||
@@ -78,7 +79,9 @@ import com.ehviewer.core.ui.component.ProvideSideSheetContent
|
||||
import com.ehviewer.core.ui.icons.EhIcons
|
||||
import com.ehviewer.core.ui.icons.filled.GoTo
|
||||
import com.ehviewer.core.ui.util.Await
|
||||
import com.ehviewer.core.ui.util.HapticFeedbackType
|
||||
import com.ehviewer.core.ui.util.asyncState
|
||||
import com.ehviewer.core.ui.util.rememberHapticFeedback
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.launchIO
|
||||
@@ -100,7 +103,6 @@ import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_WHATS_HOT
|
||||
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.dao.QuickSearch
|
||||
import com.hippo.ehviewer.ui.DrawerHandle
|
||||
import com.hippo.ehviewer.ui.Screen
|
||||
import com.hippo.ehviewer.ui.awaitSelectDate
|
||||
@@ -113,11 +115,9 @@ import com.hippo.ehviewer.ui.main.GalleryInfoListItem
|
||||
import com.hippo.ehviewer.ui.main.GalleryList
|
||||
import com.hippo.ehviewer.ui.main.SearchFilter
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.HapticFeedbackType
|
||||
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
|
||||
import com.hippo.ehviewer.ui.tools.awaitInputText
|
||||
import com.hippo.ehviewer.ui.tools.awaitInputTextWithCheckBox
|
||||
import com.hippo.ehviewer.ui.tools.rememberHapticFeedback
|
||||
import com.hippo.ehviewer.ui.tools.rememberMutableStateInDataStore
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
@@ -590,22 +590,21 @@ fun AnimatedVisibilityScope.GalleryListScreen(
|
||||
if (urlBuilder.mode != MODE_WHATS_HOT) {
|
||||
onClick(EhIcons.Default.GoTo) {
|
||||
if (isTopList) {
|
||||
val page = urlBuilder.jumpTo?.toIntOrNull() ?: 0
|
||||
val hint = string(R.string.go_to_hint, page + 1, TOPLIST_PAGES)
|
||||
val hint = string(R.string.go_to_hint, urlBuilder.page, TOPLIST_PAGES)
|
||||
val text = awaitInputText(title = gotoTitle, hint = hint, isNumber = true) { oriText ->
|
||||
val goto = ensureNotNull(oriText.trim().toIntOrNull()) { invalidNum } - 1
|
||||
ensure(goto in 0..<TOPLIST_PAGES) { outOfRange }
|
||||
}.trim().toInt() - 1
|
||||
urlBuilder.setJumpTo(text)
|
||||
val goto = ensureNotNull(oriText.trim().toIntOrNull()) { invalidNum }
|
||||
ensure(goto in 1..TOPLIST_PAGES) { outOfRange }
|
||||
}
|
||||
urlBuilder.page = text.trim().toInt()
|
||||
} else {
|
||||
val date = awaitSelectDate()
|
||||
urlBuilder.jumpTo = date
|
||||
urlBuilder.setSeek(date)
|
||||
}
|
||||
data.refresh()
|
||||
}
|
||||
onClick(Icons.AutoMirrored.Default.LastPage) {
|
||||
if (isTopList) {
|
||||
urlBuilder.setJumpTo(TOPLIST_PAGES - 1)
|
||||
urlBuilder.page = TOPLIST_PAGES
|
||||
} else {
|
||||
urlBuilder.setIndex("1", false)
|
||||
}
|
||||
|
||||
@@ -11,55 +11,60 @@ import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import androidx.paging.cachedIn
|
||||
import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_TOPLIST
|
||||
import com.hippo.ehviewer.ui.tools.foldToLoadResult
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
class GalleryListViewModel(lub: ListUrlBuilder, savedStateHandle: SavedStateHandle) : ViewModel() {
|
||||
private val mutex = Mutex()
|
||||
|
||||
val urlBuilder by savedStateHandle.saved(MutableStateSerializer()) {
|
||||
mutableStateOf(lub)
|
||||
}
|
||||
|
||||
val data = Pager(PagingConfig(25)) {
|
||||
// Use a smaller prefetch distance to avoid prepending an additional page after jumping
|
||||
val data = Pager(PagingConfig(25, prefetchDistance = 20)) {
|
||||
object : PagingSource<String, BaseGalleryInfo>() {
|
||||
override fun getRefreshKey(state: PagingState<String, BaseGalleryInfo>): String? = null
|
||||
override suspend fun load(params: LoadParams<String>) = withIOContext {
|
||||
val urlBuilder = urlBuilder.value
|
||||
if (urlBuilder.mode == MODE_TOPLIST) {
|
||||
// TODO: Since we know total pages, let pager support jump
|
||||
val key = (params.key ?: urlBuilder.jumpTo)?.toInt() ?: 0
|
||||
val key: Int
|
||||
val url = mutex.withLock {
|
||||
with(urlBuilder) {
|
||||
params.key?.let { page = it.toInt() }
|
||||
key = page
|
||||
build()
|
||||
}
|
||||
}
|
||||
val prev = (key - 1).takeIf { it > 0 }
|
||||
val next = (key + 1).takeIf { it < TOPLIST_PAGES }
|
||||
val next = (key + 1).takeIf { it <= TOPLIST_PAGES }
|
||||
runSuspendCatching {
|
||||
urlBuilder.setJumpTo(key)
|
||||
EhEngine.getGalleryList(urlBuilder.build())
|
||||
EhEngine.getGalleryList(url)
|
||||
}.foldToLoadResult { result ->
|
||||
LoadResult.Page(result.galleryInfoList, prev?.toString(), next?.toString())
|
||||
}
|
||||
} else {
|
||||
when (params) {
|
||||
is LoadParams.Prepend -> urlBuilder.setIndex(params.key, isNext = false)
|
||||
is LoadParams.Append -> urlBuilder.setIndex(params.key, isNext = true)
|
||||
is LoadParams.Refresh -> {
|
||||
val key = params.key
|
||||
if (key.isNullOrBlank()) {
|
||||
if (urlBuilder.jumpTo != null) {
|
||||
urlBuilder.next ?: urlBuilder.setIndex("2", true)
|
||||
}
|
||||
} else {
|
||||
urlBuilder.setIndex(key, false)
|
||||
val url = mutex.withLock {
|
||||
with(urlBuilder) {
|
||||
when (params) {
|
||||
is LoadParams.Prepend -> setIndex(params.key, isNext = false)
|
||||
is LoadParams.Append -> setIndex(params.key, isNext = true)
|
||||
is LoadParams.Refresh -> params.key?.let { setIndex(it, false) }
|
||||
}
|
||||
build()
|
||||
}
|
||||
}
|
||||
runSuspendCatching {
|
||||
val url = urlBuilder.build()
|
||||
EhEngine.getGalleryList(url)
|
||||
}.foldToLoadResult { result ->
|
||||
urlBuilder.jumpTo = null
|
||||
LoadResult.Page(result.galleryInfoList, result.prev, result.next)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import com.ehviewer.core.files.toOkioPath
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.ui.util.snackBarPadding
|
||||
import com.ehviewer.core.util.launch
|
||||
@@ -36,10 +37,8 @@ import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_IMAGE_SEARCH
|
||||
import com.hippo.ehviewer.ui.Screen
|
||||
import com.hippo.ehviewer.ui.main.ImageSearch
|
||||
import com.hippo.ehviewer.ui.main.plus
|
||||
import com.hippo.ehviewer.util.pickVisualMedia
|
||||
import com.hippo.ehviewer.util.sha1
|
||||
import com.hippo.files.toOkioPath
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.hippo.ehviewer.ui.screen
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryListUrlParser
|
||||
|
||||
@@ -65,25 +65,23 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.coerceAtMost
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.database.dao.SearchDao
|
||||
import com.ehviewer.core.database.model.Search
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.TagNamespace
|
||||
import com.ehviewer.core.ui.util.ifNotNullThen
|
||||
import com.ehviewer.core.ui.util.ifTrueThen
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
import com.ehviewer.core.util.launchIO
|
||||
import com.ehviewer.core.util.launchUI
|
||||
import com.hippo.ehviewer.EhApplication.Companion.searchDatabase
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhTagDatabase
|
||||
import com.hippo.ehviewer.client.data.TagNamespace
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.dao.Search
|
||||
import com.hippo.ehviewer.dao.SearchDao
|
||||
import com.hippo.ehviewer.ui.LocalNavDrawerState
|
||||
import com.hippo.ehviewer.ui.destinations.ImageSearchScreenDestination
|
||||
import com.hippo.ehviewer.ui.theme.scrim
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
|
||||
import com.hippo.ehviewer.ui.tools.rememberCompositionActiveState
|
||||
import com.jamal.composeprefs3.ui.ifNotNullThen
|
||||
import com.jamal.composeprefs3.ui.ifTrueThen
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -195,7 +193,7 @@ fun SearchBarScreen(
|
||||
// May have invalid whitespaces if pasted from clipboard, replace them with spaces
|
||||
val query = searchFieldState.text.trim().replace(WhitespaceRegex, " ")
|
||||
if (query.isNotEmpty()) {
|
||||
scope.launchIO {
|
||||
scope.launch {
|
||||
mSearchDatabase.deleteQuery(query)
|
||||
val search = Search(System.currentTimeMillis(), query)
|
||||
mSearchDatabase.insert(search)
|
||||
@@ -257,7 +255,7 @@ fun SearchBarScreen(
|
||||
}
|
||||
} else {
|
||||
val drawerState = LocalNavDrawerState.current
|
||||
IconButton(onClick = { scope.launchUI { drawerState.open() } }, shapes = IconButtonDefaults.shapes()) {
|
||||
IconButton(onClick = { scope.launch { drawerState.open() } }, shapes = IconButtonDefaults.shapes()) {
|
||||
Icon(Icons.Default.Menu, contentDescription = null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.files.delete
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.withUIContext
|
||||
@@ -40,7 +41,6 @@ import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.hippo.ehviewer.util.installPackage
|
||||
import com.hippo.files.delete
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
@@ -30,8 +30,14 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.net.toUri
|
||||
import com.ehviewer.core.files.delete
|
||||
import com.ehviewer.core.files.sendTo
|
||||
import com.ehviewer.core.files.toOkioPath
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.util.isAtLeastO
|
||||
import com.ehviewer.core.util.isAtLeastSExtension7
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.ehviewer.core.util.withIOContext
|
||||
import com.hippo.ehviewer.BuildConfig
|
||||
import com.hippo.ehviewer.EhDB
|
||||
@@ -50,16 +56,10 @@ import com.hippo.ehviewer.util.ReadableTime
|
||||
import com.hippo.ehviewer.util.displayPath
|
||||
import com.hippo.ehviewer.util.getAppLanguage
|
||||
import com.hippo.ehviewer.util.getLanguages
|
||||
import com.hippo.ehviewer.util.isAtLeastO
|
||||
import com.hippo.ehviewer.util.isAtLeastSExtension7
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.ehviewer.util.setAppLanguage
|
||||
import com.hippo.files.delete
|
||||
import com.hippo.files.toOkioPath
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import java.io.File
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
@@ -29,19 +29,29 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import arrow.fx.coroutines.parMap
|
||||
import arrow.fx.coroutines.parMapNotNull
|
||||
import com.ehviewer.core.database.model.DownloadInfo
|
||||
import com.ehviewer.core.files.delete
|
||||
import com.ehviewer.core.files.find
|
||||
import com.ehviewer.core.files.isDirectory
|
||||
import com.ehviewer.core.files.list
|
||||
import com.ehviewer.core.files.metadataOrNull
|
||||
import com.ehviewer.core.files.mkdirs
|
||||
import com.ehviewer.core.files.toOkioPath
|
||||
import com.ehviewer.core.files.toUri
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.model.BaseGalleryInfo
|
||||
import com.ehviewer.core.model.GalleryInfo
|
||||
import com.ehviewer.core.util.isAtLeastQ
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.ehviewer.core.util.launchIO
|
||||
import com.ehviewer.core.util.logcat
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
import com.hippo.ehviewer.client.EhEngine.fillGalleryListByApi
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
|
||||
import com.hippo.ehviewer.client.parser.ParserUtils
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.download.downloadDir
|
||||
import com.hippo.ehviewer.download.downloadLocation
|
||||
@@ -60,20 +70,10 @@ import com.hippo.ehviewer.ui.tools.observed
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.displayPath
|
||||
import com.hippo.ehviewer.util.displayString
|
||||
import com.hippo.ehviewer.util.isAtLeastQ
|
||||
import com.hippo.ehviewer.util.requestPermission
|
||||
import com.hippo.files.delete
|
||||
import com.hippo.files.find
|
||||
import com.hippo.files.isDirectory
|
||||
import com.hippo.files.list
|
||||
import com.hippo.files.metadataOrNull
|
||||
import com.hippo.files.mkdirs
|
||||
import com.hippo.files.toOkioPath
|
||||
import com.hippo.files.toUri
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import moe.tarsin.snackbar
|
||||
import moe.tarsin.string
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.util.isAtLeastT
|
||||
import com.ehviewer.core.util.launch
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
@@ -45,7 +46,6 @@ import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
|
||||
import com.hippo.ehviewer.ui.tools.awaitSelectItem
|
||||
import com.hippo.ehviewer.ui.tools.awaitSelectTime
|
||||
import com.hippo.ehviewer.util.copyTextToClipboard
|
||||
import com.hippo.ehviewer.util.isAtLeastT
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
@@ -53,6 +53,8 @@ import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.ehviewer.core.database.model.Filter
|
||||
import com.ehviewer.core.database.model.FilterMode
|
||||
import com.ehviewer.core.i18n.R
|
||||
import com.ehviewer.core.ui.util.Await
|
||||
import com.ehviewer.core.ui.util.thenIf
|
||||
@@ -64,8 +66,6 @@ import com.hippo.ehviewer.client.EhFilter.forget
|
||||
import com.hippo.ehviewer.client.EhFilter.remember
|
||||
import com.hippo.ehviewer.client.EhFilter.trigger
|
||||
import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
import com.hippo.ehviewer.dao.FilterMode
|
||||
import com.hippo.ehviewer.ui.Screen
|
||||
import com.hippo.ehviewer.ui.main.NavigationIcon
|
||||
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
|
||||
|
||||
@@ -18,7 +18,7 @@ import com.hippo.ehviewer.ui.Screen
|
||||
import com.hippo.ehviewer.ui.main.NavigationIcon
|
||||
import com.hippo.ehviewer.ui.openBrowser
|
||||
import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults
|
||||
import com.mikepenz.aboutlibraries.ui.compose.android.rememberLibraries
|
||||
import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
|
||||
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootGraph
|
||||
@@ -37,7 +37,7 @@ fun AnimatedVisibilityScope.LicenseScreen(navigator: DestinationsNavigator) = Sc
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
val libraries by rememberLibraries(com.hippo.ehviewer.R.raw.aboutlibraries)
|
||||
val libraries by produceLibraries(com.hippo.ehviewer.R.raw.aboutlibraries)
|
||||
LibrariesContainer(
|
||||
libraries = libraries,
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
|
||||
Loaded 100 of 238 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user