Compare commits

...
77 Commits
Author SHA1 Message Date
FooIbar e51d54e346 Prepare changelog for 1.14.3
Releases / build (push) Has been cancelled
2025-09-01 20:42:02 +08:00
renovate[bot] e06b1f0177 Lock file maintenance (#2586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 12:25:48 +00:00
renovate[bot] 178a870608 Update kotlin monorepo to v2.2.20-RC2 (#2585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 12:23:11 +00:00
FooIbar a7d3d7c651 Prepare changelog for 1.14.3 2025-09-01 20:13:28 +08:00
renovate[bot] c13024f1c7 Update AGP to v8.12.2 (#2583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-30 16:14:02 +00:00
FooIbar c53c0ef727 Fix similar galleries by artist tag 2025-08-31 00:02:06 +08:00
FooIbar cd93fe2646 Revert "Work around crash when dismissing filter settings"
This reverts commit afe638fb90.
2025-08-28 11:12:21 +08:00
renovate[bot] 7b506058a4 Update dependency androidx.compose:compose-bom-alpha to v2025.08.01 (#2580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 00:52:26 +00:00
renovate[bot] f658f74f35 Update androidx.lifecycle to v2.10.0-alpha03 (#2578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 20:41:58 +00:00
renovate[bot] eae5efa69d Update dependency androidx.activity:activity-compose to v1.12.0-alpha07 (#2579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 20:41:22 +00:00
FooIbar 4e6051085e Simplify repository content filtering 2025-08-26 20:01:02 +08:00
FooIbar f174ccfc04 Migrate to the new context menu APIs 2025-08-26 10:02:21 +08:00
FooIbar e1252d0554 Support searching download labels with label: 2025-08-25 21:58:18 +08:00
FooIbar afe638fb90 Work around crash when dismissing filter settings 2025-08-25 18:12:39 +08:00
renovate[bot] 17699ce889 Lock file maintenance (#2572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 01:58:40 +00:00
renovate[bot] 4afa06b7b9 Update actions/setup-java action to v5 (#2568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-21 06:39:56 +00:00
FooIbar 2d02815aba Extract common UI components part 1/n 2025-08-20 14:45:18 +08:00
renovate[bot] ae1824c6a2 Update dependency com.google.devtools.ksp to v2.2.20-RC-2.0.2 (#2567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-20 03:40:50 +00:00
renovate[bot] d63cc39478 Update kotlin monorepo to v2.2.20-RC (#2565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 08:38:25 +00:00
FooIbar 51d6775429 Remove unnecessary JNI lib and main thread check
The actual disk I/O will perform in the scope of DataStore.
2025-08-19 12:07:34 +08:00
renovate[bot] 35d92f1386 Update dependency sh.calvin.reorderable:reorderable to v3 (#2562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 22:27:32 +00:00
renovate[bot] ba0ef6dccc Update AGP to v8.12.1 (#2561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 17:34:26 +00:00
renovate[bot] 2a325e6700 Lock file maintenance (#2559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 01:05:47 +00:00
FooIbar 4e7566f06d Fix aboutlibraries.json being out-of-date 2025-08-17 17:06:35 +08:00
FooIbar d95bf4b71d Prepare for next iteration 2025-08-16 22:24:23 +08:00
FooIbar 43ddf6ccc4 Prepare for release 1.14.2
Releases / build (push) Has been cancelled
2025-08-16 22:23:45 +08:00
FooIbar b55da640e1 Prepare changelog for 1.14.2 2025-08-16 22:19:58 +08:00
FooIbar 90534c3c9f Remove confusing connect timeout configuration 2025-08-16 22:19:58 +08:00
FooIbar 88c9e5e902 Support new tag namespace: "location:"
Treat it as "other:".
2025-08-16 17:21:53 +08:00
FooIbar ac41117a78 [no ci] Tweak Rust toolchain update schedule 2025-08-15 17:51:31 +08:00
renovate[bot] 2418c5f625 Update dependency rust-nightly to v2025-08-15 (#2554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 08:33:52 +00:00
FooIbar 52156aa9ff [no ci] Let renovate update NDK 2025-08-15 16:23:17 +08:00
renovate[bot]andFooIbar 6010fb69a4 Update dependency rust-nightly to v2025-08-14 (#2553)
* Update dependency rust-nightly to v2025-08-14

* Fix build

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
2025-08-15 04:59:20 +00:00
FooIbar d5abbb3584 Stop speed tracker immediately if download is done
Subsequent disk I/O will make the reported download speed inaccurate.
2025-08-14 19:25:03 +08:00
FooIbar 8c27504ee2 Update Rust nightly toolchain every ~6 weeks
Follow the stable release cycle.

[no ci]
2025-08-14 15:48:47 +08:00
FooIbar 8a20525da3 Remove obsolete workaround 2025-08-14 11:14:33 +08:00
renovate[bot] c7b8326b52 Update dependency androidx.core:core-ktx to v1.17.0 (#2550)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-14 00:53:52 +00:00
renovate[bot] ee8b80651e Update dependency androidx.compose:compose-bom-alpha to v2025.08.00 (#2549)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-14 00:53:37 +00:00
renovate[bot] 589676c52a Update dependency androidx.test.uiautomator:uiautomator to v2.4.0-alpha06 (#2548)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-13 22:54:04 +00:00
FooIbar 83085d5f9a Allow disabling download speed check 2025-08-13 20:37:43 +08:00
FooIbar a1a204d0d5 Refactor settings observation 2025-08-13 00:42:20 +08:00
FooIbar 9a43c36758 KMP DataStorePreferences 2025-08-12 10:40:45 +08:00
renovate[bot] e017efe35f Update actions/checkout action to v5 (#2542)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 13:57:50 +00:00
renovate[bot] 1645c17ab7 Lock file maintenance (#2541)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 02:33:12 +00:00
FooIbar 075b800932 Exclude CMP resources dependency 2025-08-08 17:19:50 +08:00
FooIbar 56f9138db4 Misc updates 2025-08-08 17:19:50 +08:00
FooIbar 20c1b7acdb Migrate to moko-resources 2025-08-07 10:40:21 +08:00
FooIbar a10022d797 Move plural string resources to their own files 2025-08-07 10:40:21 +08:00
renovate[bot] aafbbbec38 Update dependency chrome to v139 (#2538)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 21:43:41 +00:00
FooIbar 028a994070 Hack for type-safe accessors in convention plugins 2025-08-05 22:58:52 +08:00
renovate[bot] 581125db62 Lock file maintenance (#2535)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 03:00:33 +00:00
FooIbar 295770002a Extract common build logic to convention plugins 2025-08-03 10:39:03 +08:00
renovate[bot] 1a66d84bd0 Update dependency gradle to v9 (#2532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-01 01:45:06 +00:00
renovate[bot] cd3f51b66d Update AGP to v8.12.0 (#2531)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 18:04:05 +00:00
renovate[bot] 6d8ac4c3da Update dependency com.google.devtools.ksp to v2.2.20-Beta2-2.0.2 (#2530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 18:01:43 +00:00
FooIbar 156382eaad Prepare for next iteration 2025-07-31 18:35:19 +08:00
FooIbar 2c155fe599 Prepare for release 1.14.1
Releases / build (push) Has been cancelled
2025-07-31 18:34:50 +08:00
renovate[bot] 323de69ba0 Update dependency androidx.test.ext:junit to v1.3.0 (#2527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 01:52:48 +00:00
renovate[bot] 0cdc6be34f Update dependency androidx.browser:browser to v1.9.0 (#2526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 01:52:18 +00:00
FooIbar 36c1703763 Renew GitHub token and prepare for a point release 2025-07-31 09:43:10 +08:00
renovate[bot] ee3d2f681c Update dependency androidx.work:work-runtime to v2.10.3 (#2525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 23:05:17 +00:00
renovate[bot] 11ce900868 Update dependency androidx.compose:compose-bom-alpha to v2025.07.01 (#2523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 19:59:25 +00:00
renovate[bot] b936dd2533 Update benchmark to v1.4.0 (#2522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 19:58:11 +00:00
renovate[bot] fab8cd0c1f Update dependency com.squareup.okio:okio-bom to v3.16.0 (#2520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 00:35:57 +00:00
renovate[bot] ec3a962ec8 Update dependency com.squareup.logcat:logcat to v0.4 (#2519)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 13:56:37 +00:00
FooIbar 348813109b Clean up context parameters 2025-07-29 21:46:57 +08:00
renovate[bot] 59598094df Update kotlin monorepo to v2.2.20-Beta2 (#2517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 10:18:22 +00:00
renovate[bot] 8b6ae59eda Update dependency io.ktor:ktor-client-okhttp to v3.2.3 (#2516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 10:17:55 +00:00
FooIbar b3d8653882 Speed up dependency downloading 2025-07-29 18:17:42 +08:00
renovate[bot] 719d1ebbb8 Update xmlutil to v0.91.2 (#2514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 00:21:18 +00:00
FooIbar a23f902e9a Revert "[no ci] Allow blank issue"
This reverts commit 24e36b6283.
2025-07-28 20:25:31 +08:00
renovate[bot] 79559635e1 Lock file maintenance (#2512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 03:06:58 +00:00
FooIbar c57a4e4132 Kotlin 2.2.20-Beta1 and remove custom impl 2025-07-26 23:55:25 +08:00
renovate[bot] c6f7d9d869 Update dependency com.squareup.logcat:logcat to v0.3 (#2509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-25 08:47:54 +00:00
renovate[bot] 9fcddd06e4 Update dependency com.materialkolor:material-kolor to v3.0.1 (#2508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-24 20:54:47 +00:00
FooIbar 3526a2b0e9 [no ci] Add comment about KSP version check 2025-07-24 22:58:06 +08:00
FooIbar 56187aa4dc Prepare for next iteration
(cherry picked from commit 2d02eda9e3)
2025-07-23 23:25:15 +08:00
226 changed files with 1976 additions and 1464 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+3 -3
View File
@@ -25,16 +25,16 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
+22 -11
View File
@@ -12,16 +12,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
@@ -42,6 +42,9 @@ jobs:
with:
cache-read-only: true
- name: Check build-logic
run: ./gradlew :build-logic:convention:check
- name: Spotless Check
run: ./gradlew spotlessCheck
@@ -52,16 +55,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
@@ -79,9 +82,13 @@ jobs:
uses: actions/cache@v4
with:
path: app/.cxx
key: cmake-default-${{ hashFiles('settings.gradle.kts', 'app/src/main/cpp/CMakeLists.txt', 'app/src/main/rust/Cargo.lock') }}
key: cmake-default-${{ hashFiles('rust-toolchain.toml', 'settings.gradle.kts', 'app/src/main/cpp/CMakeLists.txt', 'app/src/main/rust/Cargo.lock') }}
restore-keys: cmake-default-
# https://github.com/mikepenz/AboutLibraries/issues/1225
- name: Generate aboutlibraries.json
run: ./gradlew :app:prepareLibraryDefinitionsDefaultRelease --rerun
- name: Build
run: ./gradlew assembleDefaultRelease
@@ -120,16 +127,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
@@ -147,9 +154,13 @@ jobs:
uses: actions/cache@v4
with:
path: app/.cxx
key: cmake-marshmallow-${{ hashFiles('settings.gradle.kts', 'app/src/main/cpp/CMakeLists.txt', 'app/src/main/rust/Cargo.lock') }}
key: cmake-marshmallow-${{ hashFiles('rust-toolchain.toml', 'settings.gradle.kts', 'app/src/main/cpp/CMakeLists.txt', 'app/src/main/rust/Cargo.lock') }}
restore-keys: cmake-marshmallow-
# https://github.com/mikepenz/AboutLibraries/issues/1225
- name: Generate aboutlibraries.json
run: ./gradlew :app:prepareLibraryDefinitionsMarshmallowRelease --rerun
- name: Build
run: ./gradlew assembleMarshmallowRelease
+9 -3
View File
@@ -19,16 +19,16 @@ jobs:
sudo udevadm trigger --name-match=kvm
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Java
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup target add armv7-linux-androideabi aarch64-linux-android x86_64-linux-android
run: rustup target add armv7-linux-androideabi
- name: Rust Cache
uses: Swatinem/rust-cache@v2
@@ -51,6 +51,12 @@ jobs:
run: ./gradlew generateBaselineProfile
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
# https://github.com/mikepenz/AboutLibraries/issues/1225
- name: Generate aboutlibraries.json
run: ./gradlew
:app:prepareLibraryDefinitionsDefaultRelease --rerun
:app:prepareLibraryDefinitionsMarshmallowRelease --rerun
- name: Build
run: ./gradlew assembleRelease
+3 -2
View File
@@ -1,8 +1,9 @@
.gradle
/local.properties
.DS_Store
/build
/.kotlin
build
.cxx
.kotlin
*.iml
.idea
.vscode
+32
View File
@@ -1,5 +1,37 @@
# 变更日志
## [1.14.3] - 2025-09-01
### 新功能
* 支持以 label: 语法搜索下载标签
### Bug 修复
* [阅读器] 关闭滤镜设置时崩溃 [#2573](https://github.com/FooIbar/EhViewer/issues/2573)
* 某些情况下搜索相似画廊时出错
## [1.14.2] - 2025-08-16
### 改进
* [下载] 支持将最低下载速度设为 0 [#2545](https://github.com/FooIbar/EhViewer/issues/2545)
* [下载] 移除令人困惑的连接超时设置 [#2556](https://github.com/FooIbar/EhViewer/issues/2556)
### Bug 修复
* 下载速度计算不准确导致下载失败误报 [#2545](https://github.com/FooIbar/EhViewer/issues/2545)
* 无法解析带有 location: 标签的画廊
## [1.14.1] - 2025-07-31
### 改进
* 更新 GitHub 访问令牌
## [1.14.0] - 2025-07-23
### 重大变更
-2
View File
@@ -1,2 +0,0 @@
/build
/.cxx
+16 -55
View File
@@ -1,20 +1,15 @@
import com.mikepenz.aboutlibraries.plugin.DuplicateMode
import com.mikepenz.aboutlibraries.plugin.DuplicateRule
import java.util.regex.Pattern
import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
val isRelease: Boolean
get() = gradle.startParameter.taskNames.any { it.contains("Release") }
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.ehviewer.android.application)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.spotless)
alias(libs.plugins.aboutlibrariesPlugin)
alias(libs.plugins.composeCompilerReportGenerator)
alias(libs.plugins.baselineprofile)
}
@@ -61,7 +56,7 @@ android {
defaultConfig {
applicationId = "moe.tarsin.ehviewer"
versionCode = 180063
versionName = "1.14.0"
versionName = "1.14.3"
// versionNameSuffix = "-SNAPSHOT"
buildConfigField("String", "RAW_VERSION_NAME", "\"$versionName${versionNameSuffix.orEmpty()}\"")
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
@@ -79,9 +74,10 @@ android {
flavorDimensions += "api"
productFlavors {
create("default")
create("default") {
minSdk = 26
}
create("marshmallow") {
minSdk = 23
applicationIdSuffix = ".m"
versionNameSuffix = "-M"
}
@@ -97,16 +93,13 @@ android {
isCoreLibraryDesugaringEnabled = true
}
lint {
checkReleaseBuilds = false
disable += setOf("MissingTranslation", "MissingQuantity")
error += setOf("InlinedApi")
}
packaging {
dex {
useLegacyPackaging = false
}
jniLibs {
excludes += "**/libdatastore_shared_counter.so" // DataStore multi-process
}
resources {
// Required by Layout Inspector
pickFirsts += "/META-INF/androidx.compose.ui_ui.version"
@@ -176,6 +169,10 @@ baselineProfile {
}
dependencies {
implementation(projects.core.data)
implementation(projects.core.i18n)
implementation(projects.core.ui)
// https://developer.android.com/jetpack/androidx/releases/activity
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
@@ -189,7 +186,10 @@ dependencies {
implementation(libs.compose.destinations.core)
ksp(libs.compose.destinations.compiler)
implementation(libs.compose.preference)
implementation(libs.compose.preference) {
// R8 won't remove it because it adds a content provider
exclude(group = "org.jetbrains.compose.components", module = "components-resources")
}
implementation(libs.androidx.core)
implementation(libs.androidx.core.splashscreen)
@@ -254,37 +254,11 @@ dependencies {
}
kotlin {
jvmToolchain(21)
// https://kotlinlang.org/docs/gradle-compiler-options.html#all-compiler-options
compilerOptions {
jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
progressiveMode = true
optIn.addAll(
"coil3.annotation.ExperimentalCoilApi",
"androidx.compose.foundation.layout.ExperimentalLayoutApi",
"androidx.compose.material3.ExperimentalMaterial3Api",
"androidx.compose.material3.ExperimentalMaterial3ExpressiveApi",
"androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi",
"androidx.compose.ui.ExperimentalComposeUiApi",
"androidx.compose.foundation.ExperimentalFoundationApi",
"androidx.compose.animation.ExperimentalAnimationApi",
"androidx.compose.animation.ExperimentalSharedTransitionApi",
"androidx.compose.runtime.ExperimentalComposeRuntimeApi",
"androidx.paging.ExperimentalPagingApi",
"kotlin.ExperimentalStdlibApi",
"kotlin.concurrent.atomics.ExperimentalAtomicApi",
"kotlin.contracts.ExperimentalContracts",
"kotlin.time.ExperimentalTime",
"kotlinx.coroutines.ExperimentalCoroutinesApi",
"kotlinx.coroutines.FlowPreview",
"kotlinx.serialization.ExperimentalSerializationApi",
"splitties.experimental.ExperimentalSplittiesApi",
"splitties.preferences.DataStorePreferencesPreview",
)
freeCompilerArgs.addAll(
"-Xcontext-parameters",
"-Xannotation-default-target=param-property",
)
}
}
@@ -304,16 +278,3 @@ aboutLibraries {
duplicationRule = DuplicateRule.GROUP
}
}
val ktlintVersion = libs.ktlint.get().version
spotless {
kotlin {
// https://github.com/diffplug/spotless/issues/111
target("src/**/*.kt")
ktlint(ktlintVersion)
}
kotlinGradle {
ktlint(ktlintVersion)
}
}
+2
View File
@@ -129,6 +129,8 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Android")
set(ANDROID 1)
endif ()
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ../../../../rust-toolchain.toml)
unset(Rust_TOOLCHAIN CACHE)
FetchContent_Declare(
Corrosion
GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git
@@ -37,6 +37,10 @@ import coil3.request.allowRgb565
import coil3.request.crossfade
import coil3.serviceLoaderEnabled
import coil3.util.DebugLogger
import com.ehviewer.core.ui.util.initSETConnection
import com.ehviewer.core.util.launchIO
import com.ehviewer.core.util.launchUI
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.coil.AnimatedWebPDecoder
import com.hippo.ehviewer.coil.CropBorderInterceptor
@@ -59,7 +63,6 @@ import com.hippo.ehviewer.ui.keepNoMediaFileStatus
import com.hippo.ehviewer.ui.lockObserver
import com.hippo.ehviewer.ui.screen.detailCache
import com.hippo.ehviewer.ui.tools.dataStateFlow
import com.hippo.ehviewer.ui.tools.initSETConnection
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.CrashHandler
import com.hippo.ehviewer.util.FavouriteStatusRouter
@@ -70,9 +73,6 @@ 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.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
@@ -130,7 +130,7 @@ class EhApplication :
launchIO { dataStateFlow.value }
launchIO { OSUtils.totalMemory }
launch {
if (DownloadManager.labelList.isNotEmpty() && Settings.downloadFilterMode.key !in Settings.prefs) {
if (DownloadManager.labelList.isNotEmpty() && Settings.downloadFilterMode !in Settings.snapshot()) {
Settings.downloadFilterMode.value = DownloadsFilterMode.CUSTOM.flag
}
DownloadManager.readMetadataFromLocal()
@@ -146,6 +146,8 @@ object EhDB {
dao.fill(raw.position)
}
suspend fun searchDownloadLabel(keyword: String, limit: Int) = db.downloadLabelDao().search("%$keyword%", limit)
suspend fun putDownloadArtist(gid: Long, artists: List<DownloadArtist>) {
if (artists.isNotEmpty()) {
val dao = db.downloadArtistDao()
@@ -9,6 +9,9 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import com.ehviewer.core.preferences.DataStorePreferences
import com.ehviewer.core.preferences.PrefDelegate
import com.ehviewer.core.preferences.edit
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.data.FavListUrlBuilder
import com.hippo.ehviewer.download.DownloadsFilterMode
@@ -25,47 +28,12 @@ import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.map
import splitties.preferences.BoolPref
import splitties.preferences.DataStorePreferences
import splitties.preferences.FloatPref
import splitties.preferences.IntPref
import splitties.preferences.LongPref
import splitties.preferences.PrefDelegate
import splitties.preferences.StringOrNullPref
import splitties.preferences.StringPref
import splitties.preferences.StringSetOrNullPref
import splitties.preferences.StringSetPref
import splitties.preferences.edit
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
fun <T> PrefDelegate<T>.getValue(): T = when (this) {
is BoolPref -> value
is IntPref -> value
is FloatPref -> value
is LongPref -> value
is StringPref -> value
is StringOrNullPref -> value
is StringSetPref -> value
is StringSetOrNullPref -> value
} as T
@Suppress("UNCHECKED_CAST")
fun <T> PrefDelegate<T>.setValue(newValue: T) = when (this) {
is BoolPref -> value = newValue as Boolean
is IntPref -> value = newValue as Int
is FloatPref -> value = newValue as Float
is LongPref -> value = newValue as Long
is StringPref -> value = newValue as String
is StringOrNullPref -> value = newValue as? String
is StringSetPref -> value = newValue as Set<String?>
is StringSetOrNullPref -> value = newValue as? Set<String?>
}
@Stable
@Composable
inline fun <R, T> PrefDelegate<T>.collectAsState(crossinline transform: @DisallowComposableCalls (T) -> R): State<R> {
val flow = remember { valueFlow().map { transform(it) } }
val init = getValue()
val init = value
return flow.collectAsState(transform(init))
}
@@ -73,32 +41,28 @@ inline fun <R, T> PrefDelegate<T>.collectAsState(crossinline transform: @Disallo
@Composable
fun <T> PrefDelegate<T>.collectAsState(): State<T> {
val flow = remember { valueFlow() }
val init = getValue()
val init = value
return flow.collectAsState(init)
}
@Stable
@Composable
fun <T> PrefDelegate<T>.asMutableState(): MutableState<T> {
val flow = remember { valueFlow() }
val init = getValue()
val readOnly = flow.collectAsState(init)
val readOnly = collectAsState()
return remember {
object : MutableState<T> {
override var value: T
get() = readOnly.value
set(value) {
setValue(value)
this@asMutableState.value = value
}
override fun component1() = readOnly.value
override fun component2() = ::setValue
override fun component2() = this@asMutableState::value::set
}
}
}
object Settings : DataStorePreferences(null) {
private const val KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations"
@Suppress("ktlint:standard:backing-property-naming")
private val _favFlow = MutableSharedFlow<Unit>()
val favChangesFlow = _favFlow.debounce(1000)
@@ -107,9 +71,9 @@ object Settings : DataStorePreferences(null) {
var favCloudCount by intPref("fav_cloud", 0).emitTo(_favFlow)
// Eh
val gallerySite = intPref("gallery_site_2", 0).observed { updateWhenGallerySiteChanges() }
val gallerySite = intPref("gallery_site_2", 0).observed(::updateWhenGallerySiteChanges)
val defaultFavSlot = intPref("default_favorite_slot", -2)
val theme = intPref("theme_2", -1).observed { updateWhenThemeChanges() }
val theme = intPref("theme_2", -1).observed(::updateWhenThemeChanges)
val blackDarkTheme = boolPref("black_dark_theme", false)
val harmonizeCategoryColor = boolPref("harmonize_category_color", true)
val launchPage = intPref("launch_page_2", 0)
@@ -121,17 +85,16 @@ object Settings : DataStorePreferences(null) {
val showVoteStatus = boolPref("show_vote_status", false)
val showComments = boolPref("show_gallery_comments", true)
val commentThreshold = intPref("comment_threshold", -100)
val showTagTranslations = boolPref(KEY_SHOW_TAG_TRANSLATIONS, false).observed { updateWhenTagTranslationChanges() }
val showTagTranslations = boolPref("show_tag_translations", false).observed(::updateWhenTagTranslationChanges)
val meteredNetworkWarning = boolPref("cellular_network_warning", false)
val showJpnTitle = boolPref("show_jpn_title", false)
val requestNews = boolPref("request_news", false).observed { updateWhenRequestNewsChanges() }
val hideHvEvents = boolPref("hide_hv_events", false)
// Download
val mediaScan = boolPref("media_scan", false).observed { updateWhenKeepMediaStatusChanges() }
val mediaScan = boolPref("media_scan", false).observed(::updateWhenKeepMediaStatusChanges)
val multiThreadDownload = intPref("download_thread_2", 3)
val downloadDelay = intPref("download_delay_3", 1000)
val connTimeout = intPref("conn_timeout", 10)
val timeoutSpeed = intPref("timeout_speed_level", 6)
val preloadImage = intPref("preload_image_2", 5)
val downloadOriginImage = boolPref("download_origin_image", false)
@@ -167,15 +130,15 @@ object Settings : DataStorePreferences(null) {
val needSignIn = boolPref("need_sign_in", true)
val gridView = boolPref("grid_view", false)
val qSSaveProgress = boolPref("qs_save_progress", true)
val displayName = stringOrNullPref("display_name", null)
val avatar = stringOrNullPref("avatar", null)
val recentDownloadLabel = stringOrNullPref("recent_download_label", null)
val displayName = stringOrNullPref("display_name")
val avatar = stringOrNullPref("avatar")
val recentDownloadLabel = stringOrNullPref("recent_download_label")
var downloadScheme by stringOrNullPref("image_scheme", null)
var downloadAuthority by stringOrNullPref("image_authority", null)
var downloadPath by stringOrNullPref("image_path", null)
var downloadQuery by stringOrNullPref("image_query", null)
var downloadFragment by stringOrNullPref("image_fragment", null)
var downloadScheme by stringOrNullPref("image_scheme")
var downloadAuthority by stringOrNullPref("image_authority")
var downloadPath by stringOrNullPref("image_path")
var downloadQuery by stringOrNullPref("image_query")
var downloadFragment by stringOrNullPref("image_fragment")
var archivePasswds by stringSetPref("archive_passwds")
var appLinkVerifyTip by boolPref("app_link_verify_tip", false)
var hasDefaultDownloadLabel by boolPref("has_default_download_label", false)
@@ -185,7 +148,7 @@ object Settings : DataStorePreferences(null) {
var requestNewsTime by intPref("request_news_time", 0).observed { updateWhenRequestNewsChanges() }
var lastDawnDays by intPref("last_dawn_days", 0)
var recentToplist by stringPref("recent_toplist", "11")
var defaultDownloadLabel by stringOrNullPref("default_download_label", null)
var defaultDownloadLabel by stringOrNullPref("default_download_label")
var lastUpdateTime by longPref("last_update_time", BuildConfig.COMMIT_TIME)
// Reader
@@ -223,43 +186,39 @@ object Settings : DataStorePreferences(null) {
val stripExtraneousAds = boolPref("strip_extraneous_ads", false)
init {
if ("CN" == Locale.getDefault().country) {
edit {
if (KEY_SHOW_TAG_TRANSLATIONS !in prefs) showTagTranslations.value = true
edit { pref ->
if ("CN" == Locale.getDefault().country) {
if (showTagTranslations !in pref) pref[showTagTranslations] = true
}
if (pref[downloadFilterMode] == DownloadsFilterMode.ARTIST.flag && pref[recentDownloadLabel] == null) {
pref[recentDownloadLabel] = ""
}
}
if (downloadFilterMode.value == DownloadsFilterMode.ARTIST.flag && recentDownloadLabel.value == null) {
recentDownloadLabel.value = ""
}
}
interface Delegate<R> {
val flowGetter: () -> Flow<Unit>
fun changesFlow(): Flow<Unit>
operator fun getValue(thisRef: Any?, prop: KProperty<*>?): R
operator fun setValue(thisRef: Any?, prop: KProperty<*>?, value: R)
}
private fun intArrayPref(key: String, count: Int) = object : Delegate<IntArray> {
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
@Suppress("ktlint:standard:backing-property-naming")
private var _value = (0 until count).map { intPref("${key}_$it", 0) }.toTypedArray()
override fun getValue(thisRef: Any?, prop: KProperty<*>?): IntArray = _value.map { it.value }.toIntArray()
private val delegates = Array(count) { intPref("${key}_$it", 0) }
override fun changesFlow(): Flow<Unit> = delegates.asFlow().flatMapMerge { it.changesFlow() }.conflate()
override fun getValue(thisRef: Any?, prop: KProperty<*>?) = IntArray(delegates.size) { delegates[it].value }
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: IntArray) {
check(value.size == count)
edit { value.zip(_value) { v, d -> d.value = v } }
edit { pref -> value.zip(delegates) { v, d -> pref[d] = v } }
}
}
private fun stringArrayPref(key: String, count: Int, defMetaValue: String) = object : Delegate<Array<String>> {
override val flowGetter: () -> Flow<Unit> = { _value.asFlow().flatMapMerge { it.changesFlow() }.conflate() }
@Suppress("ktlint:standard:backing-property-naming")
private var _value = (0 until count).map { stringPref("${key}_$it", "$defMetaValue $it") }.toTypedArray()
override fun getValue(thisRef: Any?, prop: KProperty<*>?): Array<String> = _value.map { it.value }.toTypedArray()
private val delegates = Array(count) { stringPref("${key}_$it", "$defMetaValue $it") }
override fun changesFlow(): Flow<Unit> = delegates.asFlow().flatMapMerge { it.changesFlow() }.conflate()
override fun getValue(thisRef: Any?, prop: KProperty<*>?) = Array(delegates.size) { delegates[it].value }
override fun setValue(thisRef: Any?, prop: KProperty<*>?, value: Array<String>) {
check(value.size == count)
edit { value.zip(_value) { v, d -> d.value = v } }
edit { pref -> value.zip(delegates) { v, d -> pref[d] = v } }
}
}
}
@@ -2,68 +2,73 @@ package com.hippo.ehviewer
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.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.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.launch
import splitties.init.appCtx
import splitties.preferences.PrefDelegate
import splitties.systemservices.uiModeManager
private val collectScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun <T, R : PrefDelegate<T>> R.observed(func: (Unit) -> Unit) = apply { collectScope.launch { changesFlow().collect(func) } }
fun <T, R : Settings.Delegate<T>> R.observed(func: (Unit) -> Unit) = apply { collectScope.launch { flowGetter().collect(func) } }
fun <T, R : Settings.Delegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flowGetter().collect { flow.emit(Unit) } } }
fun <T, R : PrefDelegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { changesFlow().collect { flow.emit(Unit) } } }
private const val TAG = "SettingsCollector"
fun updateWhenKeepMediaStatusChanges() {
collectScope.launchIO {
runCatching {
keepNoMediaFileStatus()
private val collectScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
fun <T, R : PrefDelegate<T>> R.observed(func: suspend (T) -> Unit) = apply { collectScope.launch { valueFlow().drop(1).collectLatest(func) } }
fun <T, R : Settings.Delegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flow.emitAll(changesFlow()) } }
fun <T, R : PrefDelegate<T>> R.emitTo(flow: MutableSharedFlow<Unit>) = apply { collectScope.launch { flow.emitAll(changesFlow()) } }
suspend fun updateWhenKeepMediaStatusChanges(mediaScan: Boolean) {
withIOContext {
catch {
keepNoMediaFileStatus(mediaScan = mediaScan)
}.onLeft {
logcat(TAG, it)
}
}
}
fun updateWhenThemeChanges() {
collectScope.launch {
delay(100) // Avoid recompose being cancelled
if (isAtLeastS) {
val mode = when (Settings.theme.value) {
AppCompatDelegate.MODE_NIGHT_NO -> UiModeManager.MODE_NIGHT_NO
AppCompatDelegate.MODE_NIGHT_YES -> UiModeManager.MODE_NIGHT_YES
else -> UiModeManager.MODE_NIGHT_AUTO
}
uiModeManager.setApplicationNightMode(mode)
suspend fun updateWhenThemeChanges(theme: Int) {
delay(100) // Avoid recompose being cancelled
if (isAtLeastS) {
val mode = when (theme) {
AppCompatDelegate.MODE_NIGHT_NO -> UiModeManager.MODE_NIGHT_NO
AppCompatDelegate.MODE_NIGHT_YES -> UiModeManager.MODE_NIGHT_YES
else -> UiModeManager.MODE_NIGHT_AUTO
}
AppCompatDelegate.setDefaultNightMode(Settings.theme.value)
uiModeManager.setApplicationNightMode(mode)
}
AppCompatDelegate.setDefaultNightMode(theme)
}
fun updateWhenRequestNewsChanges() {
updateDailyCheckWork(appCtx)
}
fun updateWhenGallerySiteChanges() {
suspend fun updateWhenGallerySiteChanges(gallerySite: Int) {
if (Settings.hasSignedIn.value) {
collectScope.launchIO {
runCatching {
EhEngine.getUConfig()
}.onFailure {
logcat(it)
}
catch {
EhEngine.getUConfig(gallerySite)
}.onLeft {
logcat(TAG, it)
}
}
}
fun updateWhenTagTranslationChanges() {
EhTagDatabase.launchUpdate()
fun updateWhenTagTranslationChanges(enabled: Boolean) {
if (enabled) {
EhTagDatabase.launchUpdate()
}
}
@@ -21,8 +21,8 @@ import arrow.core.partially2
import arrow.core.right
import arrow.fx.coroutines.parMap
import arrow.fx.coroutines.parZip
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.FavListUrlBuilder
@@ -212,7 +212,8 @@ object EhEngine {
return ehRequest(url, EhUrl.URL_FORUMS).fetchUsingAsByteBuffer(ProfileParser::parse)
}
suspend fun getUConfig(url: String = EhUrl.uConfigUrl) {
suspend fun getUConfig(gallerySite: Int) {
val url = EhUrl.getUConfigUrl(gallerySite)
runSuspendCatching {
ehRequest(url).fetchUsingAsByteBuffer(UserConfigParser::parse)
}.onFailure { throwable ->
@@ -68,6 +68,7 @@ object EhTagDatabase : CoroutineScope {
// Copied from https://github.com/EhTagTranslation/EhSyringe/blob/c0cf88da04081072492829673aca34380b14dae6/src/plugin/suggest.ts#L37
// TODO: Optimize
private val nsScore = mapOf(
TagNamespace.Location.prefix to 10f,
TagNamespace.Other.prefix to 10f,
TagNamespace.Female.prefix to 9f,
TagNamespace.Male.prefix to 8.5f,
@@ -82,7 +83,7 @@ object EhTagDatabase : CoroutineScope {
data class Tag(val tag: String, val hint: String?, val score: Float)
context(ctx: Context)
context(_: Context)
fun suggestion(rawKeyword: String, expectTranslate: Boolean): List<Tag> {
val keyword = PREFIXES.fold(rawKeyword) { kwd, pfx -> kwd.removePrefix(pfx) }
if (!initialized || keyword.isEmpty()) return emptyList()
@@ -80,12 +80,11 @@ object EhUrl {
else -> ORIGIN_E
}
val uConfigUrl: String
get() = when (Settings.gallerySite.value) {
SITE_E -> URL_UCONFIG_E
SITE_EX -> URL_UCONFIG_EX
else -> URL_UCONFIG_E
}
fun getUConfigUrl(gallerySite: Int = Settings.gallerySite.value) = when (gallerySite) {
SITE_E -> URL_UCONFIG_E
SITE_EX -> URL_UCONFIG_EX
else -> URL_UCONFIG_E
}
val myTagsUrl: String
get() = when (Settings.gallerySite.value) {
@@ -27,6 +27,7 @@ import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color
import androidx.core.net.toUri
import arrow.core.memoize
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
@@ -38,7 +39,6 @@ import com.hippo.ehviewer.util.addTextToClipboard
import com.materialkolor.hct.Hct
import com.materialkolor.ktx.from
import com.materialkolor.ktx.toColor
import eu.kanade.tachiyomi.util.lang.withUIContext
import splitties.systemservices.downloadManager
object EhUtils {
@@ -16,6 +16,7 @@ sealed class TagNamespace(val value: String, val prefix: String?) {
object Female : TagNamespace("female", "f")
object Group : TagNamespace("group", "g")
object Language : TagNamespace("language", "l")
object Location : TagNamespace("location", "loc")
object Male : TagNamespace("male", "m")
object Mixed : TagNamespace("mixed", "x")
object Other : TagNamespace("other", "o")
@@ -31,6 +32,7 @@ sealed class TagNamespace(val value: String, val prefix: String?) {
Female.value -> Female
Group.value -> Group
Language.value -> Language
Location.value -> Location
Male.value -> Male
Mixed.value -> Mixed
Other.value -> Other
@@ -1,5 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
class CloudflareBypassException : EhException(R.string.cloudflare_bypass_failed)
@@ -1,5 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
class InsufficientFundsException : EhException(R.string.insufficient_funds)
@@ -15,6 +15,6 @@
*/
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
class NoHathClientException() : EhException(R.string.download_archive_failure_no_hath)
@@ -1,5 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
class NoHitsFoundException : EhException(R.string.gallery_list_empty_hit)
@@ -1,5 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
class NotLoggedInException : EhException(R.string.sign_in_first)
@@ -1,5 +1,5 @@
package com.hippo.ehviewer.client.exception
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
class QuotaExceededException : EhException(R.string.error_509)
@@ -19,10 +19,10 @@ import androidx.work.PeriodicWorkRequest
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
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.lang.withIOContext
import eu.kanade.tachiyomi.util.system.logcat
import java.time.Duration
import java.util.concurrent.TimeUnit
@@ -14,6 +14,9 @@ interface DownloadLabelDao {
@Query("SELECT * FROM DOWNLOAD_LABELS ORDER BY POSITION LIMIT :limit OFFSET :offset")
suspend fun list(offset: Int, limit: Int): List<DownloadLabel>
@Query("SELECT LABEL FROM DOWNLOAD_LABELS WHERE LABEL LIKE :pattern ORDER BY POSITION LIMIT :limit")
suspend fun search(pattern: String, limit: Int): List<String>
@Query("UPDATE DOWNLOAD_LABELS SET POSITION = POSITION - 1 WHERE POSITION > :position")
suspend fun fill(position: Int)
@@ -5,6 +5,7 @@ import androidx.room.Delete
import androidx.room.Insert
import androidx.room.MapColumn
import androidx.room.Query
import androidx.room.RoomWarnings
import androidx.room.Transaction
import androidx.room.Update
import androidx.room.Upsert
@@ -12,11 +13,12 @@ import kotlinx.coroutines.flow.Flow
@Dao
interface DownloadsDao {
@Suppress("ktlint:standard:annotation")
// https://issuetracker.google.com/327583152
@Suppress("ktlint:standard:annotation", RoomWarnings.UNNECESSARY_NULLABILITY_IN_DAO_RETURN_TYPE)
@Query("SELECT LABEL, COUNT(*) AS COUNT FROM DOWNLOADS LEFT JOIN DOWNLOAD_LABELS USING(LABEL) GROUP BY LABEL")
fun countByLabel(): Flow<Map<@MapColumn("LABEL") String?, @MapColumn("COUNT") Int>>
@Suppress("ktlint:standard:annotation")
@Suppress("ktlint:standard:annotation", RoomWarnings.UNNECESSARY_NULLABILITY_IN_DAO_RETURN_TYPE)
@Query("SELECT ARTIST, COUNT(*) AS COUNT FROM DOWNLOADS LEFT JOIN DOWNLOAD_ARTISTS USING(GID) GROUP BY ARTIST ORDER BY COUNT DESC, ARTIST")
fun countByArtist(): Flow<Map<@MapColumn("ARTIST") String?, @MapColumn("COUNT") Int>>
@@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.core.util.size
import arrow.fx.coroutines.parMapNotNull
import com.ehviewer.core.preferences.edit
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.data.BaseGalleryInfo
@@ -64,7 +65,6 @@ import logcat.LogPriority
import okio.Path
import okio.Path.Companion.toOkioPath
import okio.Path.Companion.toPath
import splitties.preferences.edit
object DownloadManager : OnSpiderListener, CoroutineScope {
override val coroutineContext = Dispatchers.IO + Job()
@@ -711,9 +711,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
fun start() {
if (currentJob == null) {
currentJob = launch {
tracker.speedFlow().collect { speed ->
updateSpeed(speed.toLong())
}
tracker.speedFlow().collect(::updateSpeed)
}
}
}
@@ -31,7 +31,8 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.savedstate.serialization.decodeFromSavedState
import androidx.savedstate.serialization.encodeToSavedState
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
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
@@ -39,7 +40,6 @@ 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 com.hippo.ehviewer.util.unsafeLazy
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -137,7 +137,7 @@ class DownloadService :
.setAutoCancel(false)
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.addAction(
R.drawable.v_pause_x24,
com.hippo.ehviewer.R.drawable.v_pause_x24,
getString(R.string.stat_download_action_stop_all),
piStopAll,
)
@@ -174,7 +174,7 @@ class DownloadService :
private fun init509Notification(): NotificationHandler {
val builder = NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.ic_baseline_warning_24)
.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(
@@ -53,7 +53,7 @@ suspend inline fun <T> useArchivePageLoader(
{ _, _ -> closeArchive() },
)
check(size > 0) { "Archive have no content!" }
if (needPassword() && archivePasswds.filterNotNull().none(::providePassword)) {
if (needPassword() && archivePasswds.none(::providePassword)) {
archivePasswds += passwdProvider(::providePassword)
}
val loader = install(
@@ -4,6 +4,7 @@ import androidx.collection.SieveCache
import androidx.collection.mutableIntObjectMapOf
import arrow.fx.coroutines.ExitCase
import arrow.fx.coroutines.bracketCase
import com.ehviewer.core.util.withNonCancellableContext
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.image.Image
@@ -13,7 +14,6 @@ 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 eu.kanade.tachiyomi.util.lang.withNonCancellableContext
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.read
@@ -50,19 +50,19 @@ 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.ehviewer.util.updateAndGet
import com.hippo.files.openFileDescriptor
import com.hippo.files.toUri
import java.nio.ByteBuffer
import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.atomics.decrementAndFetch
import kotlin.concurrent.atomics.updateAndFetch
import okio.Path
import splitties.init.appCtx
class Image private constructor(image: CoilImage, private val src: ImageSource) {
val refcnt = AtomicInt(1)
fun pin() = refcnt.updateAndGet { if (it != 0) it + 1 else 0 } != 0
fun pin() = refcnt.updateAndFetch { if (it != 0) it + 1 else 0 } != 0
fun unpin() = (refcnt.decrementAndFetch() == 0).also { if (it) recycle() }
@@ -10,6 +10,7 @@ import android.net.http.UrlRequest
import android.net.http.UrlResponseInfo
import android.os.Build
import androidx.annotation.RequiresExtension
import com.ehviewer.core.preferences.edit
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.util.restartApplication
import io.ktor.client.engine.HttpClientEngineBase
@@ -40,7 +41,6 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.job
import kotlinx.coroutines.suspendCancellableCoroutine
import splitties.preferences.edit
class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cronet") {
// Limit thread to 1 since we are async & non-blocking
@@ -108,7 +108,7 @@ class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cr
val message = error.message.orEmpty()
if ("ERR_FILE_NOT_FOUND" in message || "ERR_PROXY_CONNECTION_FAILED" in message) {
Settings.edit(true) {
enableCronet.value = false
it[enableCronet] = false
}
with(config.context) { restartApplication() }
}
@@ -12,6 +12,7 @@ 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
@@ -60,7 +61,7 @@ fun GalleryInfo.getComicInfo(): ComicInfo {
Group -> groups.addAll(list)
Character -> characters.addAll(list)
Parody -> parodies.addAll(list)
Other -> otherTags.addAll(list)
Location, Other -> otherTags.addAll(list)
Female, Male, Mixed -> ns.prefix.let { prefix ->
list.forEach { tag -> otherTags.add("$prefix:$tag") }
}
@@ -76,7 +77,7 @@ fun GalleryInfo.getComicInfo(): ComicInfo {
Group -> groups.add(tag)
Character -> characters.add(tag)
Parody -> if (tag != TAG_ORIGINAL) parodies.add(tag)
Other -> otherTags.add(tag)
Location, Other -> otherTags.add(tag)
Female, Male, Mixed -> ns.prefix.let { otherTags.add("$it:$tag") }
else -> Unit
}
@@ -24,7 +24,7 @@ import kotlin.time.Duration.Companion.seconds
import kotlin.time.Instant
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -47,7 +47,7 @@ class SpeedTracker(val window: Duration = 1.seconds) {
}
}
fun speedFlow(sample: Duration = 1.seconds) = fixedRate(sample).map {
fun speedFlow(sample: Duration = 1.seconds) = fixedRate(sample).mapNotNull {
mutex.withLock {
start?.let { start ->
val now = Clock.System.now()
@@ -55,8 +55,8 @@ class SpeedTracker(val window: Duration = 1.seconds) {
val window = passed.coerceIn(10.milliseconds, window)
val cutoff = now - window
received.removeIf { time, _ -> time < cutoff }
received.fold(0) { total, _, v -> total + v } / (window / 1.seconds)
} ?: 0.0
(received.fold(0) { total, _, v -> total + v } / (window / 1.seconds)).toLong()
}
}
}
}
@@ -72,16 +72,21 @@ suspend inline fun <R> timeoutBySpeed(
) = coroutineScope {
val onTimeout = AtomicReference<OnTimeout?>(t).let { { e: IOException -> it.exchange(null)?.invoke(e) } }
val watchdog = launch {
delay(Settings.connTimeout.value.seconds)
onTimeout(ConnectTimeoutException(url, Settings.connTimeout.value * 1000L))
val timeout = 10.seconds
delay(timeout)
onTimeout(ConnectTimeoutException(url, timeout.inWholeMilliseconds))
}
val tracker = SpeedTracker(2.seconds)
request {
var prev = 0L
onDownload { done, total ->
val bytesRead = (done - prev).toInt()
tracker.track(bytesRead)
l(total!!, done, bytesRead)
if (done == total!!) {
tracker.reset()
} else {
tracker.track(bytesRead)
}
l(total, done, bytesRead)
prev = done
}
timeout { reset() }
@@ -89,10 +94,10 @@ suspend inline fun <R> timeoutBySpeed(
watchdog.cancel()
resp.status.ensureSuccess()
val speedWatchdog = launch {
val timeoutSpeed = speedLevelToSpeed(Settings.timeoutSpeed.value) * 1024.0
tracker.speedFlow(1.seconds).collect { speed ->
if (timeoutSpeed != 0.0 && speed < timeoutSpeed) {
onTimeout(LowSpeedException(url, speed.toLong()))
val timeoutSpeed = speedLevelToSpeed(Settings.timeoutSpeed.value) * 1024L
tracker.speedFlow().collect { speed ->
if (speed < timeoutSpeed) {
onTimeout(LowSpeedException(url, speed))
}
}
}
@@ -104,7 +109,9 @@ suspend inline fun <R> timeoutBySpeed(
}
}
fun speedLevelToSpeed(level: Int) = 2f.pow(level).roundToInt()
const val MIN_SPEED_LEVEL = 3
fun speedLevelToSpeed(level: Int) = if (level == MIN_SPEED_LEVEL) 0 else 2f.pow(level).roundToInt()
inline fun <T, R> MutableObjectIntMap<T>.fold(initial: R, operation: (acc: R, T, Int) -> R): R {
var accumulator = initial
@@ -20,7 +20,7 @@ import androidx.annotation.IntDef
import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.partially1
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhUrl
@@ -35,8 +35,10 @@ 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.i18n.R
import com.ehviewer.core.ui.component.LabeledCheckbox
import com.ehviewer.core.util.withIOContext
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhUtils
@@ -54,7 +56,6 @@ import com.hippo.ehviewer.download.tempDownloadDir
import com.hippo.ehviewer.ui.destinations.ReaderScreenDestination
import com.hippo.ehviewer.ui.reader.ReaderScreenArgs
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.LabeledCheckbox
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.awaitResult
import com.hippo.ehviewer.ui.tools.awaitSelectDate
@@ -75,7 +76,6 @@ import com.hippo.files.exists
import com.hippo.files.isDirectory
import com.hippo.files.write
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlin.time.Clock
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -100,10 +100,10 @@ private fun ensureNoMediaFile(downloadDir: Path) {
private val lck = Mutex()
suspend fun keepNoMediaFileStatus(downloadDir: Path = downloadLocation) {
suspend fun keepNoMediaFileStatus(downloadDir: Path = downloadLocation, mediaScan: Boolean = Settings.mediaScan.value) {
if (downloadDir.isDirectory) {
lck.withLock {
if (Settings.mediaScan.value) {
if (mediaScan) {
removeNoMediaFile(downloadDir)
} else {
ensureNoMediaFile(downloadDir)
@@ -114,7 +114,7 @@ suspend fun keepNoMediaFileStatus(downloadDir: Path = downloadLocation) {
fun getFavoriteIcon(favorited: Boolean) = if (favorited) Icons.Default.Favorite else Icons.Default.FavoriteBorder
context(_: DialogState, context: MainActivity)
context(_: DialogState, _: MainActivity)
suspend fun startDownload(forceDefault: Boolean, vararg galleryInfos: BaseGalleryInfo) {
if (isAtLeastT) {
requestPermission(Manifest.permission.POST_NOTIFICATIONS)
@@ -10,11 +10,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.ehviewer.core.ui.util.NoopTransitionsVisibilityScope
import com.ehviewer.core.ui.util.TransitionsVisibilityScope
import com.ehviewer.core.ui.util.togetherWith
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.LocalGlobalDialogState
import com.hippo.ehviewer.ui.tools.NoopTransitionsVisibilityScope
import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope
import com.hippo.ehviewer.ui.tools.togetherWith
import com.hippo.ehviewer.util.findActivity
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.CoroutineScope
@@ -24,7 +24,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
@@ -76,7 +76,7 @@ fun GalleryInfoBottomSheet(detail: GalleryDetail) {
LazyColumn(contentPadding = WindowInsets.systemBars.only(WindowInsetsSides.Bottom).asPaddingValues()) {
itemsIndexed(data) { index, (key, content) ->
Row(
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.keyline_margin)).clickable {
modifier = Modifier.padding(horizontal = dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)).clickable {
if (index == INDEX_PARENT) {
if (content != null) {
navWithUrl(content)
@@ -70,7 +70,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.currentCompositeKeyHashCode
@@ -97,14 +96,21 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.navigation.compose.rememberNavController
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.component.LabeledCheckbox
import com.ehviewer.core.ui.component.LocalSideSheetState
import com.ehviewer.core.ui.component.MutableSideSheet
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.withIOContext
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.data.ListUrlBuilder
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
@@ -112,8 +118,6 @@ import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.download.DownloadService
import com.hippo.ehviewer.download.downloadLocation
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.filled.Subscriptions
import com.hippo.ehviewer.ui.destinations.DownloadScreenDestination
import com.hippo.ehviewer.ui.destinations.DownloadsScreenDestination
import com.hippo.ehviewer.ui.destinations.FavouritesScreenDestination
@@ -130,8 +134,6 @@ import com.hippo.ehviewer.ui.screen.asDstWith
import com.hippo.ehviewer.ui.screen.navWithUrl
import com.hippo.ehviewer.ui.settings.showNewVersion
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.LabeledCheckbox
import com.hippo.ehviewer.ui.tools.LocalWindowSizeClass
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.awaitInputText
import com.hippo.ehviewer.updater.AppUpdater
@@ -150,7 +152,6 @@ import com.ramcosta.composedestinations.DestinationsNavHost
import com.ramcosta.composedestinations.spec.Direction
import com.ramcosta.composedestinations.utils.currentDestinationAsState
import com.ramcosta.composedestinations.utils.rememberDestinationsNavigator
import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
@@ -394,7 +395,7 @@ class MainActivity : EhActivity() {
.windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Bottom)),
) {
Image(
painter = painterResource(id = R.drawable.sadpanda_low_poly),
painter = painterResource(id = com.hippo.ehviewer.R.drawable.sadpanda_low_poly),
contentDescription = null,
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
contentScale = ContentScale.FillWidth,
@@ -532,7 +533,6 @@ val LocalNavDrawerState = compositionLocalOf<DrawerState> { error("CompositionLo
val LocalDrawerHandle = compositionLocalOf<SnapshotStateList<Long>> { error("CompositionLocal LocalDrawerHandle not present!") }
val LocalSnackBarHostState = compositionLocalOf<SnackbarHostState> { error("CompositionLocal LocalSnackBarHostState not present!") }
val LocalSnackBarFabPadding = compositionLocalOf<State<Dp>> { error("CompositionLocal LocalSnackBarFabPadding not present!") }
val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope> { error("CompositionLocal LocalSharedTransitionScope not present!") }
@Composable
@@ -29,7 +29,7 @@ import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.registerForAuthenticationResult
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.Settings
context(ctx: Context)
@@ -20,7 +20,7 @@ import android.content.Context
import android.widget.Toast
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.net.toUri
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@@ -32,7 +32,7 @@ fun openBrowser(url: String) {
if (url.isEmpty()) return
try {
intent.launchUrl(ctx, url.toUri())
} catch (e: ActivityNotFoundException) {
} catch (_: ActivityNotFoundException) {
Toast.makeText(ctx, R.string.no_browser_installed, Toast.LENGTH_LONG).show()
}
}
@@ -36,7 +36,7 @@ fun postLogin() = GlobalScope.async(Dispatchers.IO) {
// Get cookies for image limits
launch {
catch {
EhEngine.getUConfig(EhUrl.URL_UCONFIG_E)
EhEngine.getUConfig(EhUrl.SITE_E)
EhCookieStore.flush()
}.onLeft {
logcat(it)
@@ -45,7 +45,7 @@ fun postLogin() = GlobalScope.async(Dispatchers.IO) {
// Sad panda check
catch {
EhEngine.getUConfig(EhUrl.URL_UCONFIG_EX)
EhEngine.getUConfig(EhUrl.SITE_EX)
EhCookieStore.flush()
Settings.gallerySite.value = EhUrl.SITE_EX
}.onLeft {
@@ -58,7 +58,12 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.util.LocalWindowSizeClass
import com.ehviewer.core.ui.util.isExpanded
import com.ehviewer.core.ui.util.thenIf
import com.ehviewer.core.util.launchIO
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhUrl
@@ -66,18 +71,13 @@ import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.ui.Screen
import com.hippo.ehviewer.ui.destinations.WebViewSignInScreenDestination
import com.hippo.ehviewer.ui.openBrowser
import com.hippo.ehviewer.ui.tools.LocalWindowSizeClass
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.isExpanded
import com.hippo.ehviewer.ui.tools.thenIf
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
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.Job
import moe.tarsin.launchIO
import moe.tarsin.navigate
@Destination<RootGraph>(start = true)
@@ -142,7 +142,7 @@ fun AnimatedVisibilityScope.SignInScreen(navigator: DestinationsNavigator) = Scr
fun UsernameAndPasswordTextField() {
OutlinedTextField(
state = username,
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width))
modifier = Modifier.width(dimensionResource(id = com.hippo.ehviewer.R.dimen.single_max_width))
.semantics { contentType = ContentType.Username }
.thenIf(!showUsernameError) { padding(bottom = 16.dp) },
label = { Text(stringResource(R.string.username)) },
@@ -154,7 +154,7 @@ fun AnimatedVisibilityScope.SignInScreen(navigator: DestinationsNavigator) = Scr
)
OutlinedSecureTextField(
state = password,
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width))
modifier = Modifier.width(dimensionResource(id = com.hippo.ehviewer.R.dimen.single_max_width))
.semantics { contentType = ContentType.Password }
.thenIf(!showPasswordError) { padding(bottom = 16.dp) },
label = { Text(stringResource(R.string.password)) },
@@ -180,27 +180,27 @@ fun AnimatedVisibilityScope.SignInScreen(navigator: DestinationsNavigator) = Scr
when {
!windowSizeClass.isExpanded -> {
Column(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).systemBarsPadding().padding(dimensionResource(id = R.dimen.keyline_margin)),
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).systemBarsPadding().padding(dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
painter = painterResource(id = com.hippo.ehviewer.R.drawable.ic_launcher_foreground),
contentDescription = null,
modifier = Modifier.padding(dimensionResource(id = R.dimen.keyline_margin)),
modifier = Modifier.padding(dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)),
)
UsernameAndPasswordTextField()
Text(
text = stringResource(id = R.string.app_waring),
modifier = Modifier.widthIn(max = dimensionResource(id = R.dimen.single_max_width)).padding(top = 24.dp),
modifier = Modifier.widthIn(max = dimensionResource(id = com.hippo.ehviewer.R.dimen.single_max_width)).padding(top = 24.dp),
style = MaterialTheme.typography.titleMedium,
)
Text(
text = stringResource(id = R.string.app_waring_2),
modifier = Modifier.widthIn(max = dimensionResource(id = R.dimen.single_max_width)).padding(top = 12.dp),
modifier = Modifier.widthIn(max = dimensionResource(id = com.hippo.ehviewer.R.dimen.single_max_width)).padding(top = 12.dp),
style = MaterialTheme.typography.titleLarge,
)
Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.keyline_margin)))
Row(modifier = Modifier.padding(top = dimensionResource(R.dimen.keyline_margin))) {
Spacer(modifier = Modifier.height(dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)))
Row(modifier = Modifier.padding(top = dimensionResource(com.hippo.ehviewer.R.dimen.keyline_margin))) {
FilledTonalButton(
onClick = { openBrowser(EhUrl.URL_REGISTER) },
shapes = ButtonDefaults.shapes(),
@@ -255,18 +255,18 @@ fun AnimatedVisibilityScope.SignInScreen(navigator: DestinationsNavigator) = Scr
}
else -> {
Row(
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).systemBarsPadding().padding(dimensionResource(id = R.dimen.keyline_margin)),
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()).systemBarsPadding().padding(dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.width(dimensionResource(id = R.dimen.signinscreen_landscape_caption_frame_width)).padding(dimensionResource(id = R.dimen.keyline_margin)),
modifier = Modifier.width(dimensionResource(id = com.hippo.ehviewer.R.dimen.signinscreen_landscape_caption_frame_width)).padding(dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
painter = painterResource(id = com.hippo.ehviewer.R.drawable.ic_launcher_foreground),
contentDescription = null,
alignment = Alignment.Center,
modifier = Modifier.padding(dimensionResource(id = R.dimen.keyline_margin)),
modifier = Modifier.padding(dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)),
)
Text(
text = stringResource(id = R.string.app_waring),
@@ -22,7 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.client.parser.Archive
import com.hippo.ehviewer.client.parser.Funds
@@ -43,7 +43,11 @@ import arrow.core.none
import arrow.core.some
import coil3.compose.AsyncImage
import coil3.network.HttpException
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.component.RollingNumber
import com.ehviewer.core.ui.component.RollingNumberPlaceholder
import com.ehviewer.core.util.launch
import com.ehviewer.core.util.launchIO
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhUtils
@@ -65,8 +69,6 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import moe.tarsin.coroutines.runSwallowingWithUI
import moe.tarsin.launch
import moe.tarsin.launchIO
private val limitScope = CoroutineScope(Dispatchers.IO)
private val refreshEvent = MutableSharedFlow<Unit>()
@@ -107,7 +109,7 @@ fun AvatarIcon() {
horizontalAlignment = Alignment.CenterHorizontally,
) {
CircularWavyProgressIndicator()
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin)))
Spacer(modifier = Modifier.size(dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)))
Text(text = placeholder)
}
}
@@ -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.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.component.CrystalCard
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.CrystalCard
import com.hippo.ehviewer.ui.tools.GalleryListCardRating
import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope
import com.hippo.ehviewer.ui.tools.listThumbGenerator
import com.hippo.ehviewer.util.FileUtils
@Composable
@@ -18,13 +18,13 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
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.SETNodeGenerator
import com.hippo.ehviewer.ui.tools.SharedElementBox
import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope
import com.hippo.ehviewer.ui.tools.shouldCrop
import com.hippo.ehviewer.ui.tools.thenIf
@Composable
@NonRestartableComposable
@@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import com.ehviewer.core.ui.component.RollingNumber
@Composable
fun FundsItem(
@@ -23,7 +23,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.text.parseAsHtml
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.client.data.GalleryComment
import com.hippo.ehviewer.ui.legacy.CoilImageGetter
import com.hippo.ehviewer.ui.legacy.LinkifyTextView
@@ -50,7 +50,7 @@ fun GalleryCommentCard(
},
modifier = modifier,
) {
val margin = dimensionResource(id = R.dimen.keyline_margin)
val margin = dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)
Row(
modifier = Modifier.padding(horizontal = margin, vertical = 8.dp).fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
@@ -32,14 +32,14 @@ import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
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
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.big.SadAndroid
import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope
import com.hippo.ehviewer.ui.tools.detailThumbGenerator
@Composable
fun GalleryDetailHeaderInfoCard(
@@ -91,21 +91,21 @@ fun GalleryDetailHeaderCard(
EhThumbCard(
key = remember(info.gid) { info },
modifier = Modifier.size(
dimensionResource(id = R.dimen.gallery_detail_thumb_width),
dimensionResource(id = R.dimen.gallery_detail_thumb_height),
dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_detail_thumb_width),
dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_detail_thumb_height),
),
)
}
Spacer(modifier = Modifier.weight(0.5F))
Column(
modifier = Modifier.height(dimensionResource(id = R.dimen.gallery_detail_thumb_height)),
modifier = Modifier.height(dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_detail_thumb_height)),
horizontalAlignment = Alignment.End,
) {
(info as? GalleryDetail)?.let {
GalleryDetailHeaderInfoCard(
detail = it,
onClick = onInfoCardClick,
modifier = Modifier.padding(top = 8.dp, end = dimensionResource(id = R.dimen.keyline_margin)),
modifier = Modifier.padding(top = 8.dp, end = dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)),
)
}
Spacer(modifier = Modifier.weight(1F))
@@ -113,7 +113,7 @@ fun GalleryDetailHeaderCard(
AssistChip(
onClick = onCategoryChipClick,
label = { Text(text = categoryText, overflow = TextOverflow.Visible, softWrap = false, maxLines = 1) },
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.keyline_margin)),
modifier = Modifier.padding(horizontal = dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)),
leadingIcon = {
Icon(
imageVector = Icons.AutoMirrored.Default.Label,
@@ -125,7 +125,7 @@ fun GalleryDetailHeaderCard(
AssistChip(
onClick = onUploaderChipClick,
label = { Text(text = uploaderText, overflow = TextOverflow.Visible, softWrap = false, maxLines = 1) },
modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen.keyline_margin)),
modifier = Modifier.padding(horizontal = dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)),
leadingIcon = {
Icon(
imageVector = Icons.Default.NoAccounts,
@@ -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.ui.component.CrystalCard
import com.ehviewer.core.ui.component.ElevatedCard
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.CrystalCard
import com.hippo.ehviewer.ui.tools.ElevatedCard
import com.hippo.ehviewer.ui.tools.GalleryListCardRating
import com.hippo.ehviewer.ui.tools.SharedElementBox
import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope
import com.hippo.ehviewer.ui.tools.listThumbGenerator
import com.hippo.ehviewer.util.FavouriteStatusRouter
@Composable
@@ -51,23 +51,23 @@ import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
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
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.big.SadAndroid
import com.hippo.ehviewer.ktbuilder.imageRequest
import com.hippo.ehviewer.ui.screen.collectDetailSizeAsState
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalGrid
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalStaggeredGrid
import com.hippo.ehviewer.util.displayString
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import moe.tarsin.launch
@Stable
operator fun PaddingValues.plus(r: PaddingValues) = object : PaddingValues {
@@ -79,7 +79,7 @@ operator fun PaddingValues.plus(r: PaddingValues) = object : PaddingValues {
}
@Composable
context(_: CoroutineScope, ctx: Context)
context(_: CoroutineScope, _: Context)
fun GalleryList(
modifier: Modifier = Modifier,
data: LazyPagingItems<BaseGalleryInfo>,
@@ -95,8 +95,8 @@ fun GalleryList(
onRefresh: () -> Unit,
onLoading: () -> Unit,
) {
val marginH = dimensionResource(id = R.dimen.gallery_list_margin_h)
val marginV = dimensionResource(id = R.dimen.gallery_list_margin_v)
val marginH = dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_list_margin_h)
val marginV = dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_list_margin_v)
var isRefreshing by remember { mutableStateOf(false) }
val refreshState = rememberPullToRefreshState()
@@ -132,8 +132,8 @@ fun GalleryList(
modifier = contentModifier.fillMaxSize(),
state = detailListState,
contentPadding = contentPadding + PaddingValues(marginH, marginV),
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(com.hippo.ehviewer.R.dimen.gallery_list_interval)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(com.hippo.ehviewer.R.dimen.gallery_list_interval)),
) {
items(
count = data.itemCount,
@@ -155,7 +155,7 @@ fun GalleryList(
}
}
} else {
val gridInterval = dimensionResource(R.dimen.gallery_grid_interval)
val gridInterval = dimensionResource(com.hippo.ehviewer.R.dimen.gallery_grid_interval)
val thumbColumns by Settings.thumbColumns.collectAsState()
FastScrollLazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(thumbColumns),
@@ -23,10 +23,10 @@ import androidx.compose.ui.unit.IntSize
import coil3.BitmapImage
import coil3.compose.AsyncImagePainter.State
import coil3.compose.rememberAsyncImagePainter
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.CrystalCard
import com.hippo.ehviewer.ui.tools.shouldCrop
@Composable
@@ -17,7 +17,7 @@ import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
@Composable
fun ImageSearch(
@@ -29,7 +29,7 @@ fun ImageSearch(
) {
AnimatedVisibility(visible = image != null, modifier = Modifier.align(Alignment.CenterHorizontally)) {
Card {
val maxSize = dimensionResource(id = R.dimen.image_search_max_size)
val maxSize = dimensionResource(id = com.hippo.ehviewer.R.dimen.image_search_max_size)
AsyncImage(
model = image,
contentDescription = null,
@@ -10,7 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.paging.LoadState
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.util.displayString
@Composable
@@ -33,19 +33,19 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import arrow.core.raise.ensure
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.component.DropdownFilterChip
import com.ehviewer.core.ui.util.thenIf
import com.ehviewer.core.util.launch
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.DropdownFilterChip
import com.hippo.ehviewer.ui.tools.awaitResult
import com.hippo.ehviewer.ui.tools.thenIf
import com.hippo.ehviewer.util.toIntOrDefault
import kotlinx.coroutines.CoroutineScope
import moe.tarsin.launch
private val categoryTable = arrayOf(
EhUtils.DOUJINSHI to R.string.doujinshi,
@@ -117,7 +117,7 @@ fun SearchFilter(
selectedItemIndex = language + 1,
onSelectedItemIndexChange = { onLanguageChange(it - 1) },
)
val minRatingItems = stringArrayResource(id = R.array.search_min_rating)
val minRatingItems = stringArrayResource(id = com.hippo.ehviewer.R.array.search_min_rating)
val minRatingStr = stringResource(id = R.string.search_sr)
DropdownFilterChip(
label = minRatingStr,
@@ -22,7 +22,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.client.parser.Torrent
import com.hippo.ehviewer.client.parser.format
@@ -21,10 +21,10 @@ import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.component.RollingNumber
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.ui.main.RollingNumber
@Composable
fun ColorFilterSetting() = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
@@ -87,7 +87,7 @@ fun ColorFilterSetting() = Column(modifier = Modifier.verticalScroll(rememberScr
}
SpinnerChoice(
title = stringResource(id = R.string.pref_color_filter_mode),
entries = stringArrayResource(id = R.array.color_filter_modes),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.color_filter_modes),
values = arrayOf("0", "1", "2", "3", "4", "5"),
field = Settings.colorFilterMode.asMutableState(),
)
@@ -13,8 +13,8 @@ import android.webkit.MimeTypeMap
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material3.SnackbarHostState
import androidx.core.content.FileProvider
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.BuildConfig.APPLICATION_ID
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.gallery.Page
@@ -121,7 +121,7 @@ suspend fun save(page: Page) {
try {
ctx.contentResolver.delete(imageUri, null, null)
} catch (e: Exception) {
e.logcat(e)
logcat("SavePage", e)
}
snackbar(cannotSave)
} else if (isAtLeastQ) {
@@ -37,8 +37,9 @@ import coil3.DrawableImage
import coil3.compose.AsyncImagePainter
import coil3.compose.SubcomposeAsyncImage
import coil3.compose.SubcomposeAsyncImageContent
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.util.thenIf
import com.google.accompanist.drawablepainter.DrawablePainter
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.gallery.Page
@@ -47,7 +48,6 @@ import com.hippo.ehviewer.gallery.PageStatus
import com.hippo.ehviewer.gallery.progressObserved
import com.hippo.ehviewer.gallery.statusObserved
import com.hippo.ehviewer.image.Image
import com.hippo.ehviewer.ui.tools.thenIf
import com.hippo.ehviewer.util.AdsPlaceholderFile
import eu.kanade.tachiyomi.ui.reader.viewer.CombinedCircularProgressIndicator
import kotlinx.coroutines.awaitCancellation
@@ -11,7 +11,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.util.isAtLeastP
@@ -20,8 +20,8 @@ import com.hippo.ehviewer.util.isAtLeastP
fun ReaderGeneralSetting() = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
SpinnerChoice(
title = stringResource(id = R.string.pref_reader_theme),
entries = stringArrayResource(id = R.array.reader_themes),
values = stringArrayResource(id = R.array.reader_themes_values),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.reader_themes),
values = stringArrayResource(id = com.hippo.ehviewer.R.array.reader_themes_values),
field = Settings.readerTheme.asMutableState(),
)
SwitchChoice(
@@ -17,7 +17,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.collectAsState
@@ -27,13 +27,13 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
fun ReaderModeSetting() = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
SpinnerChoice(
title = stringResource(id = R.string.pref_category_reading_mode),
entries = stringArrayResource(id = R.array.viewers_selector),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.viewers_selector),
values = arrayOf("0", "1", "2", "3", "4", "5"),
field = Settings.readingMode.asMutableState(),
)
SpinnerChoice(
title = stringResource(id = R.string.rotation_type),
entries = stringArrayResource(id = R.array.rotation_type),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.rotation_type),
values = arrayOf("0", "8", "16", "24", "32", "40", "48"),
field = Settings.orientationMode.asMutableState(),
)
@@ -60,7 +60,7 @@ private fun PagerSetting() = Column {
val pagerNav = Settings.readerPagerNav.asMutableState()
SpinnerChoice(
title = stringResource(id = R.string.pref_viewer_nav),
entries = stringArrayResource(id = R.array.pager_nav),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.pager_nav),
values = arrayOf("0", "1", "2", "3", "4", "5"),
field = pagerNav,
)
@@ -68,7 +68,7 @@ private fun PagerSetting() = Column {
Column {
SpinnerChoice(
title = stringResource(id = R.string.pref_read_with_tapping_inverted),
entries = stringArrayResource(id = R.array.invert_tapping_mode),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.invert_tapping_mode),
values = arrayOf("0", "1", "2", "3"),
field = Settings.readerPagerNavInverted.asMutableState(),
)
@@ -81,7 +81,7 @@ private fun PagerSetting() = Column {
val scaleType = Settings.imageScaleType.asMutableState()
SpinnerChoice(
title = stringResource(id = R.string.pref_image_scale_type),
entries = stringArrayResource(id = R.array.image_scale_type),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.image_scale_type),
values = arrayOf("1", "2", "3", "4", "5", "6"),
field = scaleType,
)
@@ -93,7 +93,7 @@ private fun PagerSetting() = Column {
}
SpinnerChoice(
title = stringResource(id = R.string.pref_zoom_start),
entries = stringArrayResource(id = R.array.zoom_start),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.zoom_start),
values = arrayOf("1", "2", "3", "4"),
field = Settings.zoomStart.asMutableState(),
)
@@ -113,22 +113,22 @@ private fun WebtoonSetting() = Column {
val webtoonNav = Settings.readerWebtoonNav.asMutableState()
SpinnerChoice(
title = stringResource(id = R.string.pref_viewer_nav),
entries = stringArrayResource(id = R.array.webtoon_nav),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.webtoon_nav),
values = arrayOf("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 = R.array.invert_tapping_mode),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.invert_tapping_mode),
values = arrayOf("0", "1", "2", "3"),
field = Settings.readerWebtoonNavInverted.asMutableState(),
)
}
SpinnerChoice(
title = stringResource(id = R.string.pref_webtoon_side_padding),
entries = stringArrayResource(id = R.array.webtoon_side_padding),
values = stringArrayResource(id = R.array.webtoon_side_padding_values),
entries = stringArrayResource(id = com.hippo.ehviewer.R.array.webtoon_side_padding),
values = stringArrayResource(id = com.hippo.ehviewer.R.array.webtoon_side_padding_values),
field = Settings.webtoonSidePadding.asMutableState(),
)
SwitchChoice(
@@ -59,9 +59,14 @@ 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.ui.util.Await
import com.ehviewer.core.ui.util.asyncInVM
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.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.collectAsState
@@ -78,12 +83,9 @@ 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.Await
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.asyncInVM
import com.hippo.ehviewer.ui.tools.awaitInputText
import com.hippo.ehviewer.ui.tools.dialog
import com.hippo.ehviewer.ui.tools.thenIf
import com.hippo.ehviewer.util.displayString
import com.hippo.ehviewer.util.hasAds
import com.ramcosta.composedestinations.annotation.Destination
@@ -94,7 +96,6 @@ import eu.kanade.tachiyomi.ui.reader.ReaderAppBars
import eu.kanade.tachiyomi.ui.reader.ReaderContentOverlay
import eu.kanade.tachiyomi.ui.reader.ReaderPageSheetMeta
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.awaitCancellation
@@ -104,7 +105,6 @@ import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.Serializable
import moe.tarsin.kt.unreachable
import moe.tarsin.launch
import moe.tarsin.string
import okio.Path.Companion.toPath
@@ -377,7 +377,7 @@ fun ReaderScreen(pageLoader: PageLoader, info: BaseGalleryInfo?) {
}
}
context(ctx: Context, state: DialogState, nav: DestinationsNavigator)
context(_: Context, _: DialogState, nav: DestinationsNavigator)
suspend inline fun <T> usePageLoader(args: ReaderScreenArgs, crossinline block: suspend (PageLoader) -> T) = when (args) {
is ReaderScreenArgs.Gallery -> {
val info = args.info
@@ -407,7 +407,7 @@ suspend inline fun <T> usePageLoader(args: ReaderScreenArgs, crossinline block:
@Composable
private fun collectBackgroundColorAsState(): State<Color> {
val grey = colorResource(R.color.reader_background_dark)
val grey = colorResource(com.hippo.ehviewer.R.color.reader_background_dark)
val dark = isSystemInDarkTheme()
return Settings.readerTheme.collectAsState { theme ->
when (theme) {
@@ -15,7 +15,7 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import kotlinx.coroutines.launch
private val tabs = intArrayOf(
@@ -25,7 +25,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.ui.tools.Slider
import com.ehviewer.core.ui.component.Slider
@Composable
fun SpinnerChoice(title: String, entries: Array<String>, values: Array<String>, field: MutableState<Int>) {
@@ -23,6 +23,8 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
import androidx.compose.foundation.lazy.staggeredgrid.items
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.DriveFileMove
import androidx.compose.material.icons.automirrored.filled.Sort
@@ -80,8 +82,25 @@ 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.i18n.R
import com.ehviewer.core.ui.component.FAB_ANIMATE_TIME
import com.ehviewer.core.ui.component.FabLayout
import com.ehviewer.core.ui.component.FastScrollLazyColumn
import com.ehviewer.core.ui.component.FastScrollLazyVerticalStaggeredGrid
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.asyncState
import com.ehviewer.core.ui.util.rememberInVM
import com.ehviewer.core.ui.util.thenIf
import com.ehviewer.core.util.launch
import com.ehviewer.core.util.launchIO
import com.ehviewer.core.util.onEachLatest
import com.ehviewer.core.util.withNonCancellableContext
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.client.EhTagDatabase
@@ -93,47 +112,30 @@ 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
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.big.Download
import com.hippo.ehviewer.ui.DrawerHandle
import com.hippo.ehviewer.ui.LocalSideSheetState
import com.hippo.ehviewer.ui.ProvideSideSheetContent
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.FAB_ANIMATE_TIME
import com.hippo.ehviewer.ui.main.FabLayout
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.Await
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalStaggeredGrid
import com.hippo.ehviewer.ui.tools.HapticFeedbackType
import com.hippo.ehviewer.ui.tools.asyncState
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.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.tools.thenIf
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
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlin.math.roundToInt
import kotlinx.coroutines.delay
import moe.tarsin.coroutines.onEachLatest
import moe.tarsin.launch
import moe.tarsin.launchIO
import moe.tarsin.navigate
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
@@ -451,6 +453,15 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
}
}
val searchFieldState = rememberTextFieldState()
class DownloadLabelSuggestion(private val label: String) : Suggestion() {
override val keyword = LABEL_PREFIX + label
override fun onClick() {
searchFieldState.setTextAndPlaceCursorAtEnd(keyword)
searchBarExpanded = false
switchLabel(label)
}
}
SearchBarScreen(
onApplySearch = { filterState = filterState.copy(keyword = it) },
expanded = searchBarExpanded,
@@ -461,6 +472,15 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
},
title = title,
searchFieldHint = hint,
searchFieldState = searchFieldState,
suggestionProvider = { query ->
val label = query.substringAfter(LABEL_PREFIX, "")
if (label.isEmpty()) {
emptyList()
} else {
EhDB.searchDownloadLabel(label, 10).map(::DownloadLabelSuggestion)
}
},
searchBarOffsetY = { searchBarOffsetY },
trailingIcon = {
var expanded by remember { mutableStateOf(false) }
@@ -523,7 +543,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
},
) { contentPadding ->
val height by collectListThumbSizeAsState()
val realPadding = contentPadding + PaddingValues(dimensionResource(id = R.dimen.gallery_list_margin_h), dimensionResource(id = R.dimen.gallery_list_margin_v))
val realPadding = contentPadding + PaddingValues(dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_list_margin_h), dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_list_margin_v))
val searchBarConnection = remember {
val slop = ViewConfiguration.get(contextOf<Context>()).scaledTouchSlop
val topPaddingPx = with(density) { contentPadding.calculateTopPadding().roundToPx() }
@@ -548,7 +568,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
Crossfade(targetState = gridView, label = "Downloads") { showGridView ->
if (showGridView) {
val gridInterval = dimensionResource(R.dimen.gallery_grid_interval)
val gridInterval = dimensionResource(com.hippo.ehviewer.R.dimen.gallery_grid_interval)
val thumbColumns by Settings.thumbColumns.collectAsState()
FastScrollLazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(thumbColumns),
@@ -571,7 +591,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
FastScrollLazyColumn(
modifier = Modifier.nestedScroll(searchBarConnection).fillMaxSize(),
contentPadding = realPadding,
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(com.hippo.ehviewer.R.dimen.gallery_list_interval)),
) {
items(list, key = { it.gid }) { info ->
val checked = info.gid in checkedInfoMap
@@ -660,7 +680,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
}
onClick(Icons.AutoMirrored.Default.Sort) {
val oldMode = SortMode.from(sortMode)
val sortModes = contextOf<Context>().resources.getStringArray(R.array.download_sort_modes).toList()
val sortModes = contextOf<Context>().resources.getStringArray(com.hippo.ehviewer.R.array.download_sort_modes).toList()
val (selected, checked) = awaitSelectItemWithCheckBox(
sortModes,
R.string.sort_by,
@@ -674,7 +694,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
invalidateKey = !invalidateKey
}
onClick(Icons.Default.FilterList) {
val downloadStates = contextOf<Context>().resources.getStringArray(R.array.download_state).toList()
val downloadStates = contextOf<Context>().resources.getStringArray(com.hippo.ehviewer.R.array.download_state).toList()
val state = awaitSingleChoice(
downloadStates,
filterState.state + 1,
@@ -712,3 +732,5 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
}
}
}
private const val LABEL_PREFIX = "label:"
@@ -53,42 +53,42 @@ import androidx.compose.ui.res.stringResource
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.ui.component.FAB_ANIMATE_TIME
import com.ehviewer.core.ui.component.FabLayout
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.filled.GoTo
import com.ehviewer.core.ui.util.asyncState
import com.ehviewer.core.ui.util.thenIf
import com.ehviewer.core.util.launch
import com.ehviewer.core.util.onEachLatest
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
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.icons.EhIcons
import com.hippo.ehviewer.icons.filled.GoTo
import com.hippo.ehviewer.ui.DrawerHandle
import com.hippo.ehviewer.ui.LocalSideSheetState
import com.hippo.ehviewer.ui.ProvideSideSheetContent
import com.hippo.ehviewer.ui.Screen
import com.hippo.ehviewer.ui.awaitSelectDate
import com.hippo.ehviewer.ui.main.AvatarIcon
import com.hippo.ehviewer.ui.main.FAB_ANIMATE_TIME
import com.hippo.ehviewer.ui.main.FabLayout
import com.hippo.ehviewer.ui.main.GalleryInfoGridItem
import com.hippo.ehviewer.ui.main.GalleryInfoListItem
import com.hippo.ehviewer.ui.main.GalleryList
import com.hippo.ehviewer.ui.startDownload
import com.hippo.ehviewer.ui.tools.asyncState
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.awaitSelectItem
import com.hippo.ehviewer.ui.tools.thenIf
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
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlin.math.roundToInt
import kotlinx.coroutines.delay
import moe.tarsin.coroutines.onEachLatest
import moe.tarsin.coroutines.runSwallowingWithUI
import moe.tarsin.launch
import moe.tarsin.navigate
@Destination<RootGraph>
@@ -14,13 +14,13 @@ import androidx.paging.PagingState
import androidx.paging.cachedIn
import androidx.paging.filter
import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
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 eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import moe.tarsin.coroutines.runSuspendCatching
@@ -51,7 +51,6 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
@@ -70,7 +69,6 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalTextToolbar
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
@@ -84,7 +82,13 @@ import androidx.core.text.buildSpannedString
import androidx.core.text.getSpans
import androidx.core.text.inSpans
import androidx.core.text.parseAsHtml
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
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.launch
import com.ehviewer.core.util.launchIO
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhFilter.remember
@@ -99,14 +103,11 @@ import com.hippo.ehviewer.ui.jumpToReaderByPage
import com.hippo.ehviewer.ui.main.GalleryCommentCard
import com.hippo.ehviewer.ui.main.NavigationIcon
import com.hippo.ehviewer.ui.openBrowser
import com.hippo.ehviewer.ui.tools.animateFloatMergePredictiveBackAsState
import com.hippo.ehviewer.ui.tools.addBBCodeTextContextMenuItems
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.awaitSelectAction
import com.hippo.ehviewer.ui.tools.normalizeSpan
import com.hippo.ehviewer.ui.tools.rememberBBCodeTextToolbar
import com.hippo.ehviewer.ui.tools.showNoButton
import com.hippo.ehviewer.ui.tools.snackBarPadding
import com.hippo.ehviewer.ui.tools.thenIf
import com.hippo.ehviewer.ui.tools.toBBCode
import com.hippo.ehviewer.ui.tools.updateSpan
import com.hippo.ehviewer.util.ReadableTime
@@ -115,13 +116,9 @@ 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.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat
import kotlin.collections.forEach
import kotlin.math.roundToInt
import moe.tarsin.coroutines.runSuspendCatching
import moe.tarsin.launch
import moe.tarsin.launchIO
import moe.tarsin.navigate
import moe.tarsin.snackbar
@@ -298,7 +295,7 @@ fun AnimatedVisibilityScope.GalleryCommentsScreen(gid: Long, navigator: Destinat
}
},
) { paddingValues ->
val keylineMargin = dimensionResource(id = R.dimen.keyline_margin)
val keylineMargin = dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)
var editTextMeasured by remember { mutableStateOf(MinimumContentPaddingEditText) }
var isRefreshing by remember { mutableStateOf(false) }
val refreshState = rememberPullToRefreshState()
@@ -479,18 +476,15 @@ fun AnimatedVisibilityScope.GalleryCommentsScreen(gid: Long, navigator: Destinat
},
) {
val color = MaterialTheme.colorScheme.onPrimaryContainer
val toolbar = rememberBBCodeTextToolbar(userCommentBackField)
CompositionLocalProvider(LocalTextToolbar provides toolbar) {
BasicTextField(
value = userComment,
onValueChange = { textFieldValue ->
userComment = textFieldValue.updateSpan(userComment)
},
modifier = Modifier.weight(1f).padding(keylineMargin),
textStyle = MaterialTheme.typography.bodyLarge.merge(color = color),
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
)
}
BasicTextField(
value = userComment,
onValueChange = { textFieldValue ->
userComment = textFieldValue.updateSpan(userComment)
},
modifier = Modifier.weight(1f).padding(keylineMargin).addBBCodeTextContextMenuItems(userCommentBackField),
textStyle = MaterialTheme.typography.bodyLarge.merge(color = color),
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
)
IconButton(
onClick = { launchIO { sendComment() } },
shapes = IconButtonDefaults.shapes(),
@@ -68,8 +68,24 @@ import androidx.paging.compose.itemKey
import arrow.core.partially1
import arrow.fx.coroutines.parMap
import arrow.fx.coroutines.parZip
import com.ehviewer.core.i18n.R
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.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.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.withIOContext
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhFilter.remember
@@ -96,8 +112,6 @@ 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.icons.EhIcons
import com.hippo.ehviewer.icons.filled.Magnet
import com.hippo.ehviewer.ktbuilder.executeIn
import com.hippo.ehviewer.ktbuilder.imageRequest
import com.hippo.ehviewer.ui.GalleryInfoBottomSheet
@@ -117,15 +131,9 @@ import com.hippo.ehviewer.ui.modifyFavorites
import com.hippo.ehviewer.ui.navToReader
import com.hippo.ehviewer.ui.openBrowser
import com.hippo.ehviewer.ui.startDownload
import com.hippo.ehviewer.ui.tools.CrystalCard
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalGrid
import com.hippo.ehviewer.ui.tools.FilledTertiaryIconButton
import com.hippo.ehviewer.ui.tools.FilledTertiaryIconToggleButton
import com.hippo.ehviewer.ui.tools.GalleryDetailRating
import com.hippo.ehviewer.ui.tools.GalleryRatingBar
import com.hippo.ehviewer.ui.tools.LocalWindowSizeClass
import com.hippo.ehviewer.ui.tools.TransitionsVisibilityScope
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.awaitResult
import com.hippo.ehviewer.ui.tools.awaitSelectAction
@@ -135,8 +143,6 @@ import com.hippo.ehviewer.ui.tools.foldToLoadResult
import com.hippo.ehviewer.ui.tools.getClippedRefreshKey
import com.hippo.ehviewer.ui.tools.getLimit
import com.hippo.ehviewer.ui.tools.getOffset
import com.hippo.ehviewer.ui.tools.isExpanded
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.tools.showNoButton
import com.hippo.ehviewer.util.FavouriteStatusRouter
import com.hippo.ehviewer.util.addTextToClipboard
@@ -144,8 +150,6 @@ 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.lang.withIOContext
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat
import io.ktor.http.encodeURLParameter
import kotlin.coroutines.resume
@@ -154,12 +158,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import moe.tarsin.async
import moe.tarsin.coroutines.runSuspendCatching
import moe.tarsin.coroutines.runSwallowingWithUI
import moe.tarsin.launch
import moe.tarsin.launchIO
import moe.tarsin.launchUI
import moe.tarsin.navigate
import moe.tarsin.snackbar
import moe.tarsin.string
@@ -174,7 +174,7 @@ fun GalleryDetailContent(
voteTag: VoteTag,
modifier: Modifier,
) {
val keylineMargin = dimensionResource(R.dimen.keyline_margin)
val keylineMargin = dimensionResource(com.hippo.ehviewer.R.dimen.keyline_margin)
val galleryDetail = galleryInfo.asGalleryDetail()
val windowSizeClass = LocalWindowSizeClass.current
val thumbColumns by Settings.thumbColumns.collectAsState()
@@ -262,8 +262,8 @@ fun GalleryDetailContent(
columns = GridCells.Fixed(thumbColumns),
contentPadding = contentPadding,
modifier = modifier.padding(horizontal = keylineMargin),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.strip_item_padding)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.strip_item_padding_v)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = com.hippo.ehviewer.R.dimen.strip_item_padding)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = com.hippo.ehviewer.R.dimen.strip_item_padding_v)),
) {
item(
key = "header",
@@ -325,8 +325,8 @@ fun GalleryDetailContent(
columns = GridCells.Fixed(thumbColumns),
contentPadding = contentPadding,
modifier = modifier.padding(horizontal = keylineMargin),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.strip_item_padding)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.strip_item_padding_v)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = com.hippo.ehviewer.R.dimen.strip_item_padding)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(id = com.hippo.ehviewer.R.dimen.strip_item_padding_v)),
) {
item(
key = "header",
@@ -340,7 +340,7 @@ fun GalleryDetailContent(
onUploaderChipClick = ::onUploaderChipClick.partially1(galleryInfo),
onBlockUploaderIconClick = ::showFilterUploaderDialog.partially1(galleryInfo),
onCategoryChipClick = ::onCategoryChipClick,
modifier = Modifier.width(dimensionResource(id = R.dimen.gallery_detail_card_landscape_width)).padding(vertical = keylineMargin),
modifier = Modifier.width(dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_detail_card_landscape_width)).padding(vertical = keylineMargin),
)
Column(
modifier = Modifier.fillMaxSize(),
@@ -443,7 +443,7 @@ fun BelowHeader(galleryDetail: GalleryDetail, voteTag: VoteTag) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(id = R.dimen.strip_item_padding_v))
.padding(bottom = dimensionResource(id = com.hippo.ehviewer.R.dimen.strip_item_padding_v))
.clip(RoundedCornerShape(16.dp))
.clickable(onClick = ::navigateToCommentScreen),
contentAlignment = Alignment.Center,
@@ -463,7 +463,7 @@ fun BelowHeader(galleryDetail: GalleryDetail, voteTag: VoteTag) {
navigate(info.gid asDstWith info.token)
}
}
val keylineMargin = dimensionResource(R.dimen.keyline_margin)
val keylineMargin = dimensionResource(com.hippo.ehviewer.R.dimen.keyline_margin)
Spacer(modifier = Modifier.size(keylineMargin))
if (galleryDetail.newerVersions.isNotEmpty()) {
Box(contentAlignment = Alignment.Center) {
@@ -765,7 +765,7 @@ fun BelowHeader(galleryDetail: GalleryDetail, voteTag: VoteTag) {
Spacer(modifier = Modifier.size(keylineMargin))
if (Settings.showComments.value) {
GalleryDetailComment(galleryDetail.comments.comments)
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.strip_item_padding_v)))
Spacer(modifier = Modifier.size(dimensionResource(id = com.hippo.ehviewer.R.dimen.strip_item_padding_v)))
}
}
@@ -784,7 +784,7 @@ private fun getRatingText(rating: Float): Int = when ((rating * 2).roundToInt())
else -> R.string.rating_none
}
private fun List<GalleryTagGroup>.artistTag() = find { (ns, _) -> ns == TagNamespace.Artist || ns == TagNamespace.Cosplayer }?.let { (ns, tags) -> "$ns:${tags[0].text}" }
private fun List<GalleryTagGroup>.artistTag() = find { (ns, _) -> ns == TagNamespace.Artist || ns == TagNamespace.Cosplayer }?.let { (ns, tags) -> "${ns.value}:${tags[0].text}" }
@Composable
context(_: Context)
@@ -829,7 +829,7 @@ private fun GalleryDetail.collectPreviewItems() = rememberInVM(previewList) {
}.flow.cachedIn(viewModelScope)
}.collectAsLazyPagingItems()
context(ctx: Context)
context(_: Context)
private fun LazyGridScope.galleryPreview(detail: GalleryDetail, data: LazyPagingItems<GalleryPreview>, onClick: (Int) -> Unit) {
val isV2Thumb = detail.previewList.first() is V2GalleryPreview
items(
@@ -34,9 +34,15 @@ 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.i18n.R
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.withIOContext
import com.hippo.ehviewer.EhApplication.Companion.imageCache
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhUrl
@@ -58,12 +64,8 @@ import com.hippo.ehviewer.ui.main.GalleryDetailErrorTip
import com.hippo.ehviewer.ui.main.NavigationIcon
import com.hippo.ehviewer.ui.navToReader
import com.hippo.ehviewer.ui.openBrowser
import com.hippo.ehviewer.ui.tools.LocalWindowSizeClass
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.awaitSelectTags
import com.hippo.ehviewer.ui.tools.isExpanded
import com.hippo.ehviewer.ui.tools.launchInVM
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.util.AppHelper
import com.hippo.ehviewer.util.awaitActivityResult
import com.hippo.ehviewer.util.bgWork
@@ -73,11 +75,9 @@ 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.lang.withIOContext
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.serialization.Serializable
import moe.tarsin.coroutines.runSuspendCatching
import moe.tarsin.launchIO
import moe.tarsin.snackbar
import moe.tarsin.tip
@@ -249,7 +249,7 @@ fun AnimatedVisibilityScope.GalleryDetailScreen(args: GalleryDetailScreenArgs, n
) {
Text(text = stringResource(id = R.string.clear_image_cache_confirm))
}
(0..<gd.pages).forEach {
repeat(gd.pages) {
val key = getImageKey(gd.gid, it)
imageCache.remove(key)
}
@@ -69,8 +69,22 @@ import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import arrow.core.raise.ensure
import arrow.core.raise.ensureNotNull
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.component.FAB_ANIMATE_TIME
import com.ehviewer.core.ui.component.FabLayout
import com.ehviewer.core.ui.component.FastScrollLazyColumn
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.filled.GoTo
import com.ehviewer.core.ui.util.Await
import com.ehviewer.core.ui.util.asyncState
import com.ehviewer.core.ui.util.thenIf
import com.ehviewer.core.util.launch
import com.ehviewer.core.util.launchIO
import com.ehviewer.core.util.onEachLatest
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.client.EhTagDatabase
@@ -87,46 +101,32 @@ 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.icons.EhIcons
import com.hippo.ehviewer.icons.filled.GoTo
import com.hippo.ehviewer.ui.DrawerHandle
import com.hippo.ehviewer.ui.LocalSideSheetState
import com.hippo.ehviewer.ui.ProvideSideSheetContent
import com.hippo.ehviewer.ui.Screen
import com.hippo.ehviewer.ui.awaitSelectDate
import com.hippo.ehviewer.ui.destinations.ProgressScreenDestination
import com.hippo.ehviewer.ui.doGalleryInfoAction
import com.hippo.ehviewer.ui.main.AdvancedSearchOption
import com.hippo.ehviewer.ui.main.AvatarIcon
import com.hippo.ehviewer.ui.main.FAB_ANIMATE_TIME
import com.hippo.ehviewer.ui.main.FabLayout
import com.hippo.ehviewer.ui.main.GalleryInfoGridItem
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.Await
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.HapticFeedbackType
import com.hippo.ehviewer.ui.tools.asyncState
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.ui.tools.thenIf
import com.hippo.ehviewer.util.FavouriteStatusRouter
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.spec.Direction
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlin.math.roundToInt
import kotlin.random.Random
import kotlinx.coroutines.delay
import moe.tarsin.coroutines.onEachLatest
import moe.tarsin.launch
import moe.tarsin.launchIO
import moe.tarsin.navigate
import moe.tarsin.snackbar
import moe.tarsin.string
@@ -194,8 +194,8 @@ fun AnimatedVisibilityScope.GalleryListScreen(
FavouriteStatusRouter.Observe(data)
val listMode by Settings.listMode.collectAsState()
val entries = stringArrayResource(id = R.array.toplist_entries)
val values = stringArrayResource(id = R.array.toplist_values)
val entries = stringArrayResource(id = com.hippo.ehviewer.R.array.toplist_entries)
val values = stringArrayResource(id = com.hippo.ehviewer.R.array.toplist_values)
val toplists = remember { entries zip values }
val quickSearchName = getSuitableTitleForUrlBuilder(urlBuilder, false)
var saveProgress by Settings.qSSaveProgress.asMutableState()
@@ -482,10 +482,10 @@ fun AnimatedVisibilityScope.GalleryListScreen(
searchFieldState = searchFieldState,
suggestionProvider = {
GalleryDetailUrlParser.parse(it, false)?.run {
GalleryDetailUrlSuggestion(gid, token)
listOf(GalleryDetailUrlSuggestion(gid, token))
} ?: GalleryPageUrlParser.parse(it, false)?.run {
GalleryPageUrlSuggestion(gid, pToken, page)
}
listOf(GalleryPageUrlSuggestion(gid, pToken, page))
}.orEmpty()
},
localSearch = false,
searchBarOffsetY = { searchBarOffsetY },
@@ -11,12 +11,12 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer
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 eu.kanade.tachiyomi.util.lang.withIOContext
import moe.tarsin.coroutines.runSuspendCatching
class GalleryListViewModel(lub: ListUrlBuilder, savedStateHandle: SavedStateHandle) : ViewModel() {
@@ -42,21 +42,22 @@ import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import androidx.paging.map
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.component.FastScrollLazyColumn
import com.ehviewer.core.ui.icons.EhIcons
import com.ehviewer.core.ui.icons.big.History
import com.ehviewer.core.ui.util.Await
import com.ehviewer.core.ui.util.rememberInVM
import com.ehviewer.core.ui.util.thenIf
import com.ehviewer.core.util.launch
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.big.History
import com.hippo.ehviewer.ui.DrawerHandle
import com.hippo.ehviewer.ui.Screen
import com.hippo.ehviewer.ui.doGalleryInfoAction
import com.hippo.ehviewer.ui.main.GalleryInfoListItem
import com.hippo.ehviewer.ui.tools.Await
import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.rememberInVM
import com.hippo.ehviewer.ui.tools.thenIf
import com.hippo.ehviewer.util.FavouriteStatusRouter
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
@@ -64,7 +65,6 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlin.math.roundToInt
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.map
import moe.tarsin.launch
import moe.tarsin.navigate
@Destination<RootGraph>
@@ -133,13 +133,13 @@ fun AnimatedVisibilityScope.HistoryScreen(navigator: DestinationsNavigator) = Sc
}
}
}
val marginH = dimensionResource(id = R.dimen.gallery_list_margin_h)
val marginH = dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_list_margin_h)
val cardHeight by collectListThumbSizeAsState()
val showPages by Settings.showGalleryPages.collectAsState()
FastScrollLazyColumn(
modifier = Modifier.nestedScroll(searchBarConnection).fillMaxSize(),
contentPadding = paddingValues,
verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)),
verticalArrangement = Arrangement.spacedBy(dimensionResource(com.hippo.ehviewer.R.dimen.gallery_list_interval)),
) {
items(
count = historyData.itemCount,
@@ -27,22 +27,22 @@ 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.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.util.snackBarPadding
import com.ehviewer.core.util.launch
import com.ehviewer.core.util.launchUI
import com.ehviewer.core.util.withIOContext
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.ui.tools.snackBarPadding
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
import eu.kanade.tachiyomi.util.lang.withIOContext
import moe.tarsin.launch
import moe.tarsin.launchUI
import moe.tarsin.navigate
import moe.tarsin.snackbar
@@ -50,8 +50,8 @@ import moe.tarsin.snackbar
@Composable
fun AnimatedVisibilityScope.ImageSearchScreen(navigator: DestinationsNavigator) = Screen(navigator) {
val selectImageFirst = stringResource(R.string.select_image_first)
val marginH = dimensionResource(id = R.dimen.gallery_list_margin_h)
val marginV = dimensionResource(id = R.dimen.gallery_list_margin_v)
val marginH = dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_list_margin_h)
val marginV = dimensionResource(id = com.hippo.ehviewer.R.dimen.gallery_list_margin_v)
var imageUri by rememberSaveable { mutableStateOf<Uri?>(null) }
Scaffold(
@@ -87,19 +87,19 @@ fun AnimatedVisibilityScope.ImageSearchScreen(navigator: DestinationsNavigator)
modifier = Modifier.padding(
paddingValues = contentPadding + PaddingValues(marginH, marginV),
).padding(
vertical = dimensionResource(id = R.dimen.search_layout_margin_v),
vertical = dimensionResource(id = com.hippo.ehviewer.R.dimen.search_layout_margin_v),
),
) {
Column(
modifier = Modifier.padding(
horizontal = dimensionResource(id = R.dimen.search_category_padding_h),
vertical = dimensionResource(id = R.dimen.search_category_padding_v),
horizontal = dimensionResource(id = com.hippo.ehviewer.R.dimen.search_category_padding_h),
vertical = dimensionResource(id = com.hippo.ehviewer.R.dimen.search_category_padding_v),
).fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = R.string.search_image),
modifier = Modifier.height(dimensionResource(id = R.dimen.search_category_title_height)),
modifier = Modifier.height(dimensionResource(id = com.hippo.ehviewer.R.dimen.search_category_title_height)),
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleMedium,
)
@@ -25,7 +25,7 @@ private fun urlToDestination(url: String): Direction? {
}
@MainThread
context(nav: DestinationsNavigator)
context(_: DestinationsNavigator)
fun navWithUrl(url: String): Boolean {
val dest = urlToDestination(url) ?: return false
navigate(dest)
@@ -21,16 +21,16 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.icons.EhIcons
import com.ehviewer.core.ui.icons.big.SadAndroid
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.big.SadAndroid
import com.hippo.ehviewer.ui.Screen
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.lang.withUIContext
import moe.tarsin.coroutines.runSuspendCatching
import moe.tarsin.navigate
@@ -65,8 +65,11 @@ 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.i18n.R
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.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.client.data.TagNamespace
@@ -79,12 +82,9 @@ 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.hippo.ehviewer.ui.tools.thenIf
import com.jamal.composeprefs3.ui.ifNotNullThen
import com.jamal.composeprefs3.ui.ifTrueThen
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
@@ -94,7 +94,7 @@ import kotlinx.coroutines.launch
import moe.tarsin.navigate
fun interface SuggestionProvider {
fun providerSuggestions(text: String): Suggestion?
suspend fun providerSuggestions(text: String): List<Suggestion>
}
abstract class Suggestion {
@@ -164,7 +164,7 @@ fun SearchBarScreen(
fun mergedSuggestionFlow(): Flow<Suggestion> = with(context) {
flow {
val query = searchFieldState.text.toString()
suggestionProvider?.run { providerSuggestions(query)?.let { emit(it) } }
suggestionProvider?.run { providerSuggestions(query).forEach { emit(it) } }
mSearchDatabase.suggestions(query, 128).forEach { emit(KeywordSuggestion(it)) }
val index = if (localSearch) query.lastIndexOf(' ') else query.lastIndexOfAny(TagTerminators)
val keyword = query.substring(index + 1).trimStart()
@@ -21,9 +21,11 @@ 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.i18n.R
import com.ehviewer.core.util.launch
import com.ehviewer.core.util.withUIContext
import com.hippo.ehviewer.BuildConfig
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.download.downloadLocation
@@ -42,9 +44,7 @@ import com.hippo.files.delete
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import eu.kanade.tachiyomi.util.lang.withUIContext
import moe.tarsin.coroutines.runSuspendCatching
import moe.tarsin.launch
import moe.tarsin.navigate
import moe.tarsin.snackbar
import moe.tarsin.string
@@ -108,8 +108,8 @@ fun AnimatedVisibilityScope.AboutScreen(navigator: DestinationsNavigator) = Scre
)
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.auto_updates),
entry = R.array.update_frequency,
entryValueRes = R.array.update_frequency_values,
entry = com.hippo.ehviewer.R.array.update_frequency,
entryValueRes = com.hippo.ehviewer.R.array.update_frequency_values,
state = Settings.updateIntervalDays.asMutableState(),
)
WorkPreference(title = stringResource(id = R.string.settings_about_check_for_updates)) {
@@ -30,9 +30,11 @@ 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.i18n.R
import com.ehviewer.core.util.launch
import com.ehviewer.core.util.withIOContext
import com.hippo.ehviewer.BuildConfig
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.client.EhEngine
@@ -57,7 +59,6 @@ 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.lang.withIOContext
import eu.kanade.tachiyomi.util.system.logcat
import java.io.File
import java.util.zip.ZipEntry
@@ -67,7 +68,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.merge
import me.zhanghai.compose.preference.DropdownListPreference
import moe.tarsin.coroutines.runSuspendCatching
import moe.tarsin.launch
import moe.tarsin.snackbar
import moe.tarsin.string
@@ -169,8 +169,8 @@ fun AnimatedVisibilityScope.AdvancedScreen(navigator: DestinationsNavigator) = S
}
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.settings_advanced_read_cache_size),
entry = R.array.read_cache_size_entries,
entryValueRes = R.array.read_cache_size_entry_values,
entry = com.hippo.ehviewer.R.array.read_cache_size_entries,
entryValueRes = com.hippo.ehviewer.R.array.read_cache_size_entry_values,
state = Settings.readCacheSize.asMutableState(),
)
var currentLanguage by remember { mutableStateOf(getAppLanguage()) }
@@ -29,8 +29,10 @@ 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.i18n.R
import com.ehviewer.core.util.launch
import com.ehviewer.core.util.launchIO
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.client.EhEngine.fillGalleryListByApi
@@ -44,6 +46,7 @@ import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.download.downloadDir
import com.hippo.ehviewer.download.downloadLocation
import com.hippo.ehviewer.spider.COMIC_INFO_FILE
import com.hippo.ehviewer.spider.MIN_SPEED_LEVEL
import com.hippo.ehviewer.spider.SpiderDen
import com.hippo.ehviewer.spider.SpiderQueen.Companion.SPIDER_INFO_FILENAME
import com.hippo.ehviewer.spider.readComicInfo
@@ -72,8 +75,6 @@ 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.launch
import moe.tarsin.launchIO
import moe.tarsin.snackbar
import moe.tarsin.string
import okio.Path
@@ -178,29 +179,21 @@ fun AnimatedVisibilityScope.DownloadScreen(navigator: DestinationsNavigator) = S
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.settings_download_concurrency),
summary = stringResource(id = R.string.settings_download_concurrency_summary, multiThreadDownload.value),
entry = R.array.multi_thread_download_entries,
entryValueRes = R.array.multi_thread_download_entry_values,
entry = com.hippo.ehviewer.R.array.multi_thread_download_entries,
entryValueRes = com.hippo.ehviewer.R.array.multi_thread_download_entry_values,
state = multiThreadDownload,
)
val downloadDelay = Settings.downloadDelay.asMutableState()
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.settings_download_download_delay),
summary = stringResource(id = R.string.settings_download_download_delay_summary, downloadDelay.value),
entry = R.array.download_delay_entries,
entryValueRes = R.array.download_delay_entry_values,
entry = com.hippo.ehviewer.R.array.download_delay_entries,
entryValueRes = com.hippo.ehviewer.R.array.download_delay_entry_values,
state = downloadDelay,
)
IntSliderPreference(
maxValue = 10,
minValue = 2,
step = 7,
title = stringResource(id = R.string.settings_download_connection_timeout),
state = Settings.connTimeout.asMutableState(),
)
IntSliderPreference(
maxValue = 10,
minValue = 4,
step = 5,
minValue = MIN_SPEED_LEVEL,
title = stringResource(id = R.string.settings_download_timeout_speed),
state = Settings.timeoutSpeed.asMutableState(),
display = ::speedLevelToSpeed,
@@ -209,8 +202,8 @@ fun AnimatedVisibilityScope.DownloadScreen(navigator: DestinationsNavigator) = S
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.settings_download_preload_image),
summary = stringResource(id = R.string.settings_download_preload_image_summary, preloadImage.value),
entry = R.array.preload_image_entries,
entryValueRes = R.array.preload_image_entry_values,
entry = com.hippo.ehviewer.R.array.preload_image_entries,
entryValueRes = com.hippo.ehviewer.R.array.preload_image_entry_values,
state = preloadImage,
)
SwitchPreference(
@@ -28,7 +28,8 @@ import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.fromHtml
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.util.launch
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.client.EhCookieStore
@@ -49,7 +50,6 @@ import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.datetime.LocalTime
import moe.tarsin.launch
import moe.tarsin.navigate
import moe.tarsin.snackbar
@@ -94,7 +94,7 @@ fun AnimatedVisibilityScope.EhScreen(navigator: DestinationsNavigator) = Screen(
val warning = stringResource(id = R.string.settings_eh_identity_cookies_signed)
val state = rememberTextFieldState(cookies.joinToString("\n") { (k, v) -> "$k: $v" })
Text(text = AnnotatedString.fromHtml(warning))
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin)))
Spacer(modifier = Modifier.size(dimensionResource(id = com.hippo.ehviewer.R.dimen.keyline_margin)))
OutlinedTextField(
state = state,
readOnly = true,
@@ -119,8 +119,8 @@ fun AnimatedVisibilityScope.EhScreen(navigator: DestinationsNavigator) = Screen(
val gallerySite = Settings.gallerySite.asMutableState()
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.settings_eh_gallery_site),
entry = R.array.gallery_site_entries,
entryValueRes = R.array.gallery_site_entry_values,
entry = com.hippo.ehviewer.R.array.gallery_site_entries,
entryValueRes = com.hippo.ehviewer.R.array.gallery_site_entry_values,
state = gallerySite,
)
Preference(
@@ -161,8 +161,8 @@ fun AnimatedVisibilityScope.EhScreen(navigator: DestinationsNavigator) = Screen(
}
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.dark_theme),
entry = R.array.night_mode_entries,
entryValueRes = R.array.night_mode_values,
entry = com.hippo.ehviewer.R.array.night_mode_entries,
entryValueRes = com.hippo.ehviewer.R.array.night_mode_values,
state = Settings.theme.asMutableState(),
)
SwitchPreference(
@@ -175,15 +175,15 @@ fun AnimatedVisibilityScope.EhScreen(navigator: DestinationsNavigator) = Screen(
)
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.settings_eh_launch_page),
entry = R.array.launch_page_entries,
entryValueRes = R.array.launch_page_entry_values,
entry = com.hippo.ehviewer.R.array.launch_page_entries,
entryValueRes = com.hippo.ehviewer.R.array.launch_page_entry_values,
state = Settings.launchPage.asMutableState(),
)
val listMode = Settings.listMode.asMutableState()
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.settings_eh_list_mode),
entry = R.array.list_mode_entries,
entryValueRes = R.array.list_mode_entry_values,
entry = com.hippo.ehviewer.R.array.list_mode_entries,
entryValueRes = com.hippo.ehviewer.R.array.list_mode_entry_values,
state = listMode,
)
AnimatedVisibility(visible = listMode.value == 0) {
@@ -197,8 +197,8 @@ fun AnimatedVisibilityScope.EhScreen(navigator: DestinationsNavigator) = Screen(
)
SimpleMenuPreferenceInt(
title = stringResource(id = R.string.settings_eh_detail_size),
entry = R.array.detail_size_entries,
entryValueRes = R.array.detail_size_entry_values,
entry = com.hippo.ehviewer.R.array.detail_size_entries,
entryValueRes = com.hippo.ehviewer.R.array.detail_size_entry_values,
state = Settings.detailSize.asMutableState(),
)
}
@@ -53,7 +53,11 @@ 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.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.util.Await
import com.ehviewer.core.ui.util.thenIf
import com.ehviewer.core.util.async
import com.ehviewer.core.util.launch
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhFilter
import com.hippo.ehviewer.client.EhFilter.forget
@@ -64,17 +68,13 @@ 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.Await
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.ui.tools.dialog
import com.hippo.ehviewer.ui.tools.thenIf
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlin.coroutines.resume
import moe.tarsin.async
import moe.tarsin.coroutines.groupByToObserved
import moe.tarsin.launch
@Destination<RootGraph>
@Composable
@@ -88,7 +88,7 @@ fun AnimatedVisibilityScope.FilterScreen(navigator: DestinationsNavigator) = Scr
fun addFilter() {
launch {
dialog { cont ->
val types = stringArrayResource(id = R.array.filter_entries)
val types = stringArrayResource(id = com.hippo.ehviewer.R.array.filter_entries)
val type = rememberTextFieldState(types[0])
val state = rememberTextFieldState()
var error by remember { mutableStateOf<String?>(null) }
@@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.ui.Screen
import com.hippo.ehviewer.ui.main.NavigationIcon
import com.hippo.ehviewer.ui.openBrowser
@@ -37,7 +37,7 @@ fun AnimatedVisibilityScope.LicenseScreen(navigator: DestinationsNavigator) = Sc
)
},
) { paddingValues ->
val libraries by rememberLibraries(R.raw.aboutlibraries)
val libraries by rememberLibraries(com.hippo.ehviewer.R.raw.aboutlibraries)
LibrariesContainer(
libraries = libraries,
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -9,9 +9,9 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.ehviewer.core.i18n.R
import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewState
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.ui.Screen
import com.hippo.ehviewer.ui.main.NavigationIcon
@@ -21,7 +21,7 @@ import androidx.compose.ui.res.integerArrayResource
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import com.hippo.ehviewer.ui.main.RollingNumber
import com.ehviewer.core.ui.component.RollingNumber
import com.hippo.ehviewer.ui.openBrowser
import com.hippo.ehviewer.util.ProgressDialog
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
@@ -12,8 +12,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import com.ehviewer.core.i18n.R
import com.ehviewer.core.util.launch
import com.hippo.ehviewer.EhApplication.Companion.searchDatabase
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.asMutableState
import com.hippo.ehviewer.ui.Screen
@@ -23,7 +24,6 @@ import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import moe.tarsin.launch
import moe.tarsin.snackbar
@Destination<RootGraph>
@@ -16,9 +16,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import com.hippo.ehviewer.R
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.filled.SadPanda
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.icons.EhIcons
import com.ehviewer.core.ui.icons.filled.SadPanda
import com.hippo.ehviewer.ui.Screen
import com.hippo.ehviewer.ui.destinations.AboutScreenDestination
import com.hippo.ehviewer.ui.destinations.AdvancedScreenDestination
@@ -1,6 +1,5 @@
package com.hippo.ehviewer.ui.settings
import android.webkit.WebView
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@@ -16,10 +15,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.ehviewer.core.i18n.R
import com.ehviewer.core.util.launch
import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewNavigator
import com.google.accompanist.web.rememberWebViewState
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.ui.Screen
@@ -28,7 +28,6 @@ import com.hippo.ehviewer.util.setDefaultSettings
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import moe.tarsin.launch
import moe.tarsin.snackbar
private const val APPLY_JS = "javascript:(function(){var apply = document.getElementById(\"apply\").children[0];apply.click();})();"
@@ -36,7 +35,7 @@ private const val APPLY_JS = "javascript:(function(){var apply = document.getEle
@Destination<RootGraph>
@Composable
fun AnimatedVisibilityScope.UConfigScreen(navigator: DestinationsNavigator) = Screen(navigator) {
val url = EhUrl.uConfigUrl
val url = EhUrl.getUConfigUrl()
val wvNavigator = rememberWebViewNavigator()
Scaffold(
topBar = {
@@ -18,9 +18,9 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import com.ehviewer.core.ui.component.scrollbarStyle
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.collectAsState
import com.hippo.ehviewer.ui.tools.scrollbarStyle
import com.hippo.ehviewer.util.isAtLeastOMR1
import com.hippo.ehviewer.util.isAtLeastS
import com.materialkolor.dynamicColorScheme
@@ -1,18 +1,17 @@
package com.hippo.ehviewer.ui.tools
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.content.Context
import androidx.annotation.StringRes
import androidx.compose.foundation.text.contextmenu.builder.item
import androidx.compose.foundation.text.contextmenu.data.TextContextMenuKeys
import androidx.compose.foundation.text.contextmenu.modifier.appendTextContextMenuComponents
import androidx.compose.foundation.text.contextmenu.modifier.filterTextContextMenuComponents
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.TextToolbar
import androidx.compose.ui.platform.TextToolbarStatus
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
@@ -25,8 +24,7 @@ import androidx.compose.ui.text.input.getTextAfterSelection
import androidx.compose.ui.text.input.getTextBeforeSelection
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.MainActivity
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.util.toRangeSet
import io.github.petertrr.diffutils.diffInline
import io.github.petertrr.diffutils.patch.ChangeDelta
@@ -36,8 +34,7 @@ import io.github.petertrr.diffutils.patch.InsertDelta
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import moe.tarsin.kt.unreachable
typealias PlatformRect = android.graphics.Rect
import moe.tarsin.string
fun TextFieldValue.updateSpan(origin: TextFieldValue): TextFieldValue {
val oriSpan = origin.annotatedString.spanStyles
@@ -141,118 +138,81 @@ fun AnnotatedString.toBBCode() = buildString {
}
@Composable
context(activity: MainActivity)
fun rememberBBCodeTextToolbar(textFieldValue: MutableState<TextFieldValue>): TextToolbar {
context(_: Context)
fun Modifier.addBBCodeTextContextMenuItems(textFieldValue: MutableState<TextFieldValue>): Modifier {
var tfv by textFieldValue
val view = LocalView.current
val coroutineScope = rememberCoroutineScope()
val toolbar = remember {
object : TextToolbar {
private var actionMode: ActionMode? = null
private val callback = object : TextActionModeCallback() {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
super.onCreateActionMode(mode, menu)
activity.menuInflater.inflate(R.menu.context_comment, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
super.onPrepareActionMode(mode, menu)
if (tfv.selection.collapsed) {
menu.removeGroup(R.id.bbcode_group)
return appendTextContextMenuComponents {
if (tfv.selection.collapsed) return@appendTextContextMenuComponents
BBCodeFormat.entries.forEach { item ->
item(key = item, label = string(item.id)) {
val capturedTfv = tfv
val start = capturedTfv.selection.min
val end = capturedTfv.selection.max
val annotatedString = buildAnnotatedString {
val len = capturedTfv.text.length
fun addStyle(style: SpanStyle) {
append(capturedTfv.annotatedString)
addStyle(style, start, end)
}
return true
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
val handled = super.onActionItemClicked(mode, item)
if (handled) {
return true
}
val capturedTfv = tfv
val start = capturedTfv.selection.min
val end = capturedTfv.selection.max
val annotatedString = buildAnnotatedString {
val len = capturedTfv.text.length
fun addStyle(style: SpanStyle) {
append(capturedTfv.annotatedString)
addStyle(style, start, end)
fun addTextDecoration(decoration: TextDecoration) {
append(capturedTfv.getTextBeforeSelection(len))
val ans = capturedTfv.getSelectedText()
val spans = ans.spanStyles.filter {
it.item.textDecoration == null || it.item.textDecoration == decoration
}
fun addTextDecoration(decoration: TextDecoration) {
withStyle(SpanStyle(textDecoration = decoration)) {
append(AnnotatedString(ans.text, spans))
}
append(capturedTfv.getTextAfterSelection(len))
}
when (item) {
BBCodeFormat.Bold -> addStyle(SpanStyle(fontWeight = FontWeight.Bold))
BBCodeFormat.Italic -> addStyle(SpanStyle(fontStyle = FontStyle.Italic))
BBCodeFormat.Underline -> addTextDecoration(TextDecoration.Underline)
BBCodeFormat.Strikethrough -> addTextDecoration(TextDecoration.LineThrough)
BBCodeFormat.Url -> append(capturedTfv.annotatedString)
BBCodeFormat.Clear -> {
append(capturedTfv.getTextBeforeSelection(len))
val ans = capturedTfv.getSelectedText()
val spans = ans.spanStyles.filter {
it.item.textDecoration == null || it.item.textDecoration == decoration
}
withStyle(SpanStyle(textDecoration = decoration)) {
append(AnnotatedString(ans.text, spans))
}
append(capturedTfv.getSelectedText().text)
append(capturedTfv.getTextAfterSelection(len))
}
when (item.itemId) {
R.id.action_bold -> addStyle(SpanStyle(fontWeight = FontWeight.Bold))
R.id.action_italic -> addStyle(SpanStyle(fontStyle = FontStyle.Italic))
R.id.action_underline -> addTextDecoration(TextDecoration.Underline)
R.id.action_strikethrough -> addTextDecoration(TextDecoration.LineThrough)
R.id.action_url -> append(capturedTfv.annotatedString)
R.id.action_clear -> {
append(capturedTfv.getTextBeforeSelection(len))
append(capturedTfv.getSelectedText().text)
append(capturedTfv.getTextAfterSelection(len))
}
}
}
tfv = TextFieldValue(
annotatedString = buildAnnotatedString {
append(annotatedString)
// Hacky: Trigger recomposition to hide text toolbar
append(Char.MIN_VALUE)
},
selection = TextRange(end),
)
coroutineScope.launch {
// Hacky: Let TextField recompose first
delay(100)
tfv = tfv.copy(annotatedString = annotatedString)
}
mode.finish()
return true
}
override fun onDestroyActionMode(mode: ActionMode) {
actionMode = null
}
}
override var status = TextToolbarStatus.Hidden
override fun hide() {
status = TextToolbarStatus.Hidden
actionMode?.finish()
actionMode = null
}
override fun showMenu(
rect: Rect,
onCopyRequested: (() -> Unit)?,
onPasteRequested: (() -> Unit)?,
onCutRequested: (() -> Unit)?,
onSelectAllRequested: (() -> Unit)?,
) {
callback.rect = rect
callback.onCopyRequested = onCopyRequested
callback.onCutRequested = onCutRequested
callback.onPasteRequested = onPasteRequested
callback.onSelectAllRequested = onSelectAllRequested
if (actionMode == null) {
status = TextToolbarStatus.Shown
actionMode = view.startActionMode(callback, ActionMode.TYPE_FLOATING)
} else {
actionMode?.invalidate()
tfv = TextFieldValue(
annotatedString = buildAnnotatedString {
append(annotatedString)
// Hacky: Trigger recomposition to hide text toolbar
append(Char.MIN_VALUE)
},
selection = TextRange(end),
)
coroutineScope.launch {
// Hacky: Let TextField recompose first
delay(100)
tfv = tfv.copy(annotatedString = annotatedString)
}
close()
}
}
}.filterTextContextMenuComponents {
when (it.key) {
TextContextMenuKeys.CutKey,
TextContextMenuKeys.CopyKey,
TextContextMenuKeys.PasteKey,
TextContextMenuKeys.SelectAllKey,
is BBCodeFormat,
-> true
else -> false
}
}
return toolbar
}
private enum class BBCodeFormat(@StringRes val id: Int) {
Bold(R.string.format_bold),
Italic(R.string.format_italic),
Underline(R.string.format_underline),
Strikethrough(R.string.format_strikethrough),
Url(R.string.format_url),
Clear(R.string.format_plain),
}
@@ -4,11 +4,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisallowComposableCalls
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.datastore.dataStoreFile
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.byteArrayPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.preferencesDataStoreFile
import androidx.lifecycle.compose.LifecycleResumeEffect
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -22,7 +22,7 @@ import kotlinx.serialization.serializer
import splitties.init.appCtx
val dataStoreScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
val dataStore = PreferenceDataStoreFactory.create { appCtx.dataStoreFile("Remembered.preferences_pb") }
val dataStore = PreferenceDataStoreFactory.create { appCtx.preferencesDataStoreFile("Remembered") }
val dataStateFlow = dataStore.data.stateIn(dataStoreScope, SharingStarted.Eagerly, emptyPreferences())
// Find out how make this work with any generic `Serializable` type
@@ -93,7 +93,9 @@ import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.right
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.ehviewer.core.ui.component.FastScrollLazyColumn
import com.ehviewer.core.ui.component.LabeledCheckbox
import com.hippo.ehviewer.client.EhTagDatabase
import com.hippo.ehviewer.client.EhTagDatabase.suggestion
import com.jamal.composeprefs3.ui.ifNotNullThen
@@ -182,7 +184,7 @@ suspend fun <R> awaitResult(
)
}
context(ctx: Context, state: DialogState)
context(_: Context, _: DialogState)
suspend fun awaitSelectTags(): List<String> = dialog { cont ->
val selected = remember { mutableStateListOf<String>() }
val state = rememberTextFieldState()
@@ -1,116 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.tools
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.compose.ui.geometry.Rect
open class TextActionModeCallback(
var rect: Rect = Rect.Zero,
var onCopyRequested: (() -> Unit)? = null,
var onPasteRequested: (() -> Unit)? = null,
var onCutRequested: (() -> Unit)? = null,
var onSelectAllRequested: (() -> Unit)? = null,
) : ActionMode.Callback2() {
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
onCopyRequested?.let {
addMenuItem(menu, MenuItemOption.Copy)
}
onPasteRequested?.let {
addMenuItem(menu, MenuItemOption.Paste)
}
onCutRequested?.let {
addMenuItem(menu, MenuItemOption.Cut)
}
onSelectAllRequested?.let {
addMenuItem(menu, MenuItemOption.SelectAll)
}
return true
}
// this method is called to populate new menu items when the actionMode was invalidated
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
updateMenuItems(menu)
// should return true so that new menu items are populated
return true
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
when (item.itemId) {
MenuItemOption.Copy.id -> onCopyRequested?.invoke()
MenuItemOption.Paste.id -> onPasteRequested?.invoke()
MenuItemOption.Cut.id -> onCutRequested?.invoke()
MenuItemOption.SelectAll.id -> onSelectAllRequested?.invoke()
else -> return false
}
mode.finish()
return true
}
override fun onDestroyActionMode(mode: ActionMode) = Unit
override fun onGetContentRect(mode: ActionMode, view: View, outRect: PlatformRect) {
outRect.set(rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt())
}
private fun updateMenuItems(menu: Menu) {
addOrRemoveMenuItem(menu, MenuItemOption.Copy, onCopyRequested)
addOrRemoveMenuItem(menu, MenuItemOption.Paste, onPasteRequested)
addOrRemoveMenuItem(menu, MenuItemOption.Cut, onCutRequested)
addOrRemoveMenuItem(menu, MenuItemOption.SelectAll, onSelectAllRequested)
}
private fun addMenuItem(menu: Menu, item: MenuItemOption) {
menu.add(0, item.id, item.order, item.titleResource)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
}
private fun addOrRemoveMenuItem(
menu: Menu,
item: MenuItemOption,
callback: (() -> Unit)?,
) {
when {
callback != null && menu.findItem(item.id) == null -> addMenuItem(menu, item)
callback == null && menu.findItem(item.id) != null -> menu.removeItem(item.id)
}
}
}
enum class MenuItemOption(val id: Int) {
Copy(0),
Paste(1),
Cut(2),
SelectAll(3),
;
val titleResource: Int
get() = when (this) {
Copy -> android.R.string.copy
Paste -> android.R.string.paste
Cut -> android.R.string.cut
SelectAll -> android.R.string.selectAll
}
/**
* This item will be shown before all items that have order greater than this value.
*/
val order = id
}
@@ -109,8 +109,8 @@ private suspend inline fun ghStatement(
private val GithubTokenParts = arrayOf(
"github",
"pat",
"11A4H2ACI0XXTRsYkt1Mps",
"uJ5n8UbLv20j6Ue4k1x25Z7u63EaCdDNYooxSeI4GJaQYKXWDZEtTmDVwnh",
"11A4H2ACI0iGDuL1O6wPYW",
"OTFg8xaCNUwR1NHaJE1AT3LoYPfz6bouI7E7ReLf8GjIRFHCL5UsHL9EnWP",
)
data class Release(
@@ -20,7 +20,7 @@ import android.content.Intent
import android.widget.Toast
import arrow.core.Either
import arrow.core.getOrElse
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
object AppHelper {
fun share(from: Activity, text: String?): Boolean {
@@ -1,28 +0,0 @@
package com.hippo.ehviewer.util
import kotlin.concurrent.atomics.AtomicInt
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
inline fun AtomicInt.loop(action: (Int) -> Unit): Nothing {
contract { callsInPlace(action, InvocationKind.AT_LEAST_ONCE) }
do {
action(load())
} while (true)
}
inline fun <R> AtomicInt.update(function: (Int) -> Int, transform: (old: Int, new: Int) -> R): R {
contract {
callsInPlace(function, InvocationKind.AT_LEAST_ONCE)
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
loop { cur ->
val upd = function(cur)
if (compareAndSet(cur, upd)) return transform(cur, upd)
}
}
inline fun AtomicInt.updateAndGet(function: (Int) -> Int): Int {
contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) }
return update(function) { _, new -> new }
}
@@ -24,7 +24,7 @@ import android.content.Context
import android.os.Build
import android.view.textclassifier.TextClassifier
import androidx.core.os.persistableBundleOf
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import com.hippo.ehviewer.ui.MainActivity
import moe.tarsin.tip
import splitties.systemservices.clipboardManager
@@ -39,7 +39,7 @@ fun copyTextToClipboard(text: CharSequence?, isSensitive: Boolean) {
)
}
context(ctx: Context)
context(_: Context)
fun addTextToClipboard(text: CharSequence?, useToast: Boolean = false) {
copyTextToClipboard(text, false)
// Avoid double notify user since system have done that on Tiramisu above
@@ -15,7 +15,7 @@
*/
package com.hippo.ehviewer.util
import com.hippo.ehviewer.R
import com.ehviewer.core.i18n.R
import eu.kanade.tachiyomi.util.system.logcat
import io.ktor.client.network.sockets.ConnectTimeoutException
import io.ktor.client.network.sockets.SocketTimeoutException
Loaded 100 of 226 files, more files were not shown because too many files have changed in this diff. Show more