Compare commits

...
10 Commits
Author SHA1 Message Date
FooIbar f69521fc1a Prepare for release 1.11.1
Releases / build (push) Has been cancelled
2024-03-12 22:45:48 +08:00
FooIbar 01b4054368 Prepare changelog for 1.11.1 2024-03-12 22:34:27 +08:00
FooIbar e07aab4434 Drop basic login as captcha is mandatory now 2024-03-12 21:27:01 +08:00
FooIbar 18caa87512 Implement group by download label 2024-03-12 21:26:09 +08:00
FooIbar 5676de24da Merge default and marshmallow flavors 2024-03-12 12:05:12 +08:00
FooIbar 57bbb1fe38 Embed Cronet and drop OkHttp (#895)
* Embed Cronet and drop OkHttp

* Fix updater
2024-03-11 12:22:39 +00:00
FooIbar dcbfbc2e5f Fix concurrent modification 2024-03-11 15:04:56 +08:00
FooIbar e1f473d301 Convert remaining icons to ImageVector 2024-03-11 12:43:52 +08:00
renovate[bot] a33256d29f Update dependency sh.calvin.reorderable:reorderable to v1.3.3 2024-03-10 11:12:12 +00:00
FooIbar 6a81877b09 Prepare for next iteration 2024-03-09 19:17:18 +08:00
75 changed files with 948 additions and 1312 deletions

No files matched your search

+25 -284
View File
@@ -8,7 +8,7 @@ on:
workflow_dispatch:
jobs:
check:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -27,7 +27,6 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
- name: Rustfmt Check
working-directory: ./app/src/main/rust
@@ -40,312 +39,54 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: true
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
gradle-home-cache-cleanup: true
- name: Spotless Check
run: ./gradlew spotlessCheck
oss:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
save-if: "false"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
gradle-home-cache-cleanup: true
- name: CMake Cache
uses: actions/cache@v4
with:
path: app/.cxx
key: cmake-default-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: cmake-default-
key: cmake-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: cmake-
- name: Build OSS
run: ./gradlew assembleDefaultOssRelease
- name: Build
run: ./gradlew assembleRelease
- name: Upload OSS ARM64
- name: Upload ARM64
uses: actions/upload-artifact@v4
with:
name: default-oss-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-arm64-v8a-release.apk
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Upload OSS x86_64
- name: Upload x86_64
uses: actions/upload-artifact@v4
with:
name: default-oss-x86_64-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-x86_64-release.apk
name: x86_64-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86_64-release.apk
- name: Upload OSS ARM32
- name: Upload ARM32
uses: actions/upload-artifact@v4
with:
name: default-oss-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-armeabi-v7a-release.apk
name: armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
- name: Upload OSS x86
- name: Upload x86
uses: actions/upload-artifact@v4
with:
name: default-oss-x86-${{ github.sha }}
path: app/build/outputs/apk/defaultOss/release/app-default-oss-x86-release.apk
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
- name: Upload OSS mapping
- name: Upload mapping
uses: actions/upload-artifact@v4
with:
name: default-oss-mapping-${{ github.sha }}
path: app/build/outputs/mapping/defaultOssRelease/mapping.txt
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/release/mapping.txt
- name: Upload OSS native debug symbols
- name: Upload native debug symbols
uses: actions/upload-artifact@v4
with:
name: default-oss-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/defaultOssRelease/native-debug-symbols.zip
gms:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
save-if: "false"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
gradle-home-cache-cleanup: true
- name: CMake Cache
uses: actions/cache/restore@v4
with:
path: app/.cxx
key: cmake-default-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: cmake-default-
- name: Install Build Tools
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "build-tools;35.0.0-rc1"
- name: Build GMS
run: ./gradlew assembleDefaultGmsRelease
- name: Upload GMS ARM64
uses: actions/upload-artifact@v4
with:
name: default-gms-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-arm64-v8a-release.apk
- name: Upload GMS x86_64
uses: actions/upload-artifact@v4
with:
name: default-gms-x86_64-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-x86_64-release.apk
- name: Upload GMS ARM32
uses: actions/upload-artifact@v4
with:
name: default-gms-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-armeabi-v7a-release.apk
- name: Upload GMS x86
uses: actions/upload-artifact@v4
with:
name: default-gms-x86-${{ github.sha }}
path: app/build/outputs/apk/defaultGms/release/app-default-gms-x86-release.apk
- name: Upload GMS mapping
uses: actions/upload-artifact@v4
with:
name: default-gms-mapping-${{ github.sha }}
path: app/build/outputs/mapping/defaultGmsRelease/mapping.txt
- name: Upload GMS native debug symbols
uses: actions/upload-artifact@v4
with:
name: default-gms-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/defaultGmsRelease/native-debug-symbols.zip
marshmallow-oss:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
save-if: "false"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
gradle-home-cache-cleanup: true
- name: CMake Cache
uses: actions/cache@v4
with:
path: app/.cxx
key: cmake-marshmallow-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: cmake-marshmallow-
- name: Install Build Tools
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "build-tools;35.0.0-rc1"
- name: Build Marshmallow OSS
run: ./gradlew assembleMarshmallowOssRelease
- name: Upload Marshmallow OSS ARM64
uses: actions/upload-artifact@v4
with:
name: marshmallow-oss-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-arm64-v8a-release.apk
- name: Upload Marshmallow OSS x86_64
uses: actions/upload-artifact@v4
with:
name: marshmallow-oss-x86_64-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-x86_64-release.apk
- name: Upload Marshmallow OSS ARM32
uses: actions/upload-artifact@v4
with:
name: marshmallow-oss-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-armeabi-v7a-release.apk
- name: Upload Marshmallow OSS x86
uses: actions/upload-artifact@v4
with:
name: marshmallow-oss-x86-${{ github.sha }}
path: app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-x86-release.apk
- name: Upload Marshmallow OSS mapping
uses: actions/upload-artifact@v4
with:
name: marshmallow-oss-mapping-${{ github.sha }}
path: app/build/outputs/mapping/marshmallowOssRelease/mapping.txt
- name: Upload Marshmallow OSS native debug symbols
uses: actions/upload-artifact@v4
with:
name: marshmallow-oss-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/marshmallowOssRelease/native-debug-symbols.zip
marshmallow-gms:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Install Rust Toolchain
run: rustup update && rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android && rustup component add rustfmt clippy
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./app/src/main/rust"
shared-key: ${{ runner.os }}
save-if: "false"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
cache-encryption-key: ${{ secrets.GradleEncryptionKey }}
gradle-home-cache-cleanup: true
- name: CMake Cache
uses: actions/cache/restore@v4
with:
path: app/.cxx
key: cmake-marshmallow-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: cmake-marshmallow-
- name: Install Build Tools
run: echo "y" | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --install "build-tools;35.0.0-rc1"
- name: Build Marshmallow GMS
run: ./gradlew assembleMarshmallowGmsRelease
- name: Upload Marshmallow GMS ARM64
uses: actions/upload-artifact@v4
with:
name: marshmallow-gms-arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-arm64-v8a-release.apk
- name: Upload Marshmallow GMS x86_64
uses: actions/upload-artifact@v4
with:
name: marshmallow-gms-x86_64-${{ github.sha }}
path: app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-x86_64-release.apk
- name: Upload Marshmallow GMS ARM32
uses: actions/upload-artifact@v4
with:
name: marshmallow-gms-armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-armeabi-v7a-release.apk
- name: Upload Marshmallow GMS x86
uses: actions/upload-artifact@v4
with:
name: marshmallow-gms-x86-${{ github.sha }}
path: app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-x86-release.apk
- name: Upload Marshmallow GMS mapping
uses: actions/upload-artifact@v4
with:
name: marshmallow-gms-mapping-${{ github.sha }}
path: app/build/outputs/mapping/marshmallowGmsRelease/mapping.txt
- name: Upload Marshmallow GMS native debug symbols
uses: actions/upload-artifact@v4
with:
name: marshmallow-gms-native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/marshmallowGmsRelease/native-debug-symbols.zip
name: native-debug-symbols-${{ github.sha }}
path: app/build/outputs/native-debug-symbols/release/native-debug-symbols.zip
+18 -69
View File
@@ -6,8 +6,7 @@ on:
- "*"
jobs:
linux:
name: Build
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -30,11 +29,9 @@ jobs:
- name: CMake Cache
uses: actions/cache@v4
with:
path: |
app/.cxx
app/src/main/cpp/nettle/nettle
key: ${{ runner.os }}-cmake-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cmake-
path: app/.cxx
key: cmake-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
restore-keys: cmake-
- name: Gradle Build
uses: gradle/actions/setup-gradle@v3
@@ -43,71 +40,23 @@ jobs:
- name: Rename Apks
run: |
mv app/build/outputs/apk/defaultOss/release/app-default-oss-universal-release.apk EhViewer-${{ github.ref_name }}-default-oss-universal.apk
mv app/build/outputs/apk/defaultOss/release/app-default-oss-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-default-oss-arm64-v8a.apk
mv app/build/outputs/apk/defaultOss/release/app-default-oss-x86_64-release.apk EhViewer-${{ github.ref_name }}-default-oss-x86_64.apk
mv app/build/outputs/apk/defaultOss/release/app-default-oss-x86-release.apk EhViewer-${{ github.ref_name }}-default-oss-x86.apk
mv app/build/outputs/apk/defaultOss/release/app-default-oss-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-default-oss-armeabi-v7a.apk
mv app/build/outputs/mapping/defaultOssRelease/mapping.txt EhViewer-${{ github.ref_name }}-default-oss-mapping.txt
mv app/build/outputs/native-debug-symbols/defaultOssRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-default-oss-native-debug-symbols.zip
mv app/build/outputs/apk/defaultGms/release/app-default-gms-universal-release.apk EhViewer-${{ github.ref_name }}-default-gms-universal.apk
mv app/build/outputs/apk/defaultGms/release/app-default-gms-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-default-gms-arm64-v8a.apk
mv app/build/outputs/apk/defaultGms/release/app-default-gms-x86_64-release.apk EhViewer-${{ github.ref_name }}-default-gms-x86_64.apk
mv app/build/outputs/apk/defaultGms/release/app-default-gms-x86-release.apk EhViewer-${{ github.ref_name }}-default-gms-x86.apk
mv app/build/outputs/apk/defaultGms/release/app-default-gms-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-default-gms-armeabi-v7a.apk
mv app/build/outputs/mapping/defaultGmsRelease/mapping.txt EhViewer-${{ github.ref_name }}-default-gms-mapping.txt
mv app/build/outputs/native-debug-symbols/defaultGmsRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-default-gms-native-debug-symbols.zip
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-universal-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-universal.apk
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-arm64-v8a.apk
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-x86_64-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-x86_64.apk
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-x86-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-x86.apk
mv app/build/outputs/apk/marshmallowOss/release/app-marshmallow-oss-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-oss-armeabi-v7a.apk
mv app/build/outputs/mapping/marshmallowOssRelease/mapping.txt EhViewer-${{ github.ref_name }}-marshmallow-oss-mapping.txt
mv app/build/outputs/native-debug-symbols/marshmallowOssRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-marshmallow-oss-native-debug-symbols.zip
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-universal-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-universal.apk
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-arm64-v8a.apk
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-x86_64-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-x86_64.apk
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-x86-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-x86.apk
mv app/build/outputs/apk/marshmallowGms/release/app-marshmallow-gms-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-gms-armeabi-v7a.apk
mv app/build/outputs/mapping/marshmallowGmsRelease/mapping.txt EhViewer-${{ github.ref_name }}-marshmallow-gms-mapping.txt
mv app/build/outputs/native-debug-symbols/marshmallowGmsRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-marshmallow-gms-native-debug-symbols.zip
mv app/build/outputs/apk/release/app-universal-release.apk EhViewer-${{ github.ref_name }}-universal.apk
mv app/build/outputs/apk/release/app-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-arm64-v8a.apk
mv app/build/outputs/apk/release/app-x86_64-release.apk EhViewer-${{ github.ref_name }}-x86_64.apk
mv app/build/outputs/apk/release/app-x86-release.apk EhViewer-${{ github.ref_name }}-x86.apk
mv app/build/outputs/apk/release/app-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-armeabi-v7a.apk
mv app/build/outputs/mapping/release/mapping.txt EhViewer-${{ github.ref_name }}-mapping.txt
mv app/build/outputs/native-debug-symbols/release/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-native-debug-symbols.zip
- name: Releases
uses: softprops/action-gh-release@v2
with:
body: Bump Version
files: |
EhViewer-${{ github.ref_name }}-default-oss-universal.apk
EhViewer-${{ github.ref_name }}-default-oss-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-default-oss-x86_64.apk
EhViewer-${{ github.ref_name }}-default-oss-x86.apk
EhViewer-${{ github.ref_name }}-default-oss-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-default-oss-mapping.txt
EhViewer-${{ github.ref_name }}-default-oss-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-default-gms-universal.apk
EhViewer-${{ github.ref_name }}-default-gms-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-default-gms-x86_64.apk
EhViewer-${{ github.ref_name }}-default-gms-x86.apk
EhViewer-${{ github.ref_name }}-default-gms-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-default-gms-mapping.txt
EhViewer-${{ github.ref_name }}-default-gms-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-marshmallow-oss-universal.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-x86_64.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-x86.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-marshmallow-oss-mapping.txt
EhViewer-${{ github.ref_name }}-marshmallow-oss-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-marshmallow-gms-universal.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-x86_64.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-x86.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-marshmallow-gms-mapping.txt
EhViewer-${{ github.ref_name }}-marshmallow-gms-native-debug-symbols.zip
EhViewer-${{ github.ref_name }}-universal.apk
EhViewer-${{ github.ref_name }}-arm64-v8a.apk
EhViewer-${{ github.ref_name }}-x86_64.apk
EhViewer-${{ github.ref_name }}-x86.apk
EhViewer-${{ github.ref_name }}-armeabi-v7a.apk
EhViewer-${{ github.ref_name }}-mapping.txt
EhViewer-${{ github.ref_name }}-native-debug-symbols.zip
+18 -1
View File
@@ -1,5 +1,22 @@
# 变更日志
## [1.11.1] - 2024-03-12
### 重大变更
* 由于 E 站强制要求人机验证,仅支持通过网页登录
* 默认支持 Android 6,不再提供 Marshmallow 变种
* 集成 Cronet 库,不再提供 GMS 变种
### 改进
* 支持在排序下载列表前按下载标签分组
### Bug 修复
* 下载画廊时崩溃 [#893](https://github.com/FooIbar/EhViewer/issues/893)
## [1.11.0] - 2024-03-09
### 重大变更
@@ -7,6 +24,7 @@
* 由于 E 站 Cookie 策略变更,不再支持 Cookie 登录
* 登录时自动选择 exhentai (如果可用)
* 不再提供默认下载路径
* 手动排序更改为以添加时间/上传时间/标题/下载标签/页数对下载列表排序
* 由于数据库结构变更,此版本导出的数据将无法被旧版本导入
### 新功能
@@ -19,7 +37,6 @@
* 随机打开本地收藏/下载中的画廊 [#695](https://github.com/FooIbar/EhViewer/issues/695)
* 随机在搜索结果中跳转
* 以网格模式查看下载列表
* 以添加时间/上传时间/标题/下载标签/页数对下载列表排序
* 本地收藏/历史/下载中的标签搜索 [#860](https://github.com/FooIbar/EhViewer/issues/860)
### 改进
-11
View File
@@ -66,17 +66,6 @@ and [Dynamic Color](https://m3.material.io/styles/color/dynamic-color/overview)
# Download
| Flavor | Feature |
|-------------|-----------------------------------|
| Default | Android 9+, full support |
| Marshmallow | Android 6.0-8.1, limited support* |
| OSS | FOSS dependencies** |
| GMS | Proprietary GMS component |
\* May not support some archives
** Fall back to OkHttp on devices that don't support [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine)
<a href="https://github.com/FooIbar/EhViewer/releases">
<img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/>
</a>
+15 -34
View File
@@ -60,10 +60,10 @@ android {
defaultConfig {
applicationId = "moe.tarsin.ehviewer"
minSdk = 28
minSdk = 23
targetSdk = 34
versionCode = 180051
versionName = "1.11.0"
versionCode = 180052
versionName = "1.11.1"
resourceConfigurations.addAll(
listOf(
"zh",
@@ -87,36 +87,16 @@ android {
}
}
flavorDimensions += listOf("api", "oss")
productFlavors {
create("default") {
dimension = "api"
}
create("marshmallow") {
dimension = "api"
minSdk = 23
applicationIdSuffix = ".m"
versionNameSuffix = "-M"
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
create("oss") {
dimension = "oss"
}
create("gms") {
dimension = "oss"
versionNameSuffix = "-gms"
}
}
externalNativeBuild {
cmake {
path = File("src/main/cpp/CMakeLists.txt")
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
kotlinOptions {
freeCompilerArgs = listOf(
// https://kotlinlang.org/docs/compiler-reference.html#progressive
@@ -151,9 +131,11 @@ android {
}
packaging {
dex {
useLegacyPackaging = false
}
resources {
excludes += "/META-INF/**"
excludes += "/okhttp3/**"
excludes += "/kotlin/**"
excludes += "**.txt"
excludes += "**.bin"
@@ -230,10 +212,6 @@ dependencies {
implementation(libs.bundles.splitties)
// https://square.github.io/okhttp/changelogs/changelog/
implementation(platform(libs.okhttp.bom))
implementation(libs.bundles.okhttp)
implementation(libs.okio.jvm)
implementation(libs.logcat)
@@ -257,7 +235,7 @@ dependencies {
implementation(libs.telephoto.zoomable)
implementation(libs.bundles.ktor)
implementation(libs.ktor.client.core)
implementation(libs.bundles.kotlinx.serialization)
@@ -267,7 +245,10 @@ dependencies {
coreLibraryDesugaring(libs.desugar)
"gmsImplementation"(libs.bundles.cronet)
implementation(libs.cronet.embedded)
debugImplementation(libs.compose.ui.tooling)
implementation(libs.compose.ui.tooling.preview)
}
kotlin {
+2
View File
@@ -16,5 +16,7 @@
-dontwarn org.conscrypt.Conscrypt
-keepattributes LineNumberTable
-allowaccessmodification
-repackageclasses
@@ -1,12 +0,0 @@
package com.hippo.ehviewer.util
import com.google.android.gms.net.CronetProviderInstaller
import org.chromium.net.CronetProvider
import splitties.init.appCtx
val isCronetAvailable: Boolean
get() = CronetProvider.getAllProviders(appCtx).any { it.isEnabled }.also {
if (!it) {
CronetProviderInstaller.installProvider(appCtx)
}
}
@@ -54,22 +54,16 @@ import com.hippo.ehviewer.util.Crash
import com.hippo.ehviewer.util.FavouriteStatusRouter
import com.hippo.ehviewer.util.FileUtils
import com.hippo.ehviewer.util.isAtLeastP
import com.hippo.ehviewer.util.isAtLeastQ
import com.hippo.ehviewer.util.isAtLeastS
import com.hippo.ehviewer.util.isCronetAvailable
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
import eu.kanade.tachiyomi.util.lang.launchIO
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
import io.ktor.client.plugins.cookies.HttpCookies
import kotlinx.coroutines.launch
import logcat.AndroidLogcatLogger
import logcat.LogPriority
import logcat.LogcatLogger
import okhttp3.AsyncDns
import okhttp3.android.AndroidAsyncDns
import okio.Path.Companion.toOkioPath
import splitties.arch.room.roomDb
import splitties.init.appCtx
@@ -185,25 +179,9 @@ class EhApplication : Application(), SingletonImageLoader.Factory {
companion object {
val ktorClient by lazy {
if (Settings.enableCronet && isCronetAvailable) {
HttpClient(CronetEngine) {
install(HttpCookies) {
storage = EhCookieStore
}
}
} else {
HttpClient(OkHttp) {
engine {
config {
if (isAtLeastQ) {
dns(AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6))
}
addInterceptor(UncaughtExceptionInterceptor())
}
}
install(HttpCookies) {
storage = EhCookieStore
}
HttpClient(CronetEngine) {
install(HttpCookies) {
storage = EhCookieStore
}
}
}
@@ -154,7 +154,6 @@ object Settings : DataStorePreferences(null) {
var downloadOriginImage by boolPref("download_origin_image", false)
var saveAsCbz by boolPref("save_as_cbz", false)
var archiveMetadata by boolPref("archive_metadata", true)
var enableCronet by boolPref("enable_cronet", true)
var recentFavCat by intPref("recent_fav_cat", FavListUrlBuilder.FAV_CAT_LOCAL)
var defaultFavSlot by intPref("default_favorite_slot", -2)
var securityDelay by intPref("require_unlock_delay", 0)
@@ -44,7 +44,6 @@ import com.hippo.ehviewer.client.parser.GalleryTokenApiParser
import com.hippo.ehviewer.client.parser.HomeParser
import com.hippo.ehviewer.client.parser.ProfileParser
import com.hippo.ehviewer.client.parser.RateGalleryResult
import com.hippo.ehviewer.client.parser.SignInParser
import com.hippo.ehviewer.client.parser.TorrentParser
import com.hippo.ehviewer.client.parser.TorrentResult
import com.hippo.ehviewer.client.parser.VoteCommentResult
@@ -233,22 +232,6 @@ object EhEngine {
suspend fun getFavorites(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsByteBuffer(FavoritesParser::parse)
.apply { galleryInfoList.fillInfo(url) }
suspend fun signIn(username: String, password: String): String {
val referer = "https://forums.e-hentai.org/index.php?act=Login&CODE=00"
val url = EhUrl.API_SIGN_IN
val origin = "https://forums.e-hentai.org"
return ehRequest(url, referer, origin) {
formBody {
append("referer", referer)
append("b", "")
append("bt", "")
append("UserName", username)
append("PassWord", password)
append("CookieDate", "1")
}
}.fetchUsingAsText(SignInParser::parse)
}
suspend fun commentGallery(url: String, comment: String, id: Long = -1) = ehRequest(url, url, EhUrl.origin) {
formBody {
if (id == -1L) {
@@ -36,7 +36,6 @@ object EhUrl {
const val API_E = "https://api.e-hentai.org/api.php"
const val URL_UCONFIG_E = HOST_E + "uconfig.php"
const val URL_MY_TAGS_E = HOST_E + "mytags"
const val API_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login&CODE=01"
const val URL_POPULAR_E = "https://e-hentai.org/popular"
const val URL_POPULAR_EX = "https://exhentai.org/popular"
const val URL_IMAGE_SEARCH_E = "https://upload.e-hentai.org/image_lookup.php"
@@ -1,42 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.parser
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.exception.ParseException
import java.util.regex.Pattern
object SignInParser {
private val NAME_PATTERN = Pattern.compile("<p>You are now logged in as: (.+?)<")
private val ERROR_PATTERN = Pattern.compile(
"<h4>The error returned was:</h4>\\s*<p>(.+?)</p>" +
"|<span class=\"postcolor\">(.+?)</span>",
)
fun parse(body: String): String {
var m = NAME_PATTERN.matcher(body)
return if (m.find()) {
m.group(1)!!
} else {
m = ERROR_PATTERN.matcher(body)
if (m.find()) {
throw EhException(m.group(1) ?: m.group(2))
} else {
throw ParseException("Can't parse sign in")
}
}
}
}
@@ -686,6 +686,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
private val mSpeedReminder = object {
private val mContentLengthMap = SparseLongArray()
private val mReceivedSizeMap = SparseLongArray()
private val mutex = Mutex()
private var mBytesRead = 0L
private var oldSpeed = -1L
private var currentJob: Job? = null
@@ -699,29 +700,37 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
}
}
fun stop() {
suspend fun stop() {
mBytesRead = 0
oldSpeed = -1
mContentLengthMap.clear()
mReceivedSizeMap.clear()
mutex.withLock {
mContentLengthMap.clear()
mReceivedSizeMap.clear()
}
currentJob?.cancel()
currentJob = null
}
fun onDownload(index: Int, contentLength: Long, receivedSize: Long, bytesRead: Int) {
mContentLengthMap.put(index, contentLength)
mReceivedSizeMap.put(index, receivedSize)
suspend fun onDownload(index: Int, contentLength: Long, receivedSize: Long, bytesRead: Int) {
mutex.withLock {
mContentLengthMap.put(index, contentLength)
mReceivedSizeMap.put(index, receivedSize)
}
mBytesRead += bytesRead.toLong()
}
fun onDone(index: Int) {
mContentLengthMap.delete(index)
mReceivedSizeMap.delete(index)
suspend fun onDone(index: Int) {
mutex.withLock {
mContentLengthMap.delete(index)
mReceivedSizeMap.delete(index)
}
}
fun onFinish() {
mContentLengthMap.clear()
mReceivedSizeMap.clear()
suspend fun onFinish() {
mutex.withLock {
mContentLengthMap.clear()
mReceivedSizeMap.clear()
}
}
private suspend fun runInternal() {
@@ -742,12 +751,14 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
var downloadingCount = 0
var downloadingContentLengthSum = 0L
var totalSize = 0L
for (i in 0..<mContentLengthMap.size()) {
val contentLength = mContentLengthMap.valueAt(i)
val receivedSize = mReceivedSizeMap.valueAt(i)
downloadingCount++
downloadingContentLengthSum += contentLength
totalSize += contentLength - receivedSize
mutex.withLock {
for (i in 0..<mContentLengthMap.size()) {
val contentLength = mContentLengthMap.valueAt(i)
val receivedSize = mReceivedSizeMap.valueAt(i)
downloadingCount++
downloadingContentLengthSum += contentLength
totalSize += contentLength - receivedSize
}
}
if (downloadingCount != 0) {
totalSize += downloadingContentLengthSum * (info.total - info.downloaded - downloadingCount) / downloadingCount
@@ -3,9 +3,9 @@ package com.hippo.ehviewer.download
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.dao.DownloadInfo
data class SortMode(val field: Field, val order: Order) {
data class SortMode(val field: Field, val order: Order, val groupByDownloadLabel: Boolean = false) {
val flag
get() = field.value shl 1 or order.value
get() = groupByDownloadLabel.compareTo(false) shl 4 or (field.value shl 1) or order.value
enum class Field(val value: Int) {
GID(0),
@@ -21,22 +21,28 @@ data class SortMode(val field: Field, val order: Order) {
}
fun comparator(): Comparator<DownloadInfo> {
val comparator = when (field) {
var comparator = when (field) {
Field.GID -> compareBy { it.gid }
Field.TITLE -> compareBy { EhUtils.getSuitableTitle(it) }
Field.TITLE, Field.LABEL -> compareBy { EhUtils.getSuitableTitle(it) }
Field.TIME -> compareBy { it.time }
Field.LABEL -> compareBy<DownloadInfo> { it.label }.thenBy { EhUtils.getSuitableTitle(it) }
Field.PAGES -> compareBy<DownloadInfo> { it.pages }.thenBy { EhUtils.getSuitableTitle(it) }
}
return if (order == Order.ASC) comparator else comparator.reversed()
if (order == Order.DESC) {
comparator = comparator.reversed()
}
if (groupByDownloadLabel) {
comparator = compareBy<DownloadInfo> { it.label }.then(comparator)
}
return comparator
}
companion object {
fun from(flag: Int): SortMode {
val field = flag shr 1
val order = flag and 1
val field = flag and 0xF shr 1
val order = flag and 0x1
val groupByDownloadLabel = flag shr 4 == 1
require(field in Field.entries.indices && order in Order.entries.indices)
return SortMode(Field.entries[field], Order.entries[order])
return SortMode(Field.entries[field], Order.entries[order], groupByDownloadLabel)
}
val Default = SortMode(Field.TIME, Order.DESC)
val All = listOf(
@@ -46,8 +52,6 @@ data class SortMode(val field: Field, val order: Order) {
SortMode(Field.GID, Order.ASC),
SortMode(Field.TITLE, Order.ASC),
SortMode(Field.TITLE, Order.DESC),
SortMode(Field.LABEL, Order.ASC),
SortMode(Field.LABEL, Order.DESC),
SortMode(Field.PAGES, Order.ASC),
SortMode(Field.PAGES, Order.DESC),
)
@@ -14,6 +14,8 @@ object EhIcons {
val Default = Filled
}
object Reader
val Default = Filled
}
@@ -1,7 +1,10 @@
package com.hippo.ehviewer.icons.filled
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@@ -37,3 +40,10 @@ val EhIcons.Filled.Crop by unsafeLazy {
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconCropPreview() {
Image(imageVector = EhIcons.Default.Crop, contentDescription = null)
}
@@ -1,10 +1,14 @@
package com.hippo.ehviewer.icons.filled
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@Suppress("BooleanLiteralArgument")
val EhIcons.Filled.CropOff by unsafeLazy {
materialIcon(name = "CropOff") {
materialPath {
@@ -58,3 +62,10 @@ val EhIcons.Filled.CropOff by unsafeLazy {
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconCropOffPreview() {
Image(imageVector = EhIcons.Default.CropOff, contentDescription = null)
}
@@ -1,7 +1,10 @@
package com.hippo.ehviewer.icons.filled
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@@ -31,3 +34,10 @@ val EhIcons.Filled.GoTo by unsafeLazy {
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconGoToPreview() {
Image(imageVector = EhIcons.Default.GoTo, contentDescription = null)
}
@@ -1,7 +1,10 @@
package com.hippo.ehviewer.icons.filled
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@@ -45,3 +48,10 @@ val EhIcons.Filled.SadPanda by unsafeLazy {
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconSadPandaPreview() {
Image(imageVector = EhIcons.Default.SadPanda, contentDescription = null)
}
@@ -1,7 +1,10 @@
package com.hippo.ehviewer.icons.filled
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@@ -59,3 +62,10 @@ val EhIcons.Filled.Subscriptions by unsafeLazy {
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconSubscriptionsPreview() {
Image(imageVector = EhIcons.Default.Subscriptions, contentDescription = null)
}
@@ -0,0 +1,53 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@Suppress("BooleanLiteralArgument")
val EhIcons.Reader.ContinuousVertical by unsafeLazy {
materialIcon(name = "ContinuousVertical") {
materialPath {
moveTo(17.3159F, 1.0F)
horizontalLineToRelative(-10.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, -2.0F, 2.0F)
lineTo(5.3159F, 21.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, 2.0F)
horizontalLineToRelative(10.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, -2.0F)
lineTo(19.3159F, 3.0F)
arcTo(2.0059F, 2.0059F, 0.0F, false, false, 17.3159F, 1.0F)
close()
moveTo(17.3159F, 21.0F)
horizontalLineToRelative(-10.0F)
lineTo(7.3159F, 3.0F)
horizontalLineToRelative(10.0F)
close()
moveTo(11.3083F, 5.0F)
horizontalLineToRelative(2.0F)
verticalLineToRelative(5.5F)
horizontalLineToRelative(-2.0F)
close()
moveTo(15.308F, 16.0F)
lineToRelative(-2.0F, 0.0F)
lineToRelative(0.0F, -4.5F)
lineToRelative(-2.0F, 0.0F)
lineToRelative(0.0F, 4.5F)
lineToRelative(-2.0F, 0.0F)
lineToRelative(3.0F, 3.0F)
lineToRelative(3.0F, -3.0F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconContinuousVerticalPreview() {
Image(imageVector = EhIcons.Reader.ContinuousVertical, contentDescription = null)
}
@@ -0,0 +1,99 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@Suppress("BooleanLiteralArgument")
val EhIcons.Reader.Default by unsafeLazy {
materialIcon(name = "Default") {
materialPath {
moveTo(6.9906F, 18.0F)
lineTo(6.9906F, 6.0F)
horizontalLineToRelative(12.0F)
lineTo(18.9906F, 3.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, -2.0F, -2.0F)
horizontalLineToRelative(-10.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, -2.0F, 2.0F)
lineTo(4.9906F, 21.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, 2.0F)
horizontalLineToRelative(10.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, -2.0F)
lineTo(18.9906F, 18.0F)
horizontalLineToRelative(-12.0F)
close()
moveTo(6.9906F, 3.0F)
horizontalLineToRelative(10.0F)
lineTo(16.9906F, 4.0F)
horizontalLineToRelative(-10.0F)
close()
moveTo(16.9906F, 21.0F)
horizontalLineToRelative(-10.0F)
lineTo(6.9906F, 20.0F)
horizontalLineToRelative(10.0F)
close()
moveTo(21.7706F, 12.44F)
lineToRelative(-0.02F, 0.03F)
arcToRelative(3.5546F, 3.5546F, 0.0F, false, false, 0.05F, -0.47F)
arcToRelative(3.9031F, 3.9031F, 0.0F, false, false, -0.05F, -0.48F)
lineToRelative(0.02F, 0.03F)
lineToRelative(0.85F, -0.68F)
arcToRelative(0.5068F, 0.5068F, 0.0F, false, false, 0.12F, -0.64F)
lineToRelative(-0.84F, -1.46F)
arcToRelative(0.4986F, 0.4986F, 0.0F, false, false, -0.61F, -0.22F)
lineToRelative(-1.01F, 0.39F)
lineToRelative(0.0099F, 0.04F)
arcToRelative(3.6135F, 3.6135F, 0.0F, false, false, -0.8F, -0.48F)
lineToRelative(-0.17F, -1.07F)
arcToRelative(0.4879F, 0.4879F, 0.0F, false, false, -0.49F, -0.43F)
horizontalLineToRelative(-1.68F)
arcToRelative(0.4876F, 0.4876F, 0.0F, false, false, -0.49F, 0.42F)
lineToRelative(-0.17F, 1.08F)
arcToRelative(3.6149F, 3.6149F, 0.0F, false, false, -0.8F, 0.48F)
lineToRelative(0.02F, -0.03F)
lineToRelative(-1.01F, -0.39F)
arcToRelative(0.4878F, 0.4878F, 0.0F, false, false, -0.61F, 0.22F)
lineToRelative(-0.84F, 1.46F)
arcToRelative(0.5068F, 0.5068F, 0.0F, false, false, 0.12F, 0.64F)
lineToRelative(0.85F, 0.68F)
lineToRelative(0.02F, -0.03F)
arcToRelative(3.9148F, 3.9148F, 0.0F, false, false, -0.05F, 0.48F)
arcToRelative(3.915F, 3.915F, 0.0F, false, false, 0.05F, 0.48F)
lineToRelative(-0.02F, -0.03F)
lineToRelative(-0.85F, 0.68F)
arcToRelative(0.5068F, 0.5068F, 0.0F, false, false, -0.12F, 0.64F)
lineToRelative(0.84F, 1.46F)
arcToRelative(0.4985F, 0.4985F, 0.0F, false, false, 0.61F, 0.22F)
lineToRelative(1.01F, -0.39F)
lineToRelative(-0.02F, -0.03F)
arcToRelative(4.4991F, 4.4991F, 0.0F, false, false, 0.8F, 0.46F)
lineToRelative(0.17F, 1.08F)
arcToRelative(0.4876F, 0.4876F, 0.0F, false, false, 0.49F, 0.42F)
horizontalLineToRelative(1.68F)
arcToRelative(0.4876F, 0.4876F, 0.0F, false, false, 0.49F, -0.42F)
lineToRelative(0.17F, -1.08F)
arcToRelative(3.6138F, 3.6138F, 0.0F, false, false, 0.8F, -0.48F)
lineToRelative(-0.02F, 0.03F)
lineToRelative(1.02F, 0.39F)
arcToRelative(0.4879F, 0.4879F, 0.0F, false, false, 0.61F, -0.22F)
lineToRelative(0.84F, -1.46F)
arcToRelative(0.5068F, 0.5068F, 0.0F, false, false, -0.12F, -0.64F)
close()
moveTo(17.9906F, 14.0F)
arcToRelative(2.0F, 2.0F, 0.0F, true, true, 2.0F, -2.0F)
arcTo(2.0059F, 2.0059F, 0.0F, false, true, 17.9906F, 14.0F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconDefaultPreview() {
Image(imageVector = EhIcons.Reader.Default, contentDescription = null)
}
@@ -0,0 +1,39 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
val EhIcons.Reader.Landscape by unsafeLazy {
materialIcon(name = "Landscape") {
materialPath {
moveTo(1.01F, 7.0F)
lineTo(1.0F, 17.0F)
curveToRelative(0.0F, 1.1F, 0.9F, 2.0F, 2.0F, 2.0F)
horizontalLineToRelative(18.0F)
curveToRelative(1.1F, 0.0F, 2.0F, -0.9F, 2.0F, -2.0F)
verticalLineTo(7.0F)
curveToRelative(0.0F, -1.1F, -0.9F, -2.0F, -2.0F, -2.0F)
horizontalLineTo(3.0F)
curveToRelative(-1.1F, 0.0F, -1.99F, 0.9F, -1.99F, 2.0F)
close()
moveTo(19.0F, 7.0F)
verticalLineToRelative(10.0F)
horizontalLineTo(5.0F)
verticalLineTo(7.0F)
horizontalLineToRelative(14.0F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconLandscapePreview() {
Image(imageVector = EhIcons.Reader.Landscape, contentDescription = null)
}
@@ -0,0 +1,59 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
val EhIcons.Reader.LandscapeLocked by unsafeLazy {
materialIcon(name = "LandscapeLocked") {
materialPath {
moveTo(21.0F, 5.0F)
lineTo(3.0F, 5.0F)
curveToRelative(-1.1F, 0.0F, -2.0F, 0.9F, -2.0F, 2.0F)
verticalLineToRelative(10.0F)
curveToRelative(0.0F, 1.1F, 0.9F, 2.0F, 2.0F, 2.0F)
horizontalLineToRelative(18.0F)
curveToRelative(1.1F, 0.0F, 2.0F, -0.9F, 2.0F, -2.0F)
lineTo(23.0F, 7.0F)
curveToRelative(0.0F, -1.1F, -0.9F, -2.0F, -2.0F, -2.0F)
close()
moveTo(19.0F, 17.0F)
lineTo(5.0F, 17.0F)
lineTo(5.0F, 7.0F)
horizontalLineToRelative(14.0F)
verticalLineToRelative(10.0F)
close()
moveTo(10.0F, 16.0F)
horizontalLineToRelative(4.0F)
curveToRelative(0.55F, 0.0F, 1.0F, -0.45F, 1.0F, -1.0F)
verticalLineToRelative(-3.0F)
curveToRelative(0.0F, -0.55F, -0.45F, -1.0F, -1.0F, -1.0F)
verticalLineToRelative(-1.0F)
curveToRelative(0.0F, -1.11F, -0.9F, -2.0F, -2.0F, -2.0F)
curveToRelative(-1.11F, 0.0F, -2.0F, 0.9F, -2.0F, 2.0F)
verticalLineToRelative(1.0F)
curveToRelative(-0.55F, 0.0F, -1.0F, 0.45F, -1.0F, 1.0F)
verticalLineToRelative(3.0F)
curveToRelative(0.0F, 0.55F, 0.45F, 1.0F, 1.0F, 1.0F)
close()
moveTo(10.8F, 10.0F)
curveToRelative(0.0F, -0.66F, 0.54F, -1.2F, 1.2F, -1.2F)
curveToRelative(0.66F, 0.0F, 1.2F, 0.54F, 1.2F, 1.2F)
verticalLineToRelative(1.0F)
horizontalLineToRelative(-2.4F)
verticalLineToRelative(-1.0F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconLandscapeLockedPreview() {
Image(imageVector = EhIcons.Reader.LandscapeLocked, contentDescription = null)
}
@@ -0,0 +1,75 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@Suppress("BooleanLiteralArgument")
val EhIcons.Reader.LeftToRight by unsafeLazy {
materialIcon(name = "LeftToRight") {
materialPath {
moveTo(17.3159F, 18.0F)
horizontalLineToRelative(-10.0F)
lineTo(7.3159F, 16.0F)
horizontalLineToRelative(-2.0F)
verticalLineToRelative(5.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, 2.0F)
horizontalLineToRelative(10.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, -2.0F)
lineTo(19.3159F, 16.0F)
horizontalLineToRelative(-2.0F)
close()
moveTo(17.3159F, 21.0F)
horizontalLineToRelative(-10.0F)
lineTo(7.3159F, 20.0F)
horizontalLineToRelative(10.0F)
close()
moveTo(7.3159F, 6.0F)
horizontalLineToRelative(10.0F)
lineTo(17.3159F, 8.0F)
horizontalLineToRelative(2.0F)
lineTo(19.3159F, 3.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, -2.0F, -2.0F)
horizontalLineToRelative(-10.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, -2.0F, 2.0F)
lineTo(5.3159F, 8.0F)
horizontalLineToRelative(2.0F)
close()
moveTo(7.3159F, 3.0F)
horizontalLineToRelative(10.0F)
lineTo(17.3159F, 4.0F)
horizontalLineToRelative(-10.0F)
close()
moveTo(22.311F, 12.0F)
lineToRelative(-3.0F, -3.0F)
lineToRelative(0.0F, 2.0F)
lineToRelative(-11.99F, 0.0F)
lineToRelative(0.0F, 2.0F)
lineToRelative(11.99F, 0.0F)
lineToRelative(0.0F, 2.0F)
lineToRelative(3.0F, -3.0F)
close()
moveTo(2.3206F, 11.0F)
horizontalLineToRelative(1.5F)
verticalLineToRelative(2.0F)
horizontalLineToRelative(-1.5F)
close()
moveTo(4.8206F, 11.0F)
horizontalLineToRelative(1.5F)
verticalLineToRelative(2.0F)
horizontalLineToRelative(-1.5F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconLeftToRightPreview() {
Image(imageVector = EhIcons.Reader.LeftToRight, contentDescription = null)
}
@@ -0,0 +1,39 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
val EhIcons.Reader.Portrait by unsafeLazy {
materialIcon(name = "Portrait") {
materialPath {
moveTo(17.0F, 1.01F)
lineTo(7.0F, 1.0F)
curveToRelative(-1.1F, 0.0F, -1.99F, 0.9F, -1.99F, 2.0F)
verticalLineToRelative(18.0F)
curveToRelative(0.0F, 1.1F, 0.89F, 2.0F, 1.99F, 2.0F)
horizontalLineToRelative(10.0F)
curveToRelative(1.1F, 0.0F, 2.0F, -0.9F, 2.0F, -2.0F)
verticalLineTo(3.0F)
curveToRelative(0.0F, -1.1F, -0.9F, -1.99F, -2.0F, -1.99F)
close()
moveTo(17.0F, 19.0F)
horizontalLineTo(7.0F)
verticalLineTo(5.0F)
horizontalLineToRelative(10.0F)
verticalLineToRelative(14.0F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconPortraitPreview() {
Image(imageVector = EhIcons.Reader.Portrait, contentDescription = null)
}
@@ -0,0 +1,59 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
val EhIcons.Reader.PortraitLocked by unsafeLazy {
materialIcon(name = "PortraitLocked") {
materialPath {
moveTo(10.0F, 16.0F)
horizontalLineToRelative(4.0F)
curveToRelative(0.55F, 0.0F, 1.0F, -0.45F, 1.0F, -1.0F)
verticalLineToRelative(-3.0F)
curveToRelative(0.0F, -0.55F, -0.45F, -1.0F, -1.0F, -1.0F)
verticalLineToRelative(-1.0F)
curveToRelative(0.0F, -1.11F, -0.9F, -2.0F, -2.0F, -2.0F)
curveToRelative(-1.11F, 0.0F, -2.0F, 0.9F, -2.0F, 2.0F)
verticalLineToRelative(1.0F)
curveToRelative(-0.55F, 0.0F, -1.0F, 0.45F, -1.0F, 1.0F)
verticalLineToRelative(3.0F)
curveToRelative(0.0F, 0.55F, 0.45F, 1.0F, 1.0F, 1.0F)
close()
moveTo(10.8F, 10.0F)
curveToRelative(0.0F, -0.66F, 0.54F, -1.2F, 1.2F, -1.2F)
curveToRelative(0.66F, 0.0F, 1.2F, 0.54F, 1.2F, 1.2F)
verticalLineToRelative(1.0F)
horizontalLineToRelative(-2.4F)
verticalLineToRelative(-1.0F)
close()
moveTo(17.0F, 1.0F)
lineTo(7.0F, 1.0F)
curveToRelative(-1.1F, 0.0F, -2.0F, 0.9F, -2.0F, 2.0F)
verticalLineToRelative(18.0F)
curveToRelative(0.0F, 1.1F, 0.9F, 2.0F, 2.0F, 2.0F)
horizontalLineToRelative(10.0F)
curveToRelative(1.1F, 0.0F, 2.0F, -0.9F, 2.0F, -2.0F)
lineTo(19.0F, 3.0F)
curveToRelative(0.0F, -1.1F, -0.9F, -2.0F, -2.0F, -2.0F)
close()
moveTo(17.0F, 19.0F)
lineTo(7.0F, 19.0F)
lineTo(7.0F, 5.0F)
horizontalLineToRelative(10.0F)
verticalLineToRelative(14.0F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconPortraitLockedPreview() {
Image(imageVector = EhIcons.Reader.PortraitLocked, contentDescription = null)
}
@@ -0,0 +1,75 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@Suppress("BooleanLiteralArgument")
val EhIcons.Reader.RightToLeft by unsafeLazy {
materialIcon(name = "RightToLeft") {
materialPath {
moveTo(7.0F, 6.0F)
horizontalLineTo(17.0F)
verticalLineTo(8.0F)
horizontalLineToRelative(2.0F)
verticalLineTo(3.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, -2.0F, -2.0F)
horizontalLineTo(7.0F)
arcTo(2.0059F, 2.0059F, 0.0F, false, false, 5.0F, 3.0F)
verticalLineTo(8.0F)
horizontalLineTo(7.0F)
close()
moveTo(7.0F, 3.0F)
horizontalLineTo(17.0F)
verticalLineTo(4.0F)
horizontalLineTo(7.0F)
close()
moveTo(17.0F, 18.0F)
lineTo(7.0F, 18.0F)
lineTo(7.0F, 16.0F)
lineTo(5.0F, 16.0F)
verticalLineToRelative(5.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, 2.0F)
lineTo(17.0F, 23.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, -2.0F)
lineTo(19.0F, 16.0F)
lineTo(17.0F, 16.0F)
close()
moveTo(17.0F, 21.0F)
lineTo(7.0F, 21.0F)
lineTo(7.0F, 20.0F)
lineTo(17.0F, 20.0F)
close()
moveTo(5.005F, 13.0F)
lineToRelative(11.99F, 0.0F)
lineToRelative(0.0F, -2.0F)
lineToRelative(-11.99F, 0.0F)
lineToRelative(0.0F, -2.0F)
lineToRelative(-3.0F, 3.0F)
lineToRelative(3.0F, 3.0F)
lineToRelative(0.0F, -2.0F)
close()
moveTo(20.4953F, 11.0F)
horizontalLineToRelative(1.5F)
verticalLineToRelative(2.0F)
horizontalLineToRelative(-1.5F)
close()
moveTo(17.9953F, 11.0F)
horizontalLineToRelative(1.5F)
verticalLineToRelative(2.0F)
horizontalLineToRelative(-1.5F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconRightToLeftPreview() {
Image(imageVector = EhIcons.Reader.RightToLeft, contentDescription = null)
}
@@ -0,0 +1,55 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
val EhIcons.Reader.ScreenRotation by unsafeLazy {
materialIcon(name = "ScreenRotation") {
materialPath {
moveTo(16.48F, 2.52F)
curveToRelative(3.27F, 1.55F, 5.61F, 4.72F, 5.97F, 8.48F)
horizontalLineToRelative(1.5F)
curveTo(23.44F, 4.84F, 18.29F, 0.0F, 12.0F, 0.0F)
lineToRelative(-0.66F, 0.03F)
lineToRelative(3.81F, 3.81F)
lineToRelative(1.33F, -1.32F)
close()
moveTo(10.23F, 1.75F)
curveToRelative(-0.59F, -0.59F, -1.54F, -0.59F, -2.12F, 0.0F)
lineTo(1.75F, 8.11F)
curveToRelative(-0.59F, 0.59F, -0.59F, 1.54F, 0.0F, 2.12F)
lineToRelative(12.02F, 12.02F)
curveToRelative(0.59F, 0.59F, 1.54F, 0.59F, 2.12F, 0.0F)
lineToRelative(6.36F, -6.36F)
curveToRelative(0.59F, -0.59F, 0.59F, -1.54F, 0.0F, -2.12F)
lineTo(10.23F, 1.75F)
close()
moveTo(14.83F, 21.19F)
lineTo(2.81F, 9.17F)
lineToRelative(6.36F, -6.36F)
lineToRelative(12.02F, 12.02F)
lineToRelative(-6.36F, 6.36F)
close()
moveTo(7.52F, 21.48F)
curveTo(4.25F, 19.94F, 1.91F, 16.76F, 1.55F, 13.0F)
lineTo(0.05F, 13.0F)
curveTo(0.56F, 19.16F, 5.71F, 24.0F, 12.0F, 24.0F)
lineToRelative(0.66F, -0.03F)
lineToRelative(-3.81F, -3.81F)
lineToRelative(-1.33F, 1.32F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconScreenRotationPreview() {
Image(imageVector = EhIcons.Reader.ScreenRotation, contentDescription = null)
}
@@ -0,0 +1,58 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@Suppress("BooleanLiteralArgument")
val EhIcons.Reader.Vertical by unsafeLazy {
materialIcon(name = "Vertical") {
materialPath {
moveTo(17.0F, 1.0F)
lineTo(7.0F, 1.0F)
arcTo(2.0059F, 2.0059F, 0.0F, false, false, 5.0F, 3.0F)
lineTo(5.0F, 21.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, 2.0F)
lineTo(17.0F, 23.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, -2.0F)
lineTo(19.0F, 3.0F)
arcTo(2.0059F, 2.0059F, 0.0F, false, false, 17.0F, 1.0F)
close()
moveTo(17.0F, 3.0F)
verticalLineToRelative(1.0462F)
lineTo(7.0F, 4.0462F)
lineTo(7.0F, 3.0F)
close()
moveTo(17.0F, 6.0462F)
verticalLineToRelative(12.0F)
lineTo(7.0F, 18.0462F)
verticalLineToRelative(-12.0F)
close()
moveTo(7.0F, 21.0F)
verticalLineToRelative(-0.9538F)
lineTo(17.0F, 20.0462F)
lineTo(17.0F, 21.0F)
close()
moveTo(9.0F, 13.0F)
lineToRelative(3.0F, 3.0F)
lineToRelative(3.0F, -3.0F)
lineToRelative(-2.0F, 0.0F)
lineToRelative(0.0F, -5.0F)
lineToRelative(-2.0F, 0.0F)
lineToRelative(0.0F, 5.0F)
lineToRelative(-2.0F, 0.0F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconVerticalPreview() {
Image(imageVector = EhIcons.Reader.Vertical, contentDescription = null)
}
@@ -0,0 +1,48 @@
package com.hippo.ehviewer.icons.reader
import androidx.compose.foundation.Image
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.util.unsafeLazy
@Suppress("BooleanLiteralArgument")
val EhIcons.Reader.Webtoon by unsafeLazy {
materialIcon(name = "Webtoon") {
materialPath {
moveTo(11.992F, 19.0F)
lineToRelative(3.0F, -3.0F)
lineToRelative(-2.0F, 0.0F)
lineToRelative(0.0F, -11.0F)
lineToRelative(-2.0F, 0.0F)
lineToRelative(0.0F, 11.0F)
lineToRelative(-2.0F, 0.0F)
lineToRelative(3.0F, 3.0F)
close()
moveTo(17.0F, 1.0F)
lineTo(7.0F, 1.0F)
arcTo(2.0059F, 2.0059F, 0.0F, false, false, 5.0F, 3.0F)
lineTo(5.0F, 21.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, 2.0F)
lineTo(17.0F, 23.0F)
arcToRelative(2.0059F, 2.0059F, 0.0F, false, false, 2.0F, -2.0F)
lineTo(19.0F, 3.0F)
arcTo(2.0059F, 2.0059F, 0.0F, false, false, 17.0F, 1.0F)
close()
moveTo(17.0F, 21.0F)
lineTo(7.0F, 21.0F)
lineTo(7.0F, 3.0F)
lineTo(17.0F, 3.0F)
close()
}
}
}
@Preview
@Composable
@Suppress("UnusedPrivateMember")
private fun IconWebtoonPreview() {
Image(imageVector = EhIcons.Reader.Webtoon, contentDescription = null)
}
@@ -14,66 +14,37 @@ import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
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.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.ui.LockDrawer
import com.hippo.ehviewer.ui.StartDestination
import com.hippo.ehviewer.ui.destinations.WebViewSignInScreenDestination
import com.hippo.ehviewer.ui.openBrowser
import com.hippo.ehviewer.ui.screen.popNavigate
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.LocalWindowSizeClass
import com.hippo.ehviewer.util.displayString
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootNavGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.Job
@RootNavGraph(start = true)
@Destination
@@ -81,100 +52,7 @@ import kotlinx.coroutines.Job
fun SignInScreen(navigator: DestinationsNavigator) {
LockDrawer(true)
val windowSizeClass = LocalWindowSizeClass.current
val coroutineScope = rememberCoroutineScope()
val focusManager = LocalFocusManager.current
var isProgressIndicatorVisible by rememberSaveable { mutableStateOf(false) }
var showUsernameError by rememberSaveable { mutableStateOf(false) }
var showPasswordError by rememberSaveable { mutableStateOf(false) }
var username by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
var passwordHidden by rememberSaveable { mutableStateOf(true) }
val context = LocalContext.current
var signInJob by remember { mutableStateOf<Job?>(null) }
val dialogState = LocalDialogState.current
fun signIn() {
if (signInJob?.isActive == true) return
if (username.isEmpty()) {
showUsernameError = true
return
} else {
showUsernameError = false
}
if (password.isEmpty()) {
showPasswordError = true
return
} else {
showPasswordError = false
}
focusManager.clearFocus()
isProgressIndicatorVisible = true
EhUtils.signOut()
signInJob = coroutineScope.launchIO {
runCatching {
EhEngine.signIn(username, password)
}.onFailure {
withUIContext {
focusManager.clearFocus()
dialogState.awaitPermissionOrCancel(
confirmText = R.string.get_it,
title = R.string.sign_in_failed,
showCancelButton = false,
text = {
Text(
"""
${it.displayString()}
${stringResource(R.string.sign_in_failed_tip)}
""".trimIndent(),
)
},
)
isProgressIndicatorVisible = false
}
}.onSuccess {
withNonCancellableContext { postLogin() }
withUIContext { navigator.popNavigate(StartDestination) }
}
}
}
@Composable
fun UsernameAndPasswordTextField() {
OutlinedTextField(
value = username,
onValueChange = { username = it },
modifier = Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
label = { Text(stringResource(R.string.username)) },
supportingText = { if (showUsernameError) Text(stringResource(R.string.error_username_cannot_empty)) },
trailingIcon = { if (showUsernameError) Icon(imageVector = Icons.Filled.Info, contentDescription = null) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
singleLine = true,
isError = showUsernameError,
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
label = { Text(stringResource(R.string.password)) },
visualTransformation = if (passwordHidden) PasswordVisualTransformation() else VisualTransformation.None,
supportingText = { if (showPasswordError) Text(stringResource(R.string.error_password_cannot_empty)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
keyboardActions = KeyboardActions(onDone = { signIn() }),
trailingIcon = {
if (showPasswordError) {
Icon(imageVector = Icons.Filled.Info, contentDescription = null)
} else {
IconButton(onClick = { passwordHidden = !passwordHidden }) {
val visibilityIcon = if (passwordHidden) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
Icon(imageVector = visibilityIcon, contentDescription = null)
}
}
},
singleLine = true,
isError = showPasswordError,
)
}
Box(contentAlignment = Alignment.Center) {
when (windowSizeClass.widthSizeClass) {
@@ -188,7 +66,6 @@ fun SignInScreen(navigator: DestinationsNavigator) {
contentDescription = null,
modifier = Modifier.padding(dimensionResource(id = 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),
@@ -199,7 +76,7 @@ fun SignInScreen(navigator: DestinationsNavigator) {
modifier = Modifier.widthIn(max = dimensionResource(id = R.dimen.single_max_width)).padding(top = 12.dp),
style = MaterialTheme.typography.headlineMedium,
)
Spacer(modifier = Modifier.weight(1f))
Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.keyline_margin)))
Row(modifier = Modifier.padding(top = dimensionResource(R.dimen.keyline_margin))) {
FilledTonalButton(
onClick = { context.openBrowser(EhUrl.URL_REGISTER) },
@@ -208,44 +85,28 @@ fun SignInScreen(navigator: DestinationsNavigator) {
Text(text = stringResource(id = R.string.register))
}
Button(
onClick = ::signIn,
onClick = { navigator.navigate(WebViewSignInScreenDestination) },
modifier = Modifier.weight(1f).padding(horizontal = 4.dp),
) {
Text(text = stringResource(id = R.string.sign_in))
}
}
Row(modifier = Modifier.padding(horizontal = 4.dp)) {
TextButton(
onClick = { navigator.navigate(WebViewSignInScreenDestination) },
modifier = Modifier.padding(horizontal = 8.dp),
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
) {
append(stringResource(id = R.string.sign_in_via_webview))
}
},
)
}
TextButton(
onClick = {
Settings.needSignIn = false
Settings.gallerySite = EhUrl.SITE_E
navigator.popNavigate(StartDestination)
TextButton(
onClick = {
Settings.needSignIn = false
Settings.gallerySite = EhUrl.SITE_E
navigator.popNavigate(StartDestination)
},
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
) {
append(stringResource(id = R.string.guest_mode))
}
},
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
) {
append(stringResource(id = R.string.guest_mode))
}
},
)
}
)
}
}
}
@@ -279,11 +140,10 @@ fun SignInScreen(navigator: DestinationsNavigator) {
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
UsernameAndPasswordTextField()
Spacer(modifier = Modifier.height(8.dp))
Spacer(modifier = Modifier.height(48.dp))
Row(horizontalArrangement = Arrangement.Center) {
Button(
onClick = ::signIn,
onClick = { navigator.navigate(WebViewSignInScreenDestination) },
modifier = Modifier.padding(horizontal = 4.dp).width(128.dp),
) {
Text(text = stringResource(id = R.string.sign_in))
@@ -296,46 +156,27 @@ fun SignInScreen(navigator: DestinationsNavigator) {
}
}
Spacer(modifier = Modifier.height(8.dp))
Row(horizontalArrangement = Arrangement.Center) {
TextButton(
onClick = { navigator.navigate(WebViewSignInScreenDestination) },
modifier = Modifier.padding(horizontal = 4.dp),
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
) {
append(stringResource(id = R.string.sign_in_via_webview))
}
},
)
}
TextButton(
onClick = {
Settings.needSignIn = false
Settings.gallerySite = EhUrl.SITE_E
navigator.popNavigate(StartDestination)
TextButton(
onClick = {
Settings.needSignIn = false
Settings.gallerySite = EhUrl.SITE_E
navigator.popNavigate(StartDestination)
},
modifier = Modifier.padding(horizontal = 4.dp),
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
) {
append(stringResource(id = R.string.guest_mode))
}
},
modifier = Modifier.padding(horizontal = 4.dp),
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline),
) {
append(stringResource(id = R.string.guest_mode))
}
},
)
}
)
}
}
}
}
}
if (isProgressIndicatorVisible) {
CircularProgressIndicator()
}
}
}
@@ -564,12 +564,15 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
withUIContext { navToReader(list.random().galleryInfo) }
}
onClick(Icons.AutoMirrored.Default.Sort) {
val selected = showSingleChoice(
val oldMode = SortMode.from(sortMode)
val (selected, checked) = showSelectItemWithCheckBox(
sortModes.toList(),
SortMode.All.indexOfFirst { it.flag == sortMode },
R.string.sort_by,
R.string.group_by_download_label,
SortMode.All.indexOfFirst { it.field == oldMode.field && it.order == oldMode.order },
oldMode.groupByDownloadLabel,
)
val mode = SortMode.All[selected]
val mode = SortMode.All[selected].copy(groupByDownloadLabel = checked)
DownloadManager.sortDownloads(mode)
sortMode = mode.flag
invalidateKey = !invalidateKey
@@ -6,9 +6,7 @@ import android.net.Uri
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -17,9 +15,6 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
@@ -30,7 +25,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
@@ -48,7 +42,6 @@ import com.hippo.ehviewer.ui.tools.observed
import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.Crash
import com.hippo.ehviewer.util.ReadableTime
import com.hippo.ehviewer.util.isCronetAvailable
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import eu.kanade.tachiyomi.util.system.logcat
@@ -144,38 +137,6 @@ fun AdvancedScreen(navigator: DestinationsNavigator) {
entryValueRes = R.array.app_language_entry_values,
value = Settings::language,
)
var enableCronet by Settings::enableCronet.observed
Preference(
title = stringResource(id = R.string.settings_advanced_http_engine),
summary = if (enableCronet) "Cronet" else "OkHttp",
) {
coroutineScope.launch {
dialogState.awaitPermissionOrCancel(
title = R.string.settings_advanced_http_engine,
showCancelButton = false,
) {
Box(modifier = Modifier.fillMaxWidth()) {
SingleChoiceSegmentedButtonRow(modifier = Modifier.align(Alignment.Center)) {
SegmentedButton(
selected = !enableCronet,
onClick = { enableCronet = false },
shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2),
) {
Text("OkHttp")
}
SegmentedButton(
selected = enableCronet,
onClick = { enableCronet = true },
shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2),
enabled = isCronetAvailable,
) {
Text("Cronet")
}
}
}
}
}
}
SwitchPreference(
title = stringResource(id = R.string.preload_thumb_aggressively),
value = Settings::preloadThumbAggressively,
@@ -22,6 +22,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.AlertDialog
@@ -487,8 +488,12 @@ class DialogState {
items: List<String>,
@StringRes title: Int,
@StringRes checkBoxText: Int,
selected: Int = -1,
initialChecked: Boolean = false,
): Pair<Int, Boolean> = showNoButton {
var checked by remember { mutableStateOf(false) }
var checked by remember { mutableStateOf(initialChecked) }
val textStyle = MaterialTheme.typography.titleMedium
val selectedColor = MaterialTheme.colorScheme.tertiary
Column(modifier = Modifier.padding(vertical = 8.dp)) {
Text(
text = stringResource(id = title),
@@ -497,14 +502,20 @@ class DialogState {
)
LazyColumn {
itemsIndexed(items) { index, text ->
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.tertiary) {
Text(
text = text,
modifier = Modifier.clickable { dismissWith(index to checked) }.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 16.dp),
style = MaterialTheme.typography.titleMedium,
)
}
ListItem(
headlineContent = {
Text(
text = text,
style = if (index == selected) textStyle.copy(color = selectedColor) else textStyle,
)
},
modifier = Modifier.clickable { dismissWith(index to checked) }.fillMaxWidth(),
trailingContent = {
if (index == selected) {
Icon(imageVector = Icons.Default.Check, contentDescription = null, tint = selectedColor)
}
},
)
}
}
LabeledCheckbox(
@@ -54,7 +54,7 @@ object AppUpdater {
return Release(shortSha, changelog, archiveUrl)
}
} else {
val curVersion = BuildConfig.VERSION_NAME.substringBefore('-')
val curVersion = BuildConfig.VERSION_NAME
val release = ghStatement(LATEST_RELEASE_URL).executeAndParseAs<GithubRelease>()
val latestVersion = release.version
val description = release.info
@@ -15,6 +15,7 @@
*/
package com.hippo.ehviewer.util
import android.os.Build
import android.os.Environment
import com.hippo.unifile.UniFile
import com.hippo.unifile.asUniFile
@@ -30,6 +31,10 @@ object AppConfig {
private const val PARSE_ERROR = "parse_error"
private const val CRASH = "crash"
val abi = Build.SUPPORTED_ABIS[0].takeIf {
it in setOf("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
} ?: "universal"
private val externalAppDir: File?
get() {
if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
@@ -1,28 +0,0 @@
package eu.kanade.tachiyomi.network.interceptor
import java.io.IOException
import okhttp3.Interceptor
import okhttp3.Response
/**
* Catches any uncaught exceptions from later in the chain and rethrows as a non-fatal
* IOException to avoid catastrophic failure.
*
* This should be the first interceptor in the client.
*
* See [Interceptor](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-interceptor/index.html)
*/
class UncaughtExceptionInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
return try {
chain.proceed(chain.request())
} catch (e: Exception) {
if (e is IOException) {
throw e
} else {
throw IOException(e)
}
}
}
}
@@ -25,7 +25,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
@@ -102,13 +101,13 @@ fun BottomReaderBar(onClickSettings: () -> Unit) {
) {
IconButton(onClick = { readingModeExpanded = true }) {
Icon(
painter = painterResource(readingMode.iconRes),
imageVector = readingMode.icon,
contentDescription = stringResource(R.string.viewer),
)
}
IconButton(onClick = { orientationModeExpanded = true }) {
Icon(
painter = painterResource(orientationMode.iconRes),
imageVector = orientationMode.icon,
contentDescription = stringResource(R.string.pref_rotation_type),
)
}
@@ -1,18 +1,24 @@
package eu.kanade.tachiyomi.ui.reader.setting
import android.content.pm.ActivityInfo
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.ui.graphics.vector.ImageVector
import com.hippo.ehviewer.R
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.reader.Landscape
import com.hippo.ehviewer.icons.reader.LandscapeLocked
import com.hippo.ehviewer.icons.reader.Portrait
import com.hippo.ehviewer.icons.reader.PortraitLocked
import com.hippo.ehviewer.icons.reader.ScreenRotation
enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int, val flagValue: Int) {
DEFAULT(0, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.label_default, R.drawable.ic_screen_rotation_24dp, 0x00000000),
FREE(1, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.rotation_free, R.drawable.ic_screen_rotation_24dp, 0x00000008),
PORTRAIT(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, R.string.rotation_portrait, R.drawable.ic_stay_current_portrait_24dp, 0x00000010),
LANDSCAPE(3, ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, R.string.rotation_landscape, R.drawable.ic_stay_current_landscape_24dp, 0x00000018),
LOCKED_PORTRAIT(4, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, R.string.rotation_force_portrait, R.drawable.ic_screen_lock_portrait_24dp, 0x00000020),
LOCKED_LANDSCAPE(5, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, R.string.rotation_force_landscape, R.drawable.ic_screen_lock_landscape_24dp, 0x00000028),
REVERSE_PORTRAIT(6, ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, R.string.rotation_reverse_portrait, R.drawable.ic_stay_current_portrait_24dp, 0x00000030),
enum class OrientationType(val prefValue: Int, val flag: Int, @StringRes val stringRes: Int, val icon: ImageVector, val flagValue: Int) {
DEFAULT(0, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.label_default, EhIcons.Reader.ScreenRotation, 0x00000000),
FREE(1, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED, R.string.rotation_free, EhIcons.Reader.ScreenRotation, 0x00000008),
PORTRAIT(2, ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT, R.string.rotation_portrait, EhIcons.Reader.Portrait, 0x00000010),
LANDSCAPE(3, ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE, R.string.rotation_landscape, EhIcons.Reader.Landscape, 0x00000018),
LOCKED_PORTRAIT(4, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT, R.string.rotation_force_portrait, EhIcons.Reader.PortraitLocked, 0x00000020),
LOCKED_LANDSCAPE(5, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE, R.string.rotation_force_landscape, EhIcons.Reader.LandscapeLocked, 0x00000028),
REVERSE_PORTRAIT(6, ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT, R.string.rotation_reverse_portrait, EhIcons.Reader.Portrait, 0x00000030),
;
companion object {
@@ -1,9 +1,16 @@
package eu.kanade.tachiyomi.ui.reader.setting
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.vector.ImageVector
import com.hippo.ehviewer.R
import com.hippo.ehviewer.icons.EhIcons
import com.hippo.ehviewer.icons.reader.ContinuousVertical
import com.hippo.ehviewer.icons.reader.Default
import com.hippo.ehviewer.icons.reader.LeftToRight
import com.hippo.ehviewer.icons.reader.RightToLeft
import com.hippo.ehviewer.icons.reader.Vertical
import com.hippo.ehviewer.icons.reader.Webtoon
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.L2RPagerViewer
@@ -12,13 +19,13 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
@Stable
enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int, val flagValue: Int) {
DEFAULT(0, R.string.label_default, R.drawable.ic_reader_default_24dp, 0x00000000),
LEFT_TO_RIGHT(1, R.string.left_to_right_viewer, R.drawable.ic_reader_ltr_24dp, 0x00000001),
RIGHT_TO_LEFT(2, R.string.right_to_left_viewer, R.drawable.ic_reader_rtl_24dp, 0x00000002),
VERTICAL(3, R.string.vertical_viewer, R.drawable.ic_reader_vertical_24dp, 0x00000003),
WEBTOON(4, R.string.webtoon_viewer, R.drawable.ic_reader_webtoon_24dp, 0x00000004),
CONTINUOUS_VERTICAL(5, R.string.vertical_plus_viewer, R.drawable.ic_reader_continuous_vertical_24dp, 0x00000005),
enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, val icon: ImageVector, val flagValue: Int) {
DEFAULT(0, R.string.label_default, EhIcons.Reader.Default, 0x00000000),
LEFT_TO_RIGHT(1, R.string.left_to_right_viewer, EhIcons.Reader.LeftToRight, 0x00000001),
RIGHT_TO_LEFT(2, R.string.right_to_left_viewer, EhIcons.Reader.RightToLeft, 0x00000002),
VERTICAL(3, R.string.vertical_viewer, EhIcons.Reader.Vertical, 0x00000003),
WEBTOON(4, R.string.webtoon_viewer, EhIcons.Reader.Webtoon, 0x00000004),
CONTINUOUS_VERTICAL(5, R.string.vertical_plus_viewer, EhIcons.Reader.ContinuousVertical, 0x00000005),
;
companion object {
@@ -1,17 +1,9 @@
package tachiyomi.data.release
import android.os.Build
import com.hippo.ehviewer.BuildConfig
import com.hippo.ehviewer.util.AppConfig
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
const val FLAVOR = "${BuildConfig.FLAVOR_api}-${BuildConfig.FLAVOR_oss}"
val apkVariant = "$FLAVOR-${
Build.SUPPORTED_ABIS[0].takeIf {
it in setOf("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
} ?: "universal"
}"
/**
* Contains information about the latest release from GitHub.
*/
@@ -23,7 +15,7 @@ data class GithubRelease(
@SerialName("assets") val assets: List<GitHubAssets>,
) {
fun getDownloadLink(): String {
val asset = assets.find { it.downloadLink.contains(apkVariant) } ?: assets[0]
val asset = assets.find { it.downloadLink.contains(AppConfig.abi) } ?: assets[0]
return asset.downloadLink
}
}
@@ -1,5 +1,6 @@
package tachiyomi.data.release
import com.hippo.ehviewer.util.AppConfig
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -20,7 +21,7 @@ data class GithubArtifacts(
@SerialName("artifacts") val artifacts: List<GithubArtifact>,
) {
fun getDownloadLink(): String {
return (artifacts.find { it.name.contains(apkVariant) } ?: artifacts[0]).downloadLink
return (artifacts.find { it.name.contains(AppConfig.abi) } ?: artifacts[0]).downloadLink
}
}
@@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M17.3159,1h-10a2.0059,2.0059 0,0 0,-2 2L5.3159,21a2.0059,2.0059 0,0 0,2 2h10a2.0059,2.0059 0,0 0,2 -2L19.3159,3A2.0059,2.0059 0,0 0,17.3159 1ZM17.3159,21h-10L7.3159,3h10Z" />
<path
android:fillColor="@android:color/black"
android:pathData="M11.3083,5h2v5.5h-2z" />
<path
android:fillColor="@android:color/black"
android:pathData="M15.308,16l-2,0l0,-4.5l-2,0l0,4.5l-2,0l3,3l3,-3z" />
</vector>
@@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M6.9906,18L6.9906,6h12L18.9906,3a2.0059,2.0059 0,0 0,-2 -2h-10a2.0059,2.0059 0,0 0,-2 2L4.9906,21a2.0059,2.0059 0,0 0,2 2h10a2.0059,2.0059 0,0 0,2 -2L18.9906,18h-12ZM6.9906,3h10L16.9906,4h-10ZM16.9906,21h-10L6.9906,20h10Z" />
<path
android:fillColor="@android:color/black"
android:pathData="M21.7706,12.44l-0.02,0.03a3.5546,3.5546 0,0 0,0.05 -0.47,3.9031 3.9031,0 0,0 -0.05,-0.48l0.02,0.03 0.85,-0.68a0.5068,0.5068 0,0 0,0.12 -0.64l-0.84,-1.46a0.4986,0.4986 0,0 0,-0.61 -0.22l-1.01,0.39 0.0099,0.04a3.6135,3.6135 0,0 0,-0.8 -0.48l-0.17,-1.07a0.4879,0.4879 0,0 0,-0.49 -0.43h-1.68a0.4876,0.4876 0,0 0,-0.49 0.42l-0.17,1.08a3.6149,3.6149 0,0 0,-0.8 0.48l0.02,-0.03 -1.01,-0.39a0.4878,0.4878 0,0 0,-0.61 0.22l-0.84,1.46a0.5068,0.5068 0,0 0,0.12 0.64l0.85,0.68 0.02,-0.03a3.9148,3.9148 0,0 0,-0.05 0.48,3.915 3.915,0 0,0 0.05,0.48l-0.02,-0.03 -0.85,0.68a0.5068,0.5068 0,0 0,-0.12 0.64l0.84,1.46a0.4985,0.4985 0,0 0,0.61 0.22l1.01,-0.39 -0.02,-0.03a4.4991,4.4991 0,0 0,0.8 0.46l0.17,1.08a0.4876,0.4876 0,0 0,0.49 0.42h1.68a0.4876,0.4876 0,0 0,0.49 -0.42l0.17,-1.08a3.6138,3.6138 0,0 0,0.8 -0.48l-0.02,0.03 1.02,0.39a0.4879,0.4879 0,0 0,0.61 -0.22l0.84,-1.46a0.5068,0.5068 0,0 0,-0.12 -0.64ZM17.9906,14a2,2 0,1 1,2 -2A2.0059,2.0059 0,0 1,17.9906 14Z" />
</vector>
@@ -1,21 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M17.3159,18h-10L7.3159,16h-2v5a2.0059,2.0059 0,0 0,2 2h10a2.0059,2.0059 0,0 0,2 -2L19.3159,16h-2ZM17.3159,21h-10L7.3159,20h10Z" />
<path
android:fillColor="@android:color/black"
android:pathData="M7.3159,6h10L17.3159,8h2L19.3159,3a2.0059,2.0059 0,0 0,-2 -2h-10a2.0059,2.0059 0,0 0,-2 2L5.3159,8h2ZM7.3159,3h10L17.3159,4h-10Z" />
<path
android:fillColor="@android:color/black"
android:pathData="M22.311,12l-3,-3l0,2l-11.99,0l0,2l11.99,0l0,2l3,-3z" />
<path
android:fillColor="@android:color/black"
android:pathData="M2.3206,11h1.5v2h-1.5z" />
<path
android:fillColor="@android:color/black"
android:pathData="M4.8206,11h1.5v2h-1.5z" />
</vector>
@@ -1,21 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M7,6H17V8h2V3a2.0059,2.0059 0,0 0,-2 -2H7A2.0059,2.0059 0,0 0,5 3V8H7ZM7,3H17V4H7Z" />
<path
android:fillColor="@android:color/black"
android:pathData="M17,18L7,18L7,16L5,16v5a2.0059,2.0059 0,0 0,2 2L17,23a2.0059,2.0059 0,0 0,2 -2L19,16L17,16ZM17,21L7,21L7,20L17,20Z" />
<path
android:fillColor="@android:color/black"
android:pathData="M5.005,13l11.99,0l0,-2l-11.99,0l0,-2l-3,3l3,3l0,-2z" />
<path
android:fillColor="@android:color/black"
android:pathData="M20.4953,11h1.5v2h-1.5z" />
<path
android:fillColor="@android:color/black"
android:pathData="M17.9953,11h1.5v2h-1.5z" />
</vector>
@@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M17,1L7,1A2.0059,2.0059 0,0 0,5 3L5,21a2.0059,2.0059 0,0 0,2 2L17,23a2.0059,2.0059 0,0 0,2 -2L19,3A2.0059,2.0059 0,0 0,17 1ZM17,3v1.0462L7,4.0462L7,3ZM17,6.0462v12L7,18.0462v-12ZM7,21v-0.9538L17,20.0462L17,21Z" />
<path
android:fillColor="@android:color/black"
android:pathData="M9,13l3,3l3,-3l-2,0l0,-5l-2,0l0,5l-2,0z" />
</vector>
@@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M11.992,19l3,-3l-2,0l0,-11l-2,0l0,11l-2,0l3,3z" />
<path
android:fillColor="@android:color/black"
android:pathData="M17,1L7,1A2.0059,2.0059 0,0 0,5 3L5,21a2.0059,2.0059 0,0 0,2 2L17,23a2.0059,2.0059 0,0 0,2 -2L19,3A2.0059,2.0059 0,0 0,17 1ZM17,21L7,21L7,3L17,3Z" />
</vector>
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M21,5L3,5c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,7c0,-1.1 -0.9,-2 -2,-2zM19,17L5,17L5,7h14v10zM10,16h4c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1v-1c0,-1.11 -0.9,-2 -2,-2 -1.11,0 -2,0.9 -2,2v1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1zM10.8,10c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2v1h-2.4v-1z" />
</vector>
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M10,16h4c0.55,0 1,-0.45 1,-1v-3c0,-0.55 -0.45,-1 -1,-1v-1c0,-1.11 -0.9,-2 -2,-2 -1.11,0 -2,0.9 -2,2v1c-0.55,0 -1,0.45 -1,1v3c0,0.55 0.45,1 1,1zM10.8,10c0,-0.66 0.54,-1.2 1.2,-1.2 0.66,0 1.2,0.54 1.2,1.2v1h-2.4v-1zM17,1L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-2 -2,-2zM17,19L7,19L7,5h10v14z" />
</vector>
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M16.48,2.52c3.27,1.55 5.61,4.72 5.97,8.48h1.5C23.44,4.84 18.29,0 12,0l-0.66,0.03 3.81,3.81 1.33,-1.32zM10.23,1.75c-0.59,-0.59 -1.54,-0.59 -2.12,0L1.75,8.11c-0.59,0.59 -0.59,1.54 0,2.12l12.02,12.02c0.59,0.59 1.54,0.59 2.12,0l6.36,-6.36c0.59,-0.59 0.59,-1.54 0,-2.12L10.23,1.75zM14.83,21.19L2.81,9.17l6.36,-6.36 12.02,12.02 -6.36,6.36zM7.52,21.48C4.25,19.94 1.91,16.76 1.55,13L0.05,13C0.56,19.16 5.71,24 12,24l0.66,-0.03 -3.81,-3.81 -1.33,1.32z" />
</vector>
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M1.01,7L1,17c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V7c0,-1.1 -0.9,-2 -2,-2H3c-1.1,0 -1.99,0.9 -1.99,2zM19,7v10H5V7h14z" />
</vector>
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19H7V5h10v14z" />
</vector>
-7
View File
@@ -23,11 +23,8 @@
<string name="history">Verlauf</string>
<string name="downloads">Downloads</string>
<string name="settings">Einstellungen</string>
<string name="username">Benutzername</string>
<string name="password">Kennwort</string>
<string name="sign_in">Anmelden</string>
<string name="register">Konto erstellen</string>
<string name="sign_in_via_webview">Mit WebView anmelden</string>
<string name="sign_in_first">Melde dich zunächst an</string>
<string name="text_is_empty">Der Text wird nicht eingegeben</string>
<string name="waring">Achtung</string>
@@ -49,7 +46,6 @@
<string name="error_get_ptoken_error">Fehler beim Erhalten pTokens</string>
<string name="error_cant_save_image">Das Bild konnte nicht gespeichert werden</string>
<string name="error_invalid_number">Nummer ungültig</string>
<string name="get_it">Verstanden</string>
<string name="gallery_list_search_bar_hint_exhentai">ExHentai durchsuchen</string>
<string name="gallery_list_search_bar_hint_e_hentai">E-Hentai durchsuchen</string>
<string name="gallery_list_empty_hit">Die Welt ist so groß und der Panda sitzt ganz allein</string>
@@ -294,10 +290,7 @@
<string name="settings_privacy_secure">Screenshot verhindern</string>
<string name="settings_privacy_secure_summary">Screenshots werden verhindert. Inhalt der App wird in Aktuelle Apps-Liste nicht angezeigt.</string>
<string name="app_waring">Inhalt dieser App kommt aus dem Internet, von dem einige dir körperlichen oder emotionalen Schaden zufügen kann. Du hast es wirklich erfahrt und möchtest immer noch fortsetzen.</string>
<string name="error_username_cannot_empty">Gibt den Nutzername ein</string>
<string name="error_password_cannot_empty">Gibt das Passwort ein</string>
<string name="guest_mode">Anmeldung überspringen</string>
<string name="sign_in_failed">Anmeldung fehlgeschlagen</string>
<string name="clipboard_gallery_url_snack_message">Ein Link von einen Galerie wurde in Zwischenablage erkennt.</string>
<string name="clipboard_gallery_url_snack_action">Gehen</string>
<string name="settings_advanced_read_cache_size">Größe des Lesezwischenspeichers</string>
-8
View File
@@ -26,12 +26,8 @@
<string name="downloads">Descargas</string>
<string name="settings">Configuración</string>
<string name="username">Usuario</string>
<string name="password">Contraseña</string>
<string name="sign_in">Iniciar sesión</string>
<string name="register">Registrarse</string>
<string name="sign_in_via_webview">Iniciar sesión vía web</string>
<string name="sign_in_first">Por favor, inicie sesión primero</string>
<string name="text_is_empty">El texto está vacío</string>
@@ -58,10 +54,7 @@
<string name="error_invalid_number">Número invalido</string>
<string name="app_waring">El contenido de esta aplicación es de Internet. Algunas de ellas pueden causarle daño psicológico. Ahora sabes los riesgos anteriormente mencionado y te gustaría asumirlos</string>
<string name="error_username_cannot_empty">El usuario no puede estar vacío</string>
<string name="guest_mode">Saltar inicio de sesión</string>
<string name="sign_in_failed">Inicio de sesión fallido</string>
<string name="get_it">Entendido</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">Buscar ExHentai</string>
@@ -385,7 +378,6 @@
<string name="delete">Eliminar</string>
<string name="download_state_failed">Descarga fallida</string>
<string name="download_state_none">Descarga en pausa</string>
<string name="error_password_cannot_empty">La contraseña no puede estar vacía</string>
<string name="error_something_wrong_happened">Ocurrió algo malo</string>
<string name="settings_privacy">Privacidad</string>
<string name="filter">Bloqueo de galerías</string>
-8
View File
@@ -26,12 +26,8 @@
<string name="downloads">Téléchargement</string>
<string name="settings">Paramétres</string>
<string name="username">Nom d\'utilisateur</string>
<string name="password">Mot de passe</string>
<string name="sign_in">Se connecter</string>
<string name="register">Créer un compte</string>
<string name="sign_in_via_webview">Se connecter via WebView</string>
<string name="sign_in_first">Connectez-vous pour continuer</string>
<string name="text_is_empty">Entrez le texte.</string>
@@ -58,11 +54,7 @@
<string name="error_invalid_number">Nombre invalide</string>
<string name="app_waring">Le contenu de l\'application provient de l\'Internet, dont quelque chose peut vous causer le dommage physique ou mentale. Vous l\'avez bien compris et voulez continuer quand même.</string>
<string name="error_username_cannot_empty">Nom d\'ultilisateur ne doit pas être une chaîne vide</string>
<string name="error_password_cannot_empty">Mot de passe ne doit pas être une chaîne vide</string>
<string name="guest_mode">Continuer sans se connecter</string>
<string name="sign_in_failed">Impossible de se connecter</string>
<string name="get_it">Compris</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">Chercher sur ExHentai</string>
-8
View File
@@ -24,12 +24,8 @@
<string name="downloads">ダウンロード</string>
<string name="settings">設定</string>
<string name="username">ユーザー名</string>
<string name="password">パスワード</string>
<string name="sign_in">ログイン</string>
<string name="register">登録</string>
<string name="sign_in_via_webview">Webでログイン</string>
<string name="sign_in_first">ログインしてください</string>
<string name="text_is_empty">テキストを入力してください</string>
@@ -56,11 +52,7 @@
<string name="error_invalid_number">無効な数字です</string>
<string name="app_waring">このアプリの内容はインターネットから取得したものです。その内容の一部は使用者のお体や心理に傷つける恐れがあります。それでも続行しますか?</string>
<string name="error_username_cannot_empty">ユーザー名を入力してください。</string>
<string name="error_password_cannot_empty">パスワードを入力してください。</string>
<string name="guest_mode">ログインをスキップ</string>
<string name="sign_in_failed">ログインに失敗しました。</string>
<string name="get_it">了解</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">ExHentaiを検索</string>
-8
View File
@@ -25,12 +25,8 @@
<string name="downloads">다운로드</string>
<string name="settings">설정</string>
<string name="username">사용자 이름</string>
<string name="password">비밀번호</string>
<string name="sign_in">로그인</string>
<string name="register">회원가입</string>
<string name="sign_in_via_webview">WebView를 통하여 로그인</string>
<string name="sign_in_first">먼저 로그인하세요.</string>
<string name="text_is_empty">텍스트가 비어 있습니다.</string>
@@ -60,11 +56,7 @@
<string name="error_invalid_number">잘못된 번호</string>
<string name="app_waring">이 앱의 내용은 인터넷에서 받아옵니다. 이 앱을 사용하기 위해서는 받아온 내용 중 일부가 사용자에게 가할 수 있는 신체적 또는 정신적 피해를 감수해야 합니다.</string>
<string name="error_username_cannot_empty">사용자 이름은 비워둘 수 없습니다.</string>
<string name="error_password_cannot_empty">비밀번호는 비워둘 수 없습니다.</string>
<string name="guest_mode">로그인 없이 사용</string>
<string name="sign_in_failed">로그인 실패</string>
<string name="get_it">알겠습니다</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">ExHentai 검색</string>
@@ -8,8 +8,6 @@
<string name="error_unknown_host">Ukjent vert</string>
<string name="error_redirection">For mange videresendinger</string>
<string name="error_socket">Nettverksfeil</string>
<string name="password">Passord</string>
<string name="sign_in_via_webview">Logg inn via WebView</string>
<string name="downloads">Nedlastninger</string>
<string name="register">Registrering</string>
<string name="error_invalid_number">Ugyldig nummer</string>
@@ -18,7 +16,6 @@
<string name="history">Historikk</string>
<string name="subscription">Abonnement</string>
<string name="settings">Innstillinger</string>
<string name="username">Brukernavn</string>
<string name="sign_in">Logg inn</string>
<string name="sign_in_first">Logg inn først</string>
<string name="download">Last ned</string>
@@ -53,7 +50,6 @@
<string name="torrents">Torrenter</string>
<string name="more_previews">Flere forhåndsvisninger</string>
<string name="refresh">Gjenoppfrisk</string>
<string name="get_it">Skjønner</string>
<string name="keyword_search">Nøkkelordssøk</string>
<string name="search_sft">Etiketter</string>
<string name="any">Ubegrenset</string>
-8
View File
@@ -25,12 +25,8 @@
<string name="downloads">ดาวน์โหลด</string>
<string name="settings">การตั้งค่า</string>
<string name="username">ชื่อผู้ใช้</string>
<string name="password">รหัสผ่าน</string>
<string name="sign_in">ลงชื่อเข้าใช้</string>
<string name="register">ลงทะเบียน</string>
<string name="sign_in_via_webview">ลงชื่อเข้าใช้ผ่านมุมมองเว็บไซต์</string>
<string name="sign_in_first">โปรดลงชื่อเข้าใช้ก่อนเป็นอันดับแรก</string>
<string name="text_is_empty">ข้อความห้ามเว้นว่างไว้</string>
@@ -60,11 +56,7 @@
<string name="error_invalid_number">จำนวนไม่ถูกต้อง</string>
<string name="app_waring">เนื้อหาภายในแอปพลิเคชั่นนี้มาจากอินเทอร์เน็ตทั้งหมด อาจมีเนื้อหาบางอย่างที่มีผลต่อร่างกายหรือจิตใจของคุณ คุณจะต้องยอมรับความเสี่ยงที่ดังกล่าว และดำเนินการต่อไป</string>
<string name="error_username_cannot_empty">ชื่อผู้ใช้ห้ามเว้นว่างไว้</string>
<string name="error_password_cannot_empty">รหัสผ่านห้ามเว้นว่างไว้</string>
<string name="guest_mode">ดำเนินการต่อโดยไม่ต้องลงชื่อเข้าระบบ</string>
<string name="sign_in_failed">การลงชื่อเข้าใช้ล้มเหลว</string>
<string name="get_it">เข้าใจแล้ว ไม่ต้องแสดงอีก</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">ค้นหาใน ExHentai</string>
-8
View File
@@ -8,11 +8,8 @@
<string name="history">Geçmiş</string>
<string name="downloads">İndirilenler</string>
<string name="settings">Ayarlar</string>
<string name="username">Kullanıcı adı</string>
<string name="password">Şifre</string>
<string name="sign_in">Giriş yap</string>
<string name="register">Kaydol</string>
<string name="sign_in_via_webview">WebView aracılığıyla giriş yap</string>
<string name="sign_in_first">Lütfen önce giriş yap</string>
<string name="waring">Uyarı</string>
<string name="invalid_download_location">İndirme konumu kullanılamıyor. Lütfen Ayarlardan ayarlayın.</string>
@@ -33,11 +30,7 @@
<string name="error_get_ptoken_error">pToken alınamadı</string>
<string name="error_cant_save_image">Görsel kaydedilemiyor</string>
<string name="error_invalid_number">Geçersiz numara</string>
<string name="error_username_cannot_empty">Kullanıcı adı boş bir metin olamaz</string>
<string name="error_password_cannot_empty">Şifre boş bir metin olamaz</string>
<string name="guest_mode">Giriş yapmaksızın devam et</string>
<string name="sign_in_failed">Giriş başarısız</string>
<string name="get_it">Anladım</string>
<string name="gallery_list_empty_hit">Dünya büyük olmasına rağmen pandacık yalnız oturuyor</string>
<string name="keyword_search">Anahtar Kelime Arama</string>
<string name="image_search">Görsel Arama</string>
@@ -68,7 +61,6 @@
<string name="error_unknown_host">Bilinmeyen host</string>
<string name="app_waring">Bu uygulamadaki içerikler internetten alınmaktadır. Bazı içerikler fiziksel veya zihinsel olarak size zararlı gelebilir.Yukarıda, riskleri öğrendiniz ve üstleneceksiniz.
\nKullanmaya devam ederek, yukarıdaki koşulları kabul edersiniz.</string>
<string name="sign_in_failed_tip">Bu sorun devam ederse, \"WebView aracılığıyla giriş yap\" seçeneğini veya \"Çerez aracılığıyla giriş yap\" seçeneğini deneyin.</string>
<string name="gallery_list_search_bar_open_gallery">Galeriyi aç</string>
<string name="gallery_list_search_bar_hint_e_hentai">E-Hentai\'de Arayın</string>
<string name="gallery_list_empty_hit_subscription">Etiketlere Ayarlar-&gt;EH-&gt;Etiketlerim yerinden abone olun</string>
+1 -11
View File
@@ -22,11 +22,8 @@
<string name="history">历史</string>
<string name="downloads">下载</string>
<string name="settings">设置</string>
<string name="username">用户名</string>
<string name="password">密码</string>
<string name="sign_in">登录</string>
<string name="register">注册</string>
<string name="sign_in_via_webview">通过网页登录</string>
<string name="sign_in_first">请先登录</string>
<string name="text_is_empty">文本为空</string>
<string name="waring">警告</string>
@@ -52,12 +49,7 @@
<string name="error_invalid_number">非法数字</string>
<string name="app_waring">本应用中内容来自互联网,部分内容可能对您的生理及心理造成难以恢复的伤害。本应用作者不会对由本应用造成的任何后果负责。未成年人应在监护人指导下使用本应用。请遵守所在地的法律法规。</string>
<string name="app_waring_2">继续使用即代表您同意上述条款。</string>
<string name="error_username_cannot_empty">用户名不可为空</string>
<string name="error_password_cannot_empty">密码不可为空</string>
<string name="guest_mode">游客模式</string>
<string name="sign_in_failed">登录失败</string>
<string name="sign_in_failed_tip">若持续出错,请尝试“通过网页登录”或“通过 Cookie 登录”。</string>
<string name="get_it">知道了</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">搜索 ExHentai</string>
<string name="gallery_list_search_bar_hint_e_hentai">搜索 E-Hentai</string>
@@ -356,7 +348,6 @@
<string name="settings_advanced_dump_logcat_to">已保存日志至 %s</string>
<string name="settings_advanced_read_cache_size">阅读缓存大小</string>
<string name="settings_advanced_app_language_title">App 界面语言</string>
<string name="settings_advanced_http_engine">Http 引擎(重启生效)</string>
<string name="settings_advanced_touch_slop">[实验性] 触摸溢出系数</string>
<string name="settings_advanced_touch_slop_summary">增大此项将降低滑动手势敏感度</string>
<string name="settings_advanced_export_data">导出数据</string>
@@ -463,10 +454,9 @@
<string name="uploaded_time_asc">上传时间(升序)</string>
<string name="title_asc">标题(升序)</string>
<string name="title_desc">标题(降序)</string>
<string name="download_label_asc">下载标签(升序)</string>
<string name="download_label_desc">下载标签(降序)</string>
<string name="page_count_asc">页数(升序)</string>
<string name="page_count_desc">页数(降序)</string>
<string name="group_by_download_label">按下载标签分组</string>
<string name="download_filter">过滤</string>
<string name="download_all">全部</string>
<string name="download_start_all_reversed">全部开始(倒序)</string>
@@ -26,12 +26,8 @@
<string name="downloads">下載</string>
<string name="settings">設置</string>
<string name="username">用户名</string>
<string name="password">密碼</string>
<string name="sign_in">登錄</string>
<string name="register">註冊</string>
<string name="sign_in_via_webview">通過網頁登錄</string>
<string name="sign_in_first">請先登錄</string>
<string name="text_is_empty">文本為空</string>
@@ -61,12 +57,7 @@
<string name="error_invalid_number">非法數字</string>
<string name="app_waring">本應用中內容來自互聯網,部分內容可能對您的生理及心理造成難以恢復的傷害。本應用作者不會對由本應用造成的任何後果負責。\n\n未成年人應在監護人指導下使用本應用。</string>
<string name="error_username_cannot_empty">用户名不可為空</string>
<string name="error_password_cannot_empty">密碼不可為空</string>
<string name="guest_mode">跳過登錄</string>
<string name="sign_in_failed">登錄失敗</string>
<string name="sign_in_failed_tip">若持續出錯,請嘗試“通過網頁登錄”或“通過 Cookie 登錄”。</string>
<string name="get_it">知道了</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">搜索 ExHentai</string>
+1 -11
View File
@@ -22,11 +22,8 @@
<string name="history">歷史</string>
<string name="downloads">下載</string>
<string name="settings">設定</string>
<string name="username">使用者名稱</string>
<string name="password">密碼</string>
<string name="sign_in">登入</string>
<string name="register">註冊</string>
<string name="sign_in_via_webview">透過網頁登入</string>
<string name="sign_in_first">請先登入</string>
<string name="text_is_empty">文字是空的</string>
<string name="waring">警告</string>
@@ -52,12 +49,7 @@
<string name="error_invalid_number">無效的數字</string>
<string name="app_waring">本應用程式中內容來自網際網路,部分內容可能對您的生理及心理造成難以恢復的傷害。本應用程式的作者不會對由其所造成的任何影響負責。\n\n未成年人應在監護人指導下使用本應用程式。\n\n繼續使用即代表您同意上述條款。</string>
<string name="app_waring_2">繼續使用即表示您同意上述條款。</string>
<string name="error_username_cannot_empty">使用者名稱欄位不能為空</string>
<string name="error_password_cannot_empty">密碼欄位不能為空</string>
<string name="guest_mode">略過登入</string>
<string name="sign_in_failed">登入失敗</string>
<string name="sign_in_failed_tip">若持續出錯,請嘗試“通過網頁登入”或“通過 Cookie 登入”。</string>
<string name="get_it">知道了</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">在 ExHentai 搜尋</string>
<string name="gallery_list_search_bar_hint_e_hentai">在 E-Hentai 搜尋</string>
@@ -355,7 +347,6 @@
<string name="settings_advanced_dump_logcat_to">已將紀錄檔儲存至 %s</string>
<string name="settings_advanced_read_cache_size">閱讀快取大小</string>
<string name="settings_advanced_app_language_title">應用程式介面語言</string>
<string name="settings_advanced_http_engine">Http 引擎 (需重新啟動)</string>
<string name="settings_advanced_touch_slop">[測試] 觸控溢出係數</string>
<string name="settings_advanced_touch_slop_summary">提高係數將降低滑動手勢敏感度</string>
<string name="settings_advanced_export_data">匯出檔案</string>
@@ -462,10 +453,9 @@
<string name="uploaded_time_asc">上傳時間 (遞增)</string>
<string name="title_asc">標題 (遞減)</string>
<string name="title_desc">標題 (遞增)</string>
<string name="download_label_asc">下載標籤 (遞減)</string>
<string name="download_label_desc">下載標籤 (遞增)</string>
<string name="page_count_asc">頁數 (遞減)</string>
<string name="page_count_desc">頁數 (遞增)</string>
<string name="group_by_download_label">按下載標籤分組</string>
<string name="download_filter">過濾</string>
<string name="download_all">全部</string>
<string name="download_start_all_reversed">全部開始(倒序)</string>
-2
View File
@@ -235,8 +235,6 @@
<item>@string/uploaded_time_asc</item>
<item>@string/title_asc</item>
<item>@string/title_desc</item>
<item>@string/download_label_asc</item>
<item>@string/download_label_desc</item>
<item>@string/page_count_asc</item>
<item>@string/page_count_desc</item>
</string-array>
+1 -11
View File
@@ -36,11 +36,8 @@
<string name="history">History</string>
<string name="downloads">Downloads</string>
<string name="settings">Settings</string>
<string name="username">Username</string>
<string name="password">Password</string>
<string name="sign_in">Sign in</string>
<string name="register">Register</string>
<string name="sign_in_via_webview">Sign in via WebView</string>
<string name="sign_in_first">Please sign in first</string>
<string name="text_is_empty">Text is empty</string>
<string name="waring">Warning</string>
@@ -90,12 +87,7 @@
<string name="error_invalid_number">Invalid number</string>
<string name="app_waring">The content of this application is from the Internet. Some of it may do physical or mental harm to you. You have learnt the risks above and would like to undertake them. By continuing to use it, you agree to the above terms.</string>
<string name="app_waring_2">By continuing to use it, you agree to the above terms.</string>
<string name="error_username_cannot_empty">Username cannot be an empty string</string>
<string name="error_password_cannot_empty">Password cannot be an empty string</string>
<string name="guest_mode">Continue without signing in</string>
<string name="sign_in_failed">Sign in failed</string>
<string name="sign_in_failed_tip">If this issue continues, try \"Sign in via WebView\" or \"Sign in via cookies\".</string>
<string name="get_it">Got it</string>
<!-- Gallery List -->
<string name="gallery_list_search_bar_hint_exhentai">Search ExHentai</string>
<string name="gallery_list_search_bar_hint_e_hentai">Search E-Hentai</string>
@@ -400,7 +392,6 @@
<string name="settings_advanced_dump_logcat_to">Logcat dumped to %s</string>
<string name="settings_advanced_read_cache_size">Read cache size</string>
<string name="settings_advanced_app_language_title">App language</string>
<string name="settings_advanced_http_engine">Http Engine (Restart required)</string>
<string name="settings_advanced_touch_slop">[Experimental] Touch slop factor</string>
<string name="settings_advanced_touch_slop_summary">Increasing this will reduce swipe gesture sensitivity</string>
<string name="settings_advanced_export_data">Export data</string>
@@ -528,10 +519,9 @@
<string name="uploaded_time_asc">Uploaded time (ascending)</string>
<string name="title_asc">Title (ascending)</string>
<string name="title_desc">Title (descending)</string>
<string name="download_label_asc">Download label (ascending)</string>
<string name="download_label_desc">Download label (descending)</string>
<string name="page_count_asc">Page count (ascending)</string>
<string name="page_count_desc">Page count (descending)</string>
<string name="group_by_download_label">Group by download label</string>
<string name="download_filter">Filter</string>
<string name="download_all">All</string>
<string name="download_start_all_reversed">Start all (reversed)</string>
@@ -1,22 +0,0 @@
@file:Suppress("NewApi")
package com.hippo.ehviewer.cronet
import android.net.http.HttpEngine
import com.hippo.ehviewer.Settings
import okio.Path.Companion.toOkioPath
import splitties.init.appCtx
val cronetHttpClient: HttpEngine = HttpEngine.Builder(appCtx).apply {
setEnableBrotli(true)
setUserAgent(Settings.userAgent)
// Cache Quic hint only since the real cache mechanism should on Ktor layer
val cache = (appCtx.cacheDir.toOkioPath() / "http_cache").toFile().apply { mkdirs() }
setStoragePath(cache.absolutePath)
setEnableHttpCache(HttpEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 4096)
addQuicHint("e-hentai.org", 443, 443)
addQuicHint("forums.e-hentai.org", 443, 443)
addQuicHint("exhentai.org", 443, 443)
}.build()
@@ -1,182 +0,0 @@
@file:Suppress("NewApi")
package com.hippo.ehviewer.ktor
import android.net.http.HttpException
import android.net.http.UploadDataProvider
import android.net.http.UploadDataSink
import android.net.http.UrlRequest
import android.net.http.UrlResponseInfo
import com.hippo.ehviewer.cronet.cronetDispatcher
import com.hippo.ehviewer.cronet.cronetHttpClient
import com.hippo.ehviewer.cronet.cronetHttpClientExecutor
import com.hippo.ehviewer.cronet.pool
import io.ktor.client.engine.HttpClientEngineBase
import io.ktor.client.engine.HttpClientEngineConfig
import io.ktor.client.engine.callContext
import io.ktor.client.request.HttpRequestData
import io.ktor.client.request.HttpResponseData
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpProtocolVersion
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.OutgoingContent
import io.ktor.http.headers
import io.ktor.util.date.GMTDate
import io.ktor.util.flattenForEach
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.InternalAPI
import io.ktor.utils.io.pool.useInstance
import io.ktor.utils.io.writer
import java.nio.ByteBuffer
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
object CronetEngine : HttpClientEngineBase("Cronet") {
override val config = HttpClientEngineConfig()
override val dispatcher = cronetDispatcher
@InternalAPI
override suspend fun execute(data: HttpRequestData): HttpResponseData {
val callContext = callContext()
return executeHttpRequest(callContext, data)
}
@OptIn(DelicateCoroutinesApi::class)
private suspend fun executeHttpRequest(
callContext: CoroutineContext,
data: HttpRequestData,
): HttpResponseData = suspendCancellableCoroutine { continuation ->
val requestTime = GMTDate()
val callback = object : UrlRequest.Callback {
lateinit var chunkChan: Channel<ByteBuffer>
override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newLocationUrl: String) {
continuation.resume(
info.toHttpResponseData(
requestTime = requestTime,
callContext = callContext,
),
)
}
override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
val channel = GlobalScope.writer(callContext) {
pool.useInstance {
chunkChan = Channel(1)
request.read(it)
chunkChan.consumeEach { buffer ->
buffer.flip()
channel.writeFully(buffer)
buffer.clear()
request.read(buffer)
}
}
}.channel
continuation.resume(
info.toHttpResponseData(
requestTime = requestTime,
callContext = callContext,
responseBody = channel,
),
)
}
override fun onReadCompleted(request: UrlRequest, info: UrlResponseInfo, byteBuffer: ByteBuffer) {
chunkChan.trySend(byteBuffer).getOrThrow()
}
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
chunkChan.close()
}
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: HttpException) {
if (::chunkChan.isInitialized) {
chunkChan.close(error)
} else {
continuation.resumeWithException(error)
}
}
override fun onCanceled(p0: UrlRequest, p1: UrlResponseInfo?) {
// No-op
}
}
val request = cronetHttpClient.newUrlRequestBuilder(data.url.toString(), cronetHttpClientExecutor, callback).apply {
setHttpMethod(data.method.value)
data.headers.flattenForEach { key, value -> addHeader(key, value) }
data.body.contentType?.let { addHeader(HttpHeaders.ContentType, it.toString()) }
data.body.contentLength?.let { addHeader(HttpHeaders.ContentLength, it.toString()) }
data.body.toUploadDataProvider(callContext)?.let { setUploadDataProvider(it, cronetHttpClientExecutor) }
}.build()
request.start()
callContext[Job]!!.invokeOnCompletion { request.cancel() }
continuation.invokeOnCancellation { request.cancel() }
}
}
private fun UrlResponseInfo.toHttpResponseData(
requestTime: GMTDate,
callContext: CoroutineContext,
responseBody: ByteReadChannel = ByteReadChannel.Empty,
): HttpResponseData {
return HttpResponseData(
statusCode = HttpStatusCode.fromValue(httpStatusCode),
requestTime = requestTime,
headers = headers {
headers.asMap.forEach { (key, value) ->
appendAll(key, value)
}
},
version = when (negotiatedProtocol) {
"h2" -> HttpProtocolVersion.HTTP_2_0
"h3" -> HttpProtocolVersion.QUIC
"quic/1+spdy/3" -> HttpProtocolVersion.SPDY_3
else -> HttpProtocolVersion.HTTP_1_1
},
body = responseBody,
callContext = callContext,
)
}
@OptIn(DelicateCoroutinesApi::class)
private fun OutgoingContent.toUploadDataProvider(callContext: CoroutineContext): UploadDataProvider? = when (this) {
is OutgoingContent.NoContent -> null
is OutgoingContent.ByteArrayContent -> object : UploadDataProvider() {
val buffer = ByteBuffer.wrap(bytes()).slice()
override fun getLength() = buffer.limit().toLong()
override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) {
byteBuffer.put(buffer)
uploadDataSink.onReadSucceeded(false)
}
override fun rewind(uploadDataSink: UploadDataSink) {
buffer.position(0)
uploadDataSink.onRewindSucceeded()
}
}
is OutgoingContent.WriteChannelContent -> object : UploadDataProvider() {
fun launch() = GlobalScope.writer(callContext) { writeTo(channel) }.channel
var chan = launch()
override fun getLength() = contentLength!!
override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) {
GlobalScope.launch(callContext) {
chan.readAvailable(byteBuffer)
uploadDataSink.onReadSucceeded(false)
}
}
override fun rewind(uploadDataSink: UploadDataSink) {
chan = launch()
uploadDataSink.onRewindSucceeded()
}
}
else -> error("UnsupportedContentType $this")
}
@@ -1,4 +0,0 @@
package com.hippo.ehviewer.util
val isCronetAvailable: Boolean
get() = isAtLeastSExtension7
-11
View File
@@ -65,17 +65,6 @@
# 下载
| 变种 | 功能 |
|-------------|------------------------|
| Default | Android 9+, 完全支持 |
| Marshmallow | Android 6.0-8.1, 有限支持* |
| OSS | FOSS 依赖项** |
| GMS | 专有 GMS 组件 |
\* 可能不支持某些压缩包
** 在不支持 [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine) 的设备上回退到 OkHttp
<a href="https://github.com/FooIbar/EhViewer/releases">
<img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/>
</a>
-11
View File
@@ -65,17 +65,6 @@
# 下載
| 變種 | 功能 |
|-------------|------------------------|
| Default | Android 9+, 完全支援 |
| Marshmallow | Android 6.0-8.1, 有限支援* |
| OSS | FOSS 依賴項** |
| GMS | 專有 GMS 元件 |
\* 可能不支援某些壓縮包
** 在不支援 [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine) 的裝置上回退到 OkHttp
<a href="https://github.com/FooIbar/EhViewer/releases">
<img alt="Get it on GitHub" src="https://github.com/Ehviewer-Overhauled/Art/blob/master/get-it-on-github.svg" width="200px"/>
</a>
+5 -10
View File
@@ -26,7 +26,6 @@ kotlinx-datetime = "0.6.0-RC.2"
kotlinx-serialization = "1.6.3"
ksp = "2.0.0-Beta4-1.0.19"
ktor = "3.0.0-beta-1"
okhttp3 = "5.0.0-alpha.12"
okio = "3.8.0"
splitties = "3.0.0"
telephoto = "0.8.0"
@@ -73,6 +72,8 @@ compose-foundation = { module = "androidx.compose.foundation:foundation-android"
compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
compose-material3 = { module = "androidx.compose.material3:material3", version = "1.3.0-SNAPSHOT" }
compose-material3-windowsizeclass = { module = "androidx.compose.material3:material3-window-size-class" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
compose-ui-util = { module = "androidx.compose.ui:ui-util" }
compose-destinations-core = { module = "io.github.raamcosta.compose-destinations:core", version.ref = "compose-destinations" }
@@ -80,8 +81,7 @@ compose-destinations-compiler = { module = "io.github.raamcosta.compose-destinat
material-motion-core = "io.github.fornewid:material-motion-compose-core:1.2.0"
cronet-api = "org.chromium.net:cronet-api:119.6045.31"
cronet-gms = "com.google.android.gms:play-services-cronet:18.0.1"
cronet-embedded = "org.chromium.net:cronet-embedded:119.6045.31"
desugar = "com.android.tools:desugar_jdk_libs_nio:2.0.4"
@@ -93,17 +93,15 @@ jsoup = "org.jsoup:jsoup:1.17.2"
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
logcat = "com.squareup.logcat:logcat:0.1"
okhttp-android = { module = "com.squareup.okhttp3:okhttp-android" }
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp3" }
okio-jvm = { module = "com.squareup.okio:okio-jvm", version.ref = "okio" }
photoview = "com.github.chrisbanes:PhotoView:2.3.0"
reorderable = "sh.calvin.reorderable:reorderable:1.3.2"
reorderable = "sh.calvin.reorderable:reorderable:1.3.3"
serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinx-serialization" }
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
@@ -122,10 +120,7 @@ telephoto-zoomable = { module = "me.saket.telephoto:zoomable", version.ref = "te
[bundles]
coil = ["coil-compose", "coil-gif", "coil-network"]
compose = ["compose-foundation", "compose-material-icons-extended", "compose-material3", "compose-material3-windowsizeclass", "compose-ui-util"]
cronet = ["cronet-api", "cronet-gms"]
kotlinx-serialization = ["serialization-cbor", "serialization-json", "serialization-json-okio", "serialization-xml-core", "serialization-xml"]
ktor = ["ktor-client-okhttp"]
okhttp = ["okhttp-android"]
splitties = ["splitties-appctx", "splitties-arch-room", "splitties-preferences", "splitties-systemservices"]
[plugins]