Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a64ff51ed | ||
|
|
ea9d9cfba7 | ||
|
|
75c6561896 | ||
|
|
8f8db82e9d | ||
|
|
a114a8ac72 | ||
|
|
2c6216057f | ||
|
|
c5cbcf7418 | ||
|
|
5746e3bb57 | ||
|
|
fd3c87be22 | ||
|
|
866fff3e45 | ||
|
|
06df7befb6 | ||
|
|
8021f86473 | ||
|
|
c9919a8b39 | ||
|
|
386015a9ec | ||
|
|
cbce98b34e | ||
|
|
f69521fc1a | ||
|
|
01b4054368 | ||
|
|
e07aab4434 | ||
|
|
18caa87512 | ||
|
|
5676de24da | ||
|
|
57bbb1fe38 | ||
|
|
dcbfbc2e5f | ||
|
|
e1f473d301 | ||
|
|
a33256d29f | ||
|
|
6a81877b09 |
No files matched your search
+42
-199
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Spotless Check
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
oss:
|
||||
default:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -80,123 +80,46 @@ jobs:
|
||||
key: cmake-default-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts', 'app/src/main/cpp/CMakeLists.txt') }}
|
||||
restore-keys: cmake-default-
|
||||
|
||||
- name: Build OSS
|
||||
run: ./gradlew assembleDefaultOssRelease
|
||||
- name: Build
|
||||
run: ./gradlew assembleDefaultRelease
|
||||
|
||||
- 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: default-arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/default/release/app-default-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: default-x86_64-${{ github.sha }}
|
||||
path: app/build/outputs/apk/default/release/app-default-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: default-armeabi-v7a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/default/release/app-default-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: default-x86-${{ github.sha }}
|
||||
path: app/build/outputs/apk/default/release/app-default-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: default-mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/defaultRelease/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
|
||||
name: default-native-debug-symbols-${{ github.sha }}
|
||||
path: app/build/outputs/native-debug-symbols/defaultRelease/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:
|
||||
marshmallow:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -231,121 +154,41 @@ jobs:
|
||||
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
|
||||
run: ./gradlew assembleMarshmallowRelease
|
||||
|
||||
- name: Build Marshmallow OSS
|
||||
run: ./gradlew assembleMarshmallowOssRelease
|
||||
|
||||
- name: Upload Marshmallow OSS ARM64
|
||||
- name: Upload 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: marshmallow-arm64-v8a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-arm64-v8a-release.apk
|
||||
|
||||
- name: Upload Marshmallow OSS x86_64
|
||||
- name: Upload 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: marshmallow-x86_64-${{ github.sha }}
|
||||
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-x86_64-release.apk
|
||||
|
||||
- name: Upload Marshmallow OSS ARM32
|
||||
- name: Upload 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: marshmallow-armeabi-v7a-${{ github.sha }}
|
||||
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-armeabi-v7a-release.apk
|
||||
|
||||
- name: Upload Marshmallow OSS x86
|
||||
- name: Upload 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: marshmallow-x86-${{ github.sha }}
|
||||
path: app/build/outputs/apk/marshmallow/release/app-marshmallow-x86-release.apk
|
||||
|
||||
- name: Upload Marshmallow OSS mapping
|
||||
- name: Upload mapping
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: marshmallow-oss-mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/marshmallowOssRelease/mapping.txt
|
||||
name: marshmallow-mapping-${{ github.sha }}
|
||||
path: app/build/outputs/mapping/marshmallowRelease/mapping.txt
|
||||
|
||||
- name: Upload Marshmallow OSS native debug symbols
|
||||
- name: Upload 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: marshmallow-native-debug-symbols-${{ github.sha }}
|
||||
path: app/build/outputs/native-debug-symbols/marshmallowRelease/native-debug-symbols.zip
|
||||
@@ -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,39 @@ 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/default/release/app-default-universal-release.apk EhViewer-${{ github.ref_name }}-default-universal.apk
|
||||
mv app/build/outputs/apk/default/release/app-default-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-default-arm64-v8a.apk
|
||||
mv app/build/outputs/apk/default/release/app-default-x86_64-release.apk EhViewer-${{ github.ref_name }}-default-x86_64.apk
|
||||
mv app/build/outputs/apk/default/release/app-default-x86-release.apk EhViewer-${{ github.ref_name }}-default-x86.apk
|
||||
mv app/build/outputs/apk/default/release/app-default-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-default-armeabi-v7a.apk
|
||||
mv app/build/outputs/mapping/defaultRelease/mapping.txt EhViewer-${{ github.ref_name }}-default-mapping.txt
|
||||
mv app/build/outputs/native-debug-symbols/defaultRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-default-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/marshmallow/release/app-marshmallow-universal-release.apk EhViewer-${{ github.ref_name }}-marshmallow-universal.apk
|
||||
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-arm64-v8a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-arm64-v8a.apk
|
||||
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-x86_64-release.apk EhViewer-${{ github.ref_name }}-marshmallow-x86_64.apk
|
||||
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-x86-release.apk EhViewer-${{ github.ref_name }}-marshmallow-x86.apk
|
||||
mv app/build/outputs/apk/marshmallow/release/app-marshmallow-armeabi-v7a-release.apk EhViewer-${{ github.ref_name }}-marshmallow-armeabi-v7a.apk
|
||||
mv app/build/outputs/mapping/marshmallowRelease/mapping.txt EhViewer-${{ github.ref_name }}-marshmallow-mapping.txt
|
||||
mv app/build/outputs/native-debug-symbols/marshmallowRelease/native-debug-symbols.zip EhViewer-${{ github.ref_name }}-marshmallow-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-universal.apk
|
||||
EhViewer-${{ github.ref_name }}-default-arm64-v8a.apk
|
||||
EhViewer-${{ github.ref_name }}-default-x86_64.apk
|
||||
EhViewer-${{ github.ref_name }}-default-x86.apk
|
||||
EhViewer-${{ github.ref_name }}-default-armeabi-v7a.apk
|
||||
EhViewer-${{ github.ref_name }}-default-mapping.txt
|
||||
EhViewer-${{ github.ref_name }}-default-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 }}-marshmallow-universal.apk
|
||||
EhViewer-${{ github.ref_name }}-marshmallow-arm64-v8a.apk
|
||||
EhViewer-${{ github.ref_name }}-marshmallow-x86_64.apk
|
||||
EhViewer-${{ github.ref_name }}-marshmallow-x86.apk
|
||||
EhViewer-${{ github.ref_name }}-marshmallow-armeabi-v7a.apk
|
||||
EhViewer-${{ github.ref_name }}-marshmallow-mapping.txt
|
||||
EhViewer-${{ github.ref_name }}-marshmallow-native-debug-symbols.zip
|
||||
+38
-1
@@ -1,5 +1,42 @@
|
||||
# 变更日志
|
||||
|
||||
## [1.11.2] - 2024-03-17
|
||||
|
||||
### 重大变更
|
||||
|
||||
* Default 变种最低支持版本更改为 Android 8.0,重新添加了最低支持 Android 6.0 的 Marshmallow 变种
|
||||
|
||||
### 改进
|
||||
|
||||
* 重新添加了删除快速搜索/下载标签时的确认对话框
|
||||
|
||||
### Bug 修复
|
||||
|
||||
* 收藏页面 搜索栏中文标签转英文异常 [#902](https://github.com/FooIbar/EhViewer/issues/902)
|
||||
* 在收藏或者取消收藏之后,收藏按钮会无反应 [#908](https://github.com/FooIbar/EhViewer/issues/908)
|
||||
* 横屏页面下阅读设置UI重合 [#909](https://github.com/FooIbar/EhViewer/issues/909)
|
||||
* Search bar does not work properly in Japanese [#906](https://github.com/FooIbar/EhViewer/issues/906)
|
||||
* 搜索结果中的 Disowned 画廊不显示上传者
|
||||
* 使用 CI 频道更新时,有时会获取到错误的更新链接
|
||||
|
||||
|
||||
## [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 +44,7 @@
|
||||
* 由于 E 站 Cookie 策略变更,不再支持 Cookie 登录
|
||||
* 登录时自动选择 exhentai (如果可用)
|
||||
* 不再提供默认下载路径
|
||||
* 手动排序更改为以添加时间/上传时间/标题/下载标签/页数对下载列表排序
|
||||
* 由于数据库结构变更,此版本导出的数据将无法被旧版本导入
|
||||
|
||||
### 新功能
|
||||
@@ -19,7 +57,6 @@
|
||||
* 随机打开本地收藏/下载中的画廊 [#695](https://github.com/FooIbar/EhViewer/issues/695)
|
||||
* 随机在搜索结果中跳转
|
||||
* 以网格模式查看下载列表
|
||||
* 以添加时间/上传时间/标题/下载标签/页数对下载列表排序
|
||||
* 本地收藏/历史/下载中的标签搜索 [#860](https://github.com/FooIbar/EhViewer/issues/860)
|
||||
|
||||
### 改进
|
||||
|
||||
@@ -66,16 +66,10 @@ 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)
|
||||
| Flavor | Feature |
|
||||
|-------------|----------------------------------|
|
||||
| Default | Android 8.0+, full support |
|
||||
| Marshmallow | Android 6.0-7.1, limited support |
|
||||
|
||||
<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"/>
|
||||
|
||||
+25
-25
@@ -60,10 +60,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "moe.tarsin.ehviewer"
|
||||
minSdk = 28
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 180051
|
||||
versionName = "1.11.0"
|
||||
versionCode = 180053
|
||||
versionName = "1.11.2"
|
||||
resourceConfigurations.addAll(
|
||||
listOf(
|
||||
"zh",
|
||||
@@ -80,6 +80,7 @@ android {
|
||||
"nb-rNO",
|
||||
),
|
||||
)
|
||||
buildConfigField("String", "RAW_VERSION_NAME", "\"$versionName$versionNameSuffix\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
|
||||
buildConfigField("String", "REPO_NAME", "\"$repoName\"")
|
||||
ndk {
|
||||
@@ -87,27 +88,14 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions += listOf("api", "oss")
|
||||
flavorDimensions += "api"
|
||||
|
||||
productFlavors {
|
||||
create("default") {
|
||||
dimension = "api"
|
||||
}
|
||||
create("default")
|
||||
create("marshmallow") {
|
||||
dimension = "api"
|
||||
minSdk = 23
|
||||
applicationIdSuffix = ".m"
|
||||
versionNameSuffix = "-M"
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
}
|
||||
create("oss") {
|
||||
dimension = "oss"
|
||||
}
|
||||
create("gms") {
|
||||
dimension = "oss"
|
||||
versionNameSuffix = "-gms"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +105,10 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
freeCompilerArgs = listOf(
|
||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||
@@ -151,9 +143,11 @@ android {
|
||||
}
|
||||
|
||||
packaging {
|
||||
dex {
|
||||
useLegacyPackaging = false
|
||||
}
|
||||
resources {
|
||||
excludes += "/META-INF/**"
|
||||
excludes += "/okhttp3/**"
|
||||
excludes += "/kotlin/**"
|
||||
excludes += "**.txt"
|
||||
excludes += "**.bin"
|
||||
@@ -230,10 +224,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 +247,7 @@ dependencies {
|
||||
|
||||
implementation(libs.telephoto.zoomable)
|
||||
|
||||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.ktor.client.core)
|
||||
|
||||
implementation(libs.bundles.kotlinx.serialization)
|
||||
|
||||
@@ -267,7 +257,17 @@ dependencies {
|
||||
|
||||
coreLibraryDesugaring(libs.desugar)
|
||||
|
||||
"gmsImplementation"(libs.bundles.cronet)
|
||||
implementation(libs.cronet.embedded)
|
||||
|
||||
debugImplementation(libs.compose.ui.tooling)
|
||||
implementation(libs.compose.ui.tooling.preview)
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
// Workaround for https://issuetracker.google.com/329489167
|
||||
force("androidx.compose.foundation:foundation-android:1.7.0-alpha03")
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
|
||||
Vendored
+2
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.onConsumedWindowInsetsChanged
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.GenericShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.BasicTextField2
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
@@ -494,7 +494,7 @@ private fun SearchBarInputField(
|
||||
if (focused) colors.focusedTextColor else colors.unfocusedTextColor
|
||||
}
|
||||
|
||||
BasicTextField(
|
||||
BasicTextField2(
|
||||
state = state,
|
||||
modifier = modifier
|
||||
.height(InputFieldHeight)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ object Settings : DataStorePreferences(null) {
|
||||
val gridView = boolPref("grid_view", false)
|
||||
val showGalleryPages = boolPref("show_gallery_pages", true)
|
||||
val qSSaveProgress = boolPref("qs_save_progress", true)
|
||||
val security = boolPref("require_unlock", false)
|
||||
val recentDownloadLabel = stringOrNullPref("recent_download_label", null)
|
||||
|
||||
var downloadScheme by stringOrNullPref("image_scheme", null)
|
||||
@@ -146,7 +147,6 @@ object Settings : DataStorePreferences(null) {
|
||||
var hasDefaultDownloadLabel by boolPref("has_default_download_label", false)
|
||||
var saveParseErrorBody by boolPref("save_parse_error_body", true)
|
||||
var saveCrashLog by boolPref("save_crash_log", true)
|
||||
var security by boolPref("require_unlock", false)
|
||||
var removeImageFiles by boolPref("include_pic", true)
|
||||
var needSignIn by boolPref("need_sign_in", true)
|
||||
var harmonizeCategoryColor by boolPref("harmonize_category_color", true)
|
||||
@@ -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) {
|
||||
|
||||
@@ -21,10 +21,7 @@ import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.util.bodyAsUtf8Text
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.forms.FormBuilder
|
||||
import io.ktor.client.request.forms.FormDataContent
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.prepareRequest
|
||||
import io.ktor.client.request.setBody
|
||||
@@ -96,8 +93,3 @@ inline fun HttpRequestBuilder.jsonBody(builder: JsonObjectBuilder.() -> Unit) {
|
||||
val json = buildJsonObject(builder).toString()
|
||||
setBody(TextContent(text = json, contentType = ContentType.Application.Json))
|
||||
}
|
||||
|
||||
fun HttpRequestBuilder.multipartBody(builder: FormBuilder.() -> Unit) {
|
||||
method = HttpMethod.Post
|
||||
setBody(MultiPartFormDataContent(formData(builder)))
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -25,7 +25,6 @@ import androidx.core.graphics.ColorUtils
|
||||
import arrow.core.memoize
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.abs
|
||||
|
||||
object EhUtils {
|
||||
@@ -58,14 +57,14 @@ object EhUtils {
|
||||
private const val BG_COLOR_UNKNOWN = 0xff000000u
|
||||
|
||||
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff
|
||||
private val PATTERN_TITLE_PREFIX = Pattern.compile(
|
||||
"^(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*",
|
||||
private val PATTERN_TITLE_PREFIX = Regex(
|
||||
"""^(?:\([^)]*\)|\[[^]]*]|\{[^}]*\}|~[^~]*~|\s+)*""",
|
||||
)
|
||||
|
||||
// Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23
|
||||
private val PATTERN_TITLE_SUFFIX = Pattern.compile(
|
||||
"(?:\\s+ch.[\\s\\d-]+)?(?:\\([^)]*\\)|\\[[^]]*]|\\{[^}]*\\}|~[^~]*~|\\s+)*$",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
private val PATTERN_TITLE_SUFFIX = Regex(
|
||||
"""(?:\s+ch.[\s\d-]+)?(?:\([^)]*\)|\[[^]]*]|\{[^}]*\}|~[^~]*~|\s+)*$""",
|
||||
RegexOption.IGNORE_CASE,
|
||||
)
|
||||
|
||||
private val CATEGORY_VALUES = hashMapOf(
|
||||
@@ -170,16 +169,12 @@ object EhUtils {
|
||||
|
||||
fun extractTitle(fullTitle: String?): String? {
|
||||
var title: String = fullTitle ?: return null
|
||||
title = PATTERN_TITLE_PREFIX.matcher(title).replaceFirst("")
|
||||
title = PATTERN_TITLE_SUFFIX.matcher(title).replaceFirst("")
|
||||
title = PATTERN_TITLE_PREFIX.replaceFirst(title, "")
|
||||
title = PATTERN_TITLE_SUFFIX.replaceFirst(title, "")
|
||||
// Sometimes title is combined by romaji and english translation.
|
||||
// Only need romaji.
|
||||
// TODO But not sure every '|' means that
|
||||
val index = title.indexOf('|')
|
||||
if (index >= 0) {
|
||||
title = title.substring(0, index)
|
||||
}
|
||||
return title.ifEmpty { null }
|
||||
return title.substringBeforeLast('|').trim().ifEmpty { null }
|
||||
}
|
||||
|
||||
fun handleThumbUrlResolution(url: String?): String? {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.client.data
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import moe.tarsin.kt.unreachable
|
||||
|
||||
interface GalleryInfo {
|
||||
@@ -62,36 +61,36 @@ interface GalleryInfo {
|
||||
"NL",
|
||||
)
|
||||
private val S_LANG_PATTERNS = arrayOf(
|
||||
Pattern.compile(
|
||||
Regex(
|
||||
"[(\\[]eng(?:lish)?[)\\]]|英訳",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
RegexOption.IGNORE_CASE,
|
||||
),
|
||||
// [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
|
||||
Pattern.compile(
|
||||
Regex(
|
||||
"[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
RegexOption.IGNORE_CASE,
|
||||
),
|
||||
Pattern.compile(
|
||||
Regex(
|
||||
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
RegexOption.IGNORE_CASE,
|
||||
),
|
||||
Pattern.compile("[(\\[]korean?[)\\]]|韓国翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]portuguese|ポルトガル翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile(
|
||||
Regex("[(\\[]korean?[)\\]]|韓国翻訳", RegexOption.IGNORE_CASE),
|
||||
Regex("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", RegexOption.IGNORE_CASE),
|
||||
Regex("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", RegexOption.IGNORE_CASE),
|
||||
Regex("[(\\[]portuguese|ポルトガル翻訳", RegexOption.IGNORE_CASE),
|
||||
Regex(
|
||||
"[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
RegexOption.IGNORE_CASE,
|
||||
),
|
||||
Pattern.compile("[(\\[]german[)\\]]|ドイツ翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]italiano?[)\\]]|イタリア翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile(
|
||||
Regex("[(\\[]german[)\\]]|ドイツ翻訳", RegexOption.IGNORE_CASE),
|
||||
Regex("[(\\[]italiano?[)\\]]|イタリア翻訳", RegexOption.IGNORE_CASE),
|
||||
Regex(
|
||||
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
RegexOption.IGNORE_CASE,
|
||||
),
|
||||
Pattern.compile("[(\\[]polish[)\\]]|ポーランド翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Regex("[(\\[]polish[)\\]]|ポーランド翻訳", RegexOption.IGNORE_CASE),
|
||||
Regex("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", RegexOption.IGNORE_CASE),
|
||||
Regex("[(\\[]dutch[)\\]]|オランダ翻訳", RegexOption.IGNORE_CASE),
|
||||
)
|
||||
val S_LANG_TAGS = arrayOf(
|
||||
"language:english",
|
||||
@@ -123,7 +122,7 @@ interface GalleryInfo {
|
||||
|
||||
private fun generateSLangFromTitle(title: String): String? {
|
||||
for (i in S_LANGS.indices) {
|
||||
if (S_LANG_PATTERNS[i].matcher(title).find()) {
|
||||
if (S_LANG_PATTERNS[i].matches(title)) {
|
||||
return S_LANGS[i]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,27 +16,21 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.util.toLongOrDefault
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Like http://exhentai.org/g/1234567/a1b2c3d4e5<br></br>
|
||||
*/
|
||||
object GalleryDetailUrlParser {
|
||||
private val URL_STRICT_PATTERN = Pattern.compile(
|
||||
private val URL_STRICT_PATTERN = Regex(
|
||||
"https?://(?:${EhUrl.DOMAIN_EX}|${EhUrl.DOMAIN_E}(?:/lofi)?)/(?:g|mpv)/(\\d+)/([0-9a-f]{10})",
|
||||
)
|
||||
private val URL_PATTERN = Pattern.compile("(\\d+)/([0-9a-f]{10})(?:[^0-9a-f]|$)")
|
||||
private val URL_PATTERN = Regex("(\\d+)/([0-9a-f]{10})(?:[^0-9a-f]|$)")
|
||||
|
||||
fun parse(url: String?, strict: Boolean = true): Result? {
|
||||
url ?: return null
|
||||
val pattern = if (strict) URL_STRICT_PATTERN else URL_PATTERN
|
||||
val m = pattern.matcher(url)
|
||||
return if (m.find()) {
|
||||
val gid = m.group(1)!!.toLongOrDefault(0).takeIf { it > 0 }
|
||||
gid?.let { Result(it, m.group(2)!!) }
|
||||
} else {
|
||||
null
|
||||
return pattern.find(url)?.destructured?.let { (gid, token) ->
|
||||
Result(gid.toLong(), token)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,34 +16,21 @@
|
||||
package com.hippo.ehviewer.client.parser
|
||||
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.util.toIntOrDefault
|
||||
import com.hippo.ehviewer.util.toLongOrDefault
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* Like http://exhentai.org/s/91ea4b6d89/901103-12
|
||||
*/
|
||||
object GalleryPageUrlParser {
|
||||
private val URL_STRICT_PATTERN = Pattern.compile(
|
||||
private val URL_STRICT_PATTERN = Regex(
|
||||
"https?://(?:${EhUrl.DOMAIN_EX}|${EhUrl.DOMAIN_E}(?:/lofi)?)/s/([0-9a-f]{10})/(\\d+)-(\\d+)",
|
||||
)
|
||||
private val URL_PATTERN = Pattern.compile("([0-9a-f]{10})/(\\d+)-(\\d+)")
|
||||
private val URL_PATTERN = Regex("([0-9a-f]{10})/(\\d+)-(\\d+)")
|
||||
|
||||
fun parse(url: String?, strict: Boolean = true): Result? {
|
||||
url ?: return null
|
||||
val pattern = if (strict) URL_STRICT_PATTERN else URL_PATTERN
|
||||
val m = pattern.matcher(url)
|
||||
return if (m.find()) {
|
||||
val gid = m.group(2)!!.toLongOrDefault(-1L)
|
||||
val pToken = m.group(1)!!
|
||||
val page = m.group(3)!!.toIntOrDefault(0) - 1
|
||||
if (gid < 0 || page < 0) {
|
||||
null
|
||||
} else {
|
||||
Result(gid, pToken, page)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
return pattern.find(url)?.destructured?.let { (pToken, gid, page) ->
|
||||
Result(gid.toLong(), pToken, page.toInt() - 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -94,12 +94,7 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
if (label == null) {
|
||||
return false
|
||||
}
|
||||
for ((label1) in labelList) {
|
||||
if (label == label1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return labelList.any { it.label == label }
|
||||
}
|
||||
|
||||
fun containDownloadInfo(gid: Long) = mAllInfoMap.containsKey(gid)
|
||||
@@ -518,12 +513,14 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
|
||||
}
|
||||
|
||||
suspend fun deleteLabel(label: String) {
|
||||
labelList.run {
|
||||
with(labelList) {
|
||||
val index = indexOfFirst { it.label == label }
|
||||
val item = get(index)
|
||||
EhDB.removeDownloadLabel(item)
|
||||
subList(index + 1, size).forEach {
|
||||
it.position--
|
||||
}
|
||||
EhDB.removeDownloadLabel(removeAt(index))
|
||||
removeAt(index)
|
||||
}
|
||||
allInfoList.forEach {
|
||||
if (it.label == label) {
|
||||
@@ -686,6 +683,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 +697,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 +748,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)
|
||||
}
|
||||
+44
-80
@@ -26,33 +26,27 @@ 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.job
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.chromium.net.CronetException
|
||||
import org.chromium.net.UploadDataProvider
|
||||
import org.chromium.net.UploadDataSink
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
import org.chromium.net.apihelpers.UploadDataProviders
|
||||
|
||||
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)
|
||||
}
|
||||
override suspend fun execute(data: HttpRequestData) = executeHttpRequest(callContext(), data)
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
private suspend fun executeHttpRequest(
|
||||
callContext: CoroutineContext,
|
||||
data: HttpRequestData,
|
||||
): HttpResponseData = suspendCancellableCoroutine { continuation ->
|
||||
) = suspendCancellableCoroutine { continuation ->
|
||||
val requestTime = GMTDate()
|
||||
|
||||
val callback = object : UrlRequest.Callback() {
|
||||
@@ -67,23 +61,22 @@ object CronetEngine : HttpClientEngineBase("Cronet") {
|
||||
}
|
||||
|
||||
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,
|
||||
responseBody = GlobalScope.writer(callContext) {
|
||||
pool.useInstance {
|
||||
chunkChan = Channel()
|
||||
request.read(it)
|
||||
chunkChan.consumeEach { buffer ->
|
||||
buffer.flip()
|
||||
channel.writeFully(buffer)
|
||||
buffer.clear()
|
||||
request.read(buffer)
|
||||
}
|
||||
}
|
||||
}.channel,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -97,24 +90,24 @@ object CronetEngine : HttpClientEngineBase("Cronet") {
|
||||
}
|
||||
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||
if (::chunkChan.isInitialized) {
|
||||
chunkChan.close(error)
|
||||
} else {
|
||||
if (continuation.isActive) {
|
||||
continuation.resumeWithException(error)
|
||||
} else {
|
||||
chunkChan.close(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val request = cronetHttpClient.newUrlRequestBuilder(data.url.toString(), callback, cronetHttpClientExecutor).apply {
|
||||
cronetHttpClient.newUrlRequestBuilder(data.url.toString(), callback, cronetHttpClientExecutor).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() }
|
||||
data.body.toUploadDataProvider()?.let { setUploadDataProvider(it, cronetHttpClientExecutor) }
|
||||
}.build().apply {
|
||||
start()
|
||||
callContext.job.invokeOnCompletion { cancel() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,55 +115,26 @@ private fun UrlResponseInfo.toHttpResponseData(
|
||||
requestTime: GMTDate,
|
||||
callContext: CoroutineContext,
|
||||
responseBody: ByteReadChannel = ByteReadChannel.Empty,
|
||||
): HttpResponseData {
|
||||
return HttpResponseData(
|
||||
statusCode = HttpStatusCode.fromValue(httpStatusCode),
|
||||
requestTime = requestTime,
|
||||
headers = headers {
|
||||
allHeaders.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,
|
||||
)
|
||||
}
|
||||
) = HttpResponseData(
|
||||
statusCode = HttpStatusCode.fromValue(httpStatusCode),
|
||||
requestTime = requestTime,
|
||||
headers = headers {
|
||||
allHeaders.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) {
|
||||
private fun OutgoingContent.toUploadDataProvider() = 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()
|
||||
}
|
||||
}
|
||||
is OutgoingContent.ByteArrayContent -> UploadDataProviders.create(bytes())
|
||||
else -> error("UnsupportedContentType $this")
|
||||
}
|
||||
@@ -128,7 +128,7 @@ fun Context.interceptSecurityOrReturn() {
|
||||
val lockedDelayTime = lockedResumeTime - lockedLastLeaveTime
|
||||
if (lockedDelayTime < Settings.securityDelay * 60) {
|
||||
locked = false
|
||||
} else if (Settings.security && isAuthenticationSupported() && locked) {
|
||||
} else if (Settings.security.value && isAuthenticationSupported() && locked) {
|
||||
startActivity(Intent(this, SecurityActivity::class.java))
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ package com.hippo.ehviewer.ui.reader
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Brightness5
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -25,7 +28,7 @@ import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
|
||||
@Composable
|
||||
fun ColorFilterSetting() = Column {
|
||||
fun ColorFilterSetting() = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
|
||||
val customBrightness = Settings.customBrightness.asMutableState()
|
||||
SwitchChoice(
|
||||
title = stringResource(id = R.string.pref_custom_brightness),
|
||||
|
||||
@@ -2,8 +2,12 @@ package com.hippo.ehviewer.ui.reader
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringArrayResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.hippo.ehviewer.R
|
||||
@@ -11,7 +15,7 @@ import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
|
||||
@Composable
|
||||
fun ReaderGeneralSetting() = Column {
|
||||
fun ReaderGeneralSetting() = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_reader_theme),
|
||||
entries = stringArrayResource(id = R.array.reader_themes),
|
||||
|
||||
@@ -4,8 +4,11 @@ import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -21,7 +24,7 @@ import com.hippo.ehviewer.collectAsState
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
|
||||
@Composable
|
||||
fun ReaderModeSetting() = Column {
|
||||
fun ReaderModeSetting() = Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
|
||||
SpinnerChoice(
|
||||
title = stringResource(id = R.string.pref_category_reading_mode),
|
||||
entries = stringArrayResource(id = R.array.viewers_selector),
|
||||
|
||||
@@ -4,10 +4,10 @@ import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.statusBarsIgnoringVisibility
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
@@ -112,7 +112,7 @@ fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsN
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { dispose() },
|
||||
sheetState = state,
|
||||
windowInsets = WindowInsets(0),
|
||||
windowInsets = WindowInsets.statusBarsIgnoringVisibility,
|
||||
) {
|
||||
ReaderPageSheetMeta(
|
||||
retry = { pageLoader.retryPage(page.index) },
|
||||
@@ -123,7 +123,6 @@ fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsN
|
||||
saveTo = { launchIO { with(pageLoader) { saveTo(page) } } },
|
||||
dismiss = { launch { state.hide().also { dispose() } } },
|
||||
)
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,12 +182,12 @@ fun ReaderScreen(info: BaseGalleryInfo, page: Int = -1, navigator: DestinationsN
|
||||
// Yeah, I know color state should not be read here, but we have to do it...
|
||||
scrimColor = scrim,
|
||||
dragHandle = null,
|
||||
windowInsets = WindowInsets(0),
|
||||
windowInsets = WindowInsets.statusBarsIgnoringVisibility,
|
||||
) {
|
||||
SettingsPager(modifier = Modifier.fillMaxSize()) { page ->
|
||||
isColorFilter = page == 2
|
||||
appbarVisible = !isColorFilter
|
||||
}
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,13 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SwipeToDismissBoxDefaults
|
||||
import androidx.compose.material3.SwipeToDismissBoxState
|
||||
import androidx.compose.material3.SwipeToDismissBoxValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
@@ -61,6 +63,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
@@ -142,6 +145,7 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
val activity = remember { findActivity<MainActivity>() }
|
||||
val density = LocalDensity.current
|
||||
val view = LocalView.current
|
||||
val positionalThreshold = SwipeToDismissBoxDefaults.positionalThreshold
|
||||
val allName = stringResource(R.string.download_all)
|
||||
val defaultName = stringResource(R.string.default_download_label_name)
|
||||
val title = stringResource(
|
||||
@@ -261,21 +265,28 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
},
|
||||
)
|
||||
}
|
||||
itemsIndexed(labelList, key = { _, item -> item.label }) { index, (item) ->
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = {
|
||||
itemsIndexed(labelList, key = { _, item -> item.id!! }) { index, (item) ->
|
||||
// Not using rememberSwipeToDismissBoxState to prevent LazyColumn from reusing it
|
||||
val dismissState = remember { SwipeToDismissBoxState(SwipeToDismissBoxValue.Settled, density, positionalThreshold = positionalThreshold) }
|
||||
LaunchedEffect(dismissState) {
|
||||
snapshotFlow { dismissState.currentValue }.collect {
|
||||
if (it == SwipeToDismissBoxValue.EndToStart) {
|
||||
launch {
|
||||
runCatching {
|
||||
awaitPermissionOrCancel(confirmText = R.string.delete) {
|
||||
Text(text = stringResource(R.string.delete_label, item))
|
||||
}
|
||||
}.onSuccess {
|
||||
DownloadManager.deleteLabel(item)
|
||||
when (filterState.label) {
|
||||
item -> switchLabel("")
|
||||
null -> invalidateKey = !invalidateKey
|
||||
}
|
||||
}.onFailure {
|
||||
dismissState.reset()
|
||||
}
|
||||
}
|
||||
true
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
ReorderableItem(reorderableLabelState, key = item) { isDragging ->
|
||||
SwipeToDismissBox2(
|
||||
state = dismissState,
|
||||
@@ -564,12 +575,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
|
||||
|
||||
@@ -230,6 +230,7 @@ fun FavouritesScreen(navigator: DestinationsNavigator) = composing(navigator) {
|
||||
},
|
||||
onSearchHidden = { fabHidden = false },
|
||||
refreshState = refreshState,
|
||||
tagNamespace = !isLocalFav,
|
||||
searchBarOffsetY = { searchBarOffsetY },
|
||||
trailingIcon = {
|
||||
val sheetState = LocalSideSheetState.current
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.os.Parcelable
|
||||
import android.text.TextUtils.TruncateAt.END
|
||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.MutatorMutex
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -163,8 +164,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import splitties.systemservices.downloadManager
|
||||
@@ -506,32 +505,28 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa
|
||||
} else {
|
||||
stringResource(id = R.string.not_favorited)
|
||||
}
|
||||
val favoritesLock = remember { Mutex() }
|
||||
val favoritesLock = remember { MutatorMutex() }
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
val removeSucceed = stringResource(R.string.remove_from_favorite_success)
|
||||
val addSucceed = stringResource(R.string.add_to_favorite_success)
|
||||
val removeFailed = stringResource(R.string.remove_from_favorite_failure)
|
||||
// val removeFailed = stringResource(R.string.remove_from_favorite_failure)
|
||||
val addFailed = stringResource(R.string.add_to_favorite_failure)
|
||||
FilledTertiaryIconToggleButton(
|
||||
checked = favSlot != NOT_FAVORITED,
|
||||
onCheckedChange = {
|
||||
launchIO {
|
||||
favoritesLock.withLock {
|
||||
var remove = false
|
||||
favoritesLock.mutate {
|
||||
runSuspendCatching {
|
||||
remove = !modifyFavorites(galleryDetail.galleryInfo)
|
||||
}.onSuccess {
|
||||
if (remove) {
|
||||
showSnackbar(removeSucceed)
|
||||
} else {
|
||||
modifyFavorites(galleryDetail.galleryInfo)
|
||||
}.onSuccess { add ->
|
||||
if (add) {
|
||||
showSnackbar(addSucceed)
|
||||
} else {
|
||||
showSnackbar(removeSucceed)
|
||||
}
|
||||
}.onFailure {
|
||||
if (remove) {
|
||||
showSnackbar(removeFailed)
|
||||
} else {
|
||||
showSnackbar(addFailed)
|
||||
}
|
||||
// TODO: We don't know if it's add or remove
|
||||
showSnackbar(addFailed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,12 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SwipeToDismissBoxDefaults
|
||||
import androidx.compose.material3.SwipeToDismissBoxState
|
||||
import androidx.compose.material3.SwipeToDismissBoxValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
@@ -62,6 +63,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
@@ -201,6 +203,7 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) = c
|
||||
onBack = { showSearchLayout = false },
|
||||
)
|
||||
val density = LocalDensity.current
|
||||
val positionalThreshold = SwipeToDismissBoxDefaults.positionalThreshold
|
||||
val listState = rememberLazyGridState()
|
||||
val gridState = rememberLazyStaggeredGridState()
|
||||
val isTopList = remember(urlBuilder) { urlBuilder.mode == MODE_TOPLIST }
|
||||
@@ -385,21 +388,30 @@ fun GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) = c
|
||||
}
|
||||
itemsIndexed(quickSearchList, key = { _, item -> item.id!! }) { index, item ->
|
||||
ReorderableItem(reorderableLazyListState, key = item.id!!) { isDragging ->
|
||||
val dismissState = rememberSwipeToDismissBoxState(
|
||||
confirmValueChange = { value ->
|
||||
// Not using rememberSwipeToDismissBoxState to prevent LazyColumn from reusing it
|
||||
val dismissState = remember { SwipeToDismissBoxState(SwipeToDismissBoxValue.Settled, density, positionalThreshold = positionalThreshold) }
|
||||
LaunchedEffect(dismissState) {
|
||||
snapshotFlow { dismissState.currentValue }.collect { value ->
|
||||
if (value == SwipeToDismissBoxValue.EndToStart) {
|
||||
with(quickSearchList) {
|
||||
val removeIndex = indexOf(item)
|
||||
subList(removeIndex + 1, size).forEach {
|
||||
it.position--
|
||||
runCatching {
|
||||
awaitPermissionOrCancel(confirmText = R.string.delete) {
|
||||
Text(text = stringResource(R.string.delete_quick_search, item.name))
|
||||
}
|
||||
removeAt(removeIndex)
|
||||
}.onSuccess {
|
||||
EhDB.deleteQuickSearch(item)
|
||||
with(quickSearchList) {
|
||||
val removeIndex = indexOf(item)
|
||||
subList(removeIndex + 1, size).forEach {
|
||||
it.position--
|
||||
}
|
||||
removeAt(removeIndex)
|
||||
}
|
||||
}.onFailure {
|
||||
dismissState.reset()
|
||||
}
|
||||
launchIO { EhDB.deleteQuickSearch(item) }
|
||||
}
|
||||
true
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
SwipeToDismissBox2(
|
||||
state = dismissState,
|
||||
backgroundContent = {},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -264,25 +264,30 @@ fun EhScreen(navigator: DestinationsNavigator) {
|
||||
entryValueRes = R.array.launch_page_entry_values,
|
||||
value = Settings::launchPage.observed,
|
||||
)
|
||||
val listMode = Settings.listMode.asMutableState()
|
||||
SimpleMenuPreferenceInt(
|
||||
title = stringResource(id = R.string.settings_eh_list_mode),
|
||||
entry = R.array.list_mode_entries,
|
||||
entryValueRes = R.array.list_mode_entry_values,
|
||||
value = Settings.listMode.asMutableState(),
|
||||
)
|
||||
IntSliderPreference(
|
||||
maxValue = 60,
|
||||
minValue = 20,
|
||||
step = 7,
|
||||
title = stringResource(id = R.string.list_tile_thumb_size),
|
||||
value = Settings.listThumbSize::value,
|
||||
)
|
||||
SimpleMenuPreferenceInt(
|
||||
title = stringResource(id = R.string.settings_eh_detail_size),
|
||||
entry = R.array.detail_size_entries,
|
||||
entryValueRes = R.array.detail_size_entry_values,
|
||||
value = Settings.detailSize.asMutableState(),
|
||||
value = listMode,
|
||||
)
|
||||
AnimatedVisibility(visible = listMode.value == 0) {
|
||||
Column {
|
||||
IntSliderPreference(
|
||||
maxValue = 60,
|
||||
minValue = 20,
|
||||
step = 7,
|
||||
title = stringResource(id = R.string.list_tile_thumb_size),
|
||||
value = Settings.listThumbSize::value,
|
||||
)
|
||||
SimpleMenuPreferenceInt(
|
||||
title = stringResource(id = R.string.settings_eh_detail_size),
|
||||
entry = R.array.detail_size_entries,
|
||||
entryValueRes = R.array.detail_size_entry_values,
|
||||
value = Settings.detailSize.asMutableState(),
|
||||
)
|
||||
}
|
||||
}
|
||||
IntSliderPreference(
|
||||
maxValue = 10,
|
||||
minValue = 1,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.hippo.ehviewer.ui.settings
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -22,6 +23,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import com.hippo.ehviewer.EhApplication.Companion.searchDatabase
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.asMutableState
|
||||
import com.hippo.ehviewer.ui.isAuthenticationSupported
|
||||
import com.hippo.ehviewer.ui.tools.LocalDialogState
|
||||
import com.hippo.ehviewer.ui.tools.observed
|
||||
@@ -54,20 +56,27 @@ fun PrivacyScreen(navigator: DestinationsNavigator) {
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
) {
|
||||
Column(modifier = Modifier.padding(it).nestedScroll(scrollBehavior.nestedScrollConnection)) {
|
||||
val security = Settings.security.asMutableState()
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_privacy_require_unlock),
|
||||
value = Settings::security,
|
||||
enabled = LocalContext.current.isAuthenticationSupported(),
|
||||
)
|
||||
val securityDelay = Settings::securityDelay.observed
|
||||
val summary = if (securityDelay.value == 0) stringResource(id = R.string.settings_privacy_require_unlock_delay_summary_immediately) else stringResource(id = R.string.settings_privacy_require_unlock_delay_summary, securityDelay.value)
|
||||
IntSliderPreference(
|
||||
maxValue = 30,
|
||||
title = stringResource(id = R.string.settings_privacy_require_unlock_delay),
|
||||
summary = summary,
|
||||
value = securityDelay.rememberedAccessor,
|
||||
value = security.rememberedAccessor,
|
||||
enabled = LocalContext.current.isAuthenticationSupported(),
|
||||
)
|
||||
AnimatedVisibility(visible = security.value) {
|
||||
val securityDelay = Settings::securityDelay.observed
|
||||
val summary = if (securityDelay.value == 0) {
|
||||
stringResource(id = R.string.settings_privacy_require_unlock_delay_summary_immediately)
|
||||
} else {
|
||||
stringResource(id = R.string.settings_privacy_require_unlock_delay_summary, securityDelay.value)
|
||||
}
|
||||
IntSliderPreference(
|
||||
maxValue = 30,
|
||||
title = stringResource(id = R.string.settings_privacy_require_unlock_delay),
|
||||
summary = summary,
|
||||
value = securityDelay.rememberedAccessor,
|
||||
enabled = LocalContext.current.isAuthenticationSupported(),
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_privacy_secure),
|
||||
summary = stringResource(id = R.string.settings_privacy_secure_summary),
|
||||
|
||||
@@ -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.RAW_VERSION_NAME
|
||||
val release = ghStatement(LATEST_RELEASE_URL).executeAndParseAs<GithubRelease>()
|
||||
val latestVersion = release.version
|
||||
val description = release.info
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import com.hippo.ehviewer.BuildConfig
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.asUniFile
|
||||
import java.io.File
|
||||
@@ -30,6 +32,12 @@ object AppConfig {
|
||||
private const val PARSE_ERROR = "parse_error"
|
||||
private const val CRASH = "crash"
|
||||
|
||||
private val abi = Build.SUPPORTED_ABIS[0].takeIf {
|
||||
it in setOf("arm64-v8a", "x86_64", "armeabi-v7a", "x86")
|
||||
} ?: "universal"
|
||||
|
||||
fun matchVariant(name: String) = name.contains(BuildConfig.FLAVOR) && name.contains(abi)
|
||||
|
||||
private val externalAppDir: File?
|
||||
get() {
|
||||
if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
|
||||
|
||||
@@ -17,57 +17,37 @@ package com.hippo.ehviewer.util
|
||||
|
||||
import android.os.Looper
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import java.io.BufferedReader
|
||||
import java.io.FileReader
|
||||
import java.util.regex.Pattern
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
object OSUtils {
|
||||
private const val PROCFS_MEMFILE = "/proc/meminfo"
|
||||
private val PROCFS_MEMFILE_FORMAT = Pattern.compile("^([a-zA-Z]*):[ \t]*([0-9]*)[ \t]kB")
|
||||
private val PROCFS_MEMFILE_FORMAT = Regex("^([a-zA-Z]*):[ \t]*([0-9]*)[ \t]kB")
|
||||
private const val MEMTOTAL_STRING = "MemTotal"
|
||||
private var sTotalMem = Long.MIN_VALUE
|
||||
|
||||
val appAllocatedMemory: Long
|
||||
/**
|
||||
* Get application allocated memory size
|
||||
*/
|
||||
get() = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()
|
||||
|
||||
val appMaxMemory: Long
|
||||
/**
|
||||
* Get application max memory size
|
||||
*/
|
||||
get() = Runtime.getRuntime().maxMemory()
|
||||
val totalMemory: Long
|
||||
/**
|
||||
* Get device RAM size
|
||||
*/
|
||||
get() {
|
||||
if (sTotalMem == Long.MIN_VALUE) {
|
||||
runCatching {
|
||||
BufferedReader(FileReader(PROCFS_MEMFILE), 64).use { reader ->
|
||||
var line: String?
|
||||
while (reader.readLine().also { line = it } != null) {
|
||||
val matcher = PROCFS_MEMFILE_FORMAT.matcher(line!!)
|
||||
if (matcher.find() && MEMTOTAL_STRING == matcher.group(1)) {
|
||||
var mem = matcher.group(2)!!.toLongOrDefault(-1L)
|
||||
if (mem != -1L) {
|
||||
mem *= 1024
|
||||
}
|
||||
sTotalMem = mem
|
||||
break
|
||||
}
|
||||
|
||||
val totalMemory by lazy {
|
||||
runCatching {
|
||||
FileReader(PROCFS_MEMFILE).buffered(64).use { reader ->
|
||||
reader.lineSequence().forEach { line ->
|
||||
PROCFS_MEMFILE_FORMAT.find(line)?.destructured?.let { (k, v) ->
|
||||
if (k == MEMTOTAL_STRING) {
|
||||
return@runCatching v.toLong() * 1024
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
logcat(it)
|
||||
}
|
||||
if (sTotalMem == Long.MIN_VALUE) {
|
||||
sTotalMem = -1L
|
||||
}
|
||||
}
|
||||
return sTotalMem
|
||||
}
|
||||
-1L
|
||||
}.onFailure {
|
||||
logcat(it)
|
||||
}.getOrElse { -1L }
|
||||
}
|
||||
}
|
||||
|
||||
val isMainThread: Boolean
|
||||
|
||||
@@ -16,36 +16,24 @@
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.URLSpan
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object TextUrl {
|
||||
private val URL_PATTERN =
|
||||
Pattern.compile("(http|https)://[a-z0-9A-Z%-]+(\\.[a-z0-9A-Z%-]+)+(:\\d{1,5})?(/[a-zA-Z0-9-_~:#@!&',;=%/*.?+$\\[\\]()]+)?/?")
|
||||
Regex("(http|https)://[a-z0-9A-Z%-]+(\\.[a-z0-9A-Z%-]+)+(:\\d{1,5})?(/[a-zA-Z0-9-_~:#@!&',;=%/*.?+$\\[\\]()]+)?/?")
|
||||
|
||||
fun handleTextUrl(content: CharSequence): CharSequence {
|
||||
val m = URL_PATTERN.matcher(content)
|
||||
var spannable: Spannable? = null
|
||||
while (m.find()) {
|
||||
// Ensure spannable
|
||||
if (spannable == null) {
|
||||
spannable = if (content is Spannable) {
|
||||
content
|
||||
} else {
|
||||
SpannableString(content)
|
||||
}
|
||||
}
|
||||
val start = m.start()
|
||||
val end = m.end()
|
||||
val links = spannable.getSpans(start, end, URLSpan::class.java)
|
||||
fun handleTextUrl(content: Spannable): Spannable {
|
||||
URL_PATTERN.findAll(content).forEach { result ->
|
||||
val start = result.range.first
|
||||
val end = result.range.last + 1
|
||||
val links = content.getSpans(start, end, URLSpan::class.java)
|
||||
if (links.isNotEmpty()) {
|
||||
// There has been URLSpan already, leave it alone
|
||||
continue
|
||||
return@forEach
|
||||
}
|
||||
val urlSpan = URLSpan(m.group(0))
|
||||
spannable.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
val urlSpan = URLSpan(result.groupValues[0])
|
||||
content.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return spannable ?: content
|
||||
return content
|
||||
}
|
||||
}
|
||||
-28
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,10 +43,9 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.statusBarsIgnoringVisibility
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -319,13 +318,12 @@ class ReaderActivity : EhActivity() {
|
||||
dragHandle = null,
|
||||
// Yeah, I know color state should not be read here, but we have to do it...
|
||||
scrimColor = scrim,
|
||||
windowInsets = WindowInsets(0),
|
||||
windowInsets = WindowInsets.statusBarsIgnoringVisibility,
|
||||
) {
|
||||
SettingsPager(modifier = Modifier.fillMaxSize()) { page ->
|
||||
isColorFilter = page == 2
|
||||
setMenuVisibility(!isColorFilter)
|
||||
}
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -650,10 +648,9 @@ class ReaderActivity : EhActivity() {
|
||||
dialogState.dialog {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = { it.cancel() },
|
||||
windowInsets = WindowInsets(0),
|
||||
windowInsets = WindowInsets.statusBarsIgnoringVisibility,
|
||||
) {
|
||||
ReaderPageSheet(page) { it.cancel() }
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@ package eu.kanade.tachiyomi.ui.reader
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FileCopy
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
@@ -47,12 +51,14 @@ fun ReaderPageSheetMeta(
|
||||
Spacer(modifier = Modifier.size(32.dp))
|
||||
Text(text = stringResource(id = text))
|
||||
}
|
||||
Item(icon = Icons.Default.Refresh, text = R.string.refresh, onClick = retry)
|
||||
Item(icon = Icons.Default.Refresh, text = R.string.refresh_original, onClick = retryOrigin)
|
||||
Item(icon = Icons.Default.Share, text = R.string.action_share, onClick = share)
|
||||
Item(icon = Icons.Default.FileCopy, text = R.string.action_copy, onClick = copy)
|
||||
Item(icon = Icons.Default.Save, text = R.string.action_save, onClick = save)
|
||||
Item(icon = Icons.Default.Save, text = R.string.action_save_to, onClick = saveTo)
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState()).navigationBarsPadding()) {
|
||||
Item(icon = Icons.Default.Refresh, text = R.string.refresh, onClick = retry)
|
||||
Item(icon = Icons.Default.Refresh, text = R.string.refresh_original, onClick = retryOrigin)
|
||||
Item(icon = Icons.Default.Share, text = R.string.action_share, onClick = share)
|
||||
Item(icon = Icons.Default.FileCopy, text = R.string.action_copy, onClick = copy)
|
||||
Item(icon = Icons.Default.Save, text = R.string.action_save, onClick = save)
|
||||
Item(icon = Icons.Default.Save, text = R.string.action_save_to, onClick = saveTo)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -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 { AppConfig.matchVariant(it.downloadLink) } ?: 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,10 @@ data class GithubArtifacts(
|
||||
@SerialName("artifacts") val artifacts: List<GithubArtifact>,
|
||||
) {
|
||||
fun getDownloadLink(): String {
|
||||
return (artifacts.find { it.name.contains(apkVariant) } ?: artifacts[0]).downloadLink
|
||||
// The default order is upload order, so we need to sort it
|
||||
return artifacts.sortedBy { it.name }.run {
|
||||
find { AppConfig.matchVariant(it.name) } ?: this[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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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->EH->Etiketlerim yerinden abone olun</string>
|
||||
|
||||
@@ -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>
|
||||
@@ -93,6 +85,7 @@
|
||||
<string name="duplicate_quick_search">已存在相同的快速搜索,名称为“%s”。</string>
|
||||
<string name="duplicate_name">已存在相同名称</string>
|
||||
<string name="save_progress">保存进度</string>
|
||||
<string name="delete_quick_search">删除快速搜索“%s”?</string>
|
||||
<string name="go_to_hint">第 %1$d 页,共 %2$d 页</string>
|
||||
<!-- search_min_rating -->
|
||||
<string name="any">不限</string>
|
||||
@@ -253,6 +246,7 @@
|
||||
<string name="label_text_is_invalid">“默认”是无效标签</string>
|
||||
<string name="label_text_exist">标签已存在</string>
|
||||
<string name="rename_label_title">重命名标签</string>
|
||||
<string name="delete_label">删除标签“%s”?</string>
|
||||
<!-- History -->
|
||||
<string name="no_history">这里是阅读历史</string>
|
||||
<string name="clear_all">全部清除</string>
|
||||
@@ -356,7 +350,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 +456,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>
|
||||
|
||||
@@ -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>
|
||||
@@ -93,6 +85,7 @@
|
||||
<string name="duplicate_quick_search">已存在相同的快速搜尋,名稱為“%s”。</string>
|
||||
<string name="duplicate_name">名稱重複</string>
|
||||
<string name="save_progress">儲存進度</string>
|
||||
<string name="delete_quick_search">刪除快速搜尋“%s”?</string>
|
||||
<string name="go_to_hint">第 %1$d 頁,共 %2$d 頁</string>
|
||||
<!-- search_min_rating -->
|
||||
<string name="any">不限</string>
|
||||
@@ -252,6 +245,7 @@
|
||||
<string name="label_text_is_invalid">“預設”是無效的標籤</string>
|
||||
<string name="label_text_exist">標籤已存在</string>
|
||||
<string name="rename_label_title">重新命名標籤</string>
|
||||
<string name="delete_label">刪除標籤“%s”?</string>
|
||||
<!-- History -->
|
||||
<string name="no_history">歷史紀錄將會顯示在這裡</string>
|
||||
<string name="clear_all">全部清除</string>
|
||||
@@ -355,7 +349,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 +455,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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -133,6 +125,7 @@
|
||||
<string name="duplicate_quick_search">A duplicate quick search exists. The name is \"%s\".</string>
|
||||
<string name="duplicate_name">This name is already in use.</string>
|
||||
<string name="save_progress">Save progress</string>
|
||||
<string name="delete_quick_search">Delete quick search \"%s\"?</string>
|
||||
<string name="go_to_hint">Page %1$d, total %2$d pages</string>
|
||||
<!-- search_min_rating -->
|
||||
<string name="any">Any</string>
|
||||
@@ -295,6 +288,7 @@
|
||||
<string name="label_text_is_invalid">\"Default\" is an invalid label</string>
|
||||
<string name="label_text_exist">Label exists</string>
|
||||
<string name="rename_label_title">Rename label</string>
|
||||
<string name="delete_label">Delete label \"%s\"?</string>
|
||||
<!-- History -->
|
||||
<string name="no_history">Viewed galleries will be shown here</string>
|
||||
<string name="clear_all">Clear all</string>
|
||||
@@ -400,7 +394,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 +521,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>
|
||||
|
||||
@@ -96,9 +96,16 @@ fn parse_token_and_gid(str: &str) -> Option<(i64, String)> {
|
||||
}
|
||||
|
||||
fn parse_uploader_and_pages(str: &str) -> (Option<String>, bool, i32) {
|
||||
let uploader = regex!(r#"<a href="https://e[x-]hentai.org/uploader/.*?">(.*?)</a>"#)
|
||||
.captures(str)
|
||||
.map(|grp| grp[1].to_string());
|
||||
let uploader =
|
||||
regex!(r#"<a href="https://e[x-]hentai.org/uploader/.*?">(.*?)</a>|(\(Disowned\))"#)
|
||||
.captures(str)
|
||||
.map(|grp| {
|
||||
grp.get(1)
|
||||
.or_else(|| grp.get(2))
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.to_string()
|
||||
});
|
||||
let pages = match regex!(r"<div>(\d+) pages</div>").captures(str) {
|
||||
None => 0,
|
||||
Some(grp) => grp[1].parse().unwrap_or(0),
|
||||
|
||||
@@ -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
|
||||
+4
-10
@@ -65,16 +65,10 @@
|
||||
|
||||
# 下载
|
||||
|
||||
| 变种 | 功能 |
|
||||
|-------------|------------------------|
|
||||
| Default | Android 9+, 完全支持 |
|
||||
| Marshmallow | Android 6.0-8.1, 有限支持* |
|
||||
| OSS | FOSS 依赖项** |
|
||||
| GMS | 专有 GMS 组件 |
|
||||
|
||||
\* 可能不支持某些压缩包
|
||||
|
||||
** 在不支持 [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine) 的设备上回退到 OkHttp
|
||||
| 变种 | 功能 |
|
||||
|-------------|-----------------------|
|
||||
| Default | Android 8.0+, 完全支持 |
|
||||
| Marshmallow | Android 6.0-7.1, 有限支持 |
|
||||
|
||||
<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"/>
|
||||
|
||||
+4
-10
@@ -65,16 +65,10 @@
|
||||
|
||||
# 下載
|
||||
|
||||
| 變種 | 功能 |
|
||||
|-------------|------------------------|
|
||||
| Default | Android 9+, 完全支援 |
|
||||
| Marshmallow | Android 6.0-8.1, 有限支援* |
|
||||
| OSS | FOSS 依賴項** |
|
||||
| GMS | 專有 GMS 元件 |
|
||||
|
||||
\* 可能不支援某些壓縮包
|
||||
|
||||
** 在不支援 [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine) 的裝置上回退到 OkHttp
|
||||
| 變種 | 功能 |
|
||||
|-------------|-----------------------|
|
||||
| Default | Android 8.0+, 完全支援 |
|
||||
| Marshmallow | Android 6.0-7.1, 有限支援 |
|
||||
|
||||
<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"/>
|
||||
|
||||
@@ -26,8 +26,7 @@ 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"
|
||||
okio = "3.9.0"
|
||||
splitties = "3.0.0"
|
||||
telephoto = "0.8.0"
|
||||
xmlutil = "0.86.3"
|
||||
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user