Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3158d320b | ||
|
|
38494c9fbc | ||
|
|
114158cf73 | ||
|
|
6d108dd7ff | ||
|
|
f36b7f1dbe | ||
|
|
0a22ebd8e9 | ||
|
|
3682eeaf94 | ||
|
|
7df2ae4ba7 | ||
|
|
c9519ec681 | ||
|
|
b146ed684d | ||
|
|
d2787c36d7 | ||
|
|
3ff663114a | ||
|
|
573e62f310 | ||
|
|
f9af670b82 | ||
|
|
bf461475c6 | ||
|
|
bdea6e0cc1 | ||
|
|
57f0ec4e5d | ||
|
|
d663092363 | ||
|
|
edf6188e36 | ||
|
|
f3f3395e68 | ||
|
|
ac9dc347e3 | ||
|
|
8721d85946 | ||
|
|
a0bd1a8738 | ||
|
|
35fdf3e3b0 | ||
|
|
aced8293f1 | ||
|
|
3f516faad8 | ||
|
|
824f7b9602 | ||
|
|
95aeeaa16f | ||
|
|
63f08f0230 | ||
|
|
3b241fe857 | ||
|
|
75bc104f43 | ||
|
|
30afd56324 | ||
|
|
5ee1bb11a0 | ||
|
|
c1de45abce | ||
|
|
8805033c8d | ||
|
|
0ed59bb8a9 | ||
|
|
8163f2fd28 | ||
|
|
521a65c9d2 | ||
|
|
eb98424668 | ||
|
|
961c731743 | ||
|
|
5188769fb6 | ||
|
|
8f27d9e30f | ||
|
|
b58566999e |
23
.github/workflows/watchdiff.yml
vendored
23
.github/workflows/watchdiff.yml
vendored
@@ -1,23 +0,0 @@
|
||||
# This is a basic workflow that is manually triggered
|
||||
|
||||
name: Watch hitomi.la file changes
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "*/10 * * * *"
|
||||
|
||||
jobs:
|
||||
watchdiff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
ref: watchdiff
|
||||
- name: Download files
|
||||
run: ./fetch.sh
|
||||
- name: Commit and Push
|
||||
id: push
|
||||
run: |
|
||||
git config --global user.name 'Watchdiff bot'
|
||||
git config --global user.email 'tom5079@naver.com'
|
||||
{ git add . && git commit -m "File update" && git push; } | tail -1 | grep -q "nothing to commit"
|
||||
47
.gitignore
vendored
47
.gitignore
vendored
@@ -1,20 +1,33 @@
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log/OS Files
|
||||
*.log
|
||||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.apk
|
||||
output.json
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.idea/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
#Github pages
|
||||
/gh-pages
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
#Private files
|
||||
**/google-services.json
|
||||
**/credentials.json
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
139
.idea/codeStyles/Project.xml
generated
139
.idea/codeStyles/Project.xml
generated
@@ -1,139 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="RIGHT_MARGIN" value="120" />
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="java.util" alias="false" withSubpackages="false" />
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
<package name="io.ktor" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="PACKAGES_IMPORT_LAYOUT">
|
||||
<value>
|
||||
<package name="" alias="false" withSubpackages="true" />
|
||||
<package name="java" alias="false" withSubpackages="true" />
|
||||
<package name="javax" alias="false" withSubpackages="true" />
|
||||
<package name="kotlin" alias="false" withSubpackages="true" />
|
||||
<package name="" alias="true" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
6
.idea/compiler.xml
generated
6
.idea/compiler.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/copyright/GPL.xml
generated
6
.idea/copyright/GPL.xml
generated
@@ -1,6 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value=" Pupil, Hitomi.la viewer for Android Copyright (C) &#36;today.year tom5079 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>." />
|
||||
<option name="myName" value="GPL" />
|
||||
</copyright>
|
||||
</component>
|
||||
7
.idea/copyright/profiles_settings.xml
generated
7
.idea/copyright/profiles_settings.xml
generated
@@ -1,7 +0,0 @@
|
||||
<component name="CopyrightManager">
|
||||
<settings>
|
||||
<module2copyright>
|
||||
<element module="Project Files" copyright="GPL" />
|
||||
</module2copyright>
|
||||
</settings>
|
||||
</component>
|
||||
28
.idea/deploymentTargetDropDown.xml
generated
28
.idea/deploymentTargetDropDown.xml
generated
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<runningDeviceTargetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="RUNNING_DEVICE_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Pixel_2_API_31.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</runningDeviceTargetSelectedWithDropDown>
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Pixel_2_API_31.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2022-02-01T02:15:22.286886Z" />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/dictionaries/tom50.xml
generated
7
.idea/dictionaries/tom50.xml
generated
@@ -1,7 +0,0 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="tom50">
|
||||
<words>
|
||||
<w>hitomi</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
4
.idea/encodings.xml
generated
4
.idea/encodings.xml
generated
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||
</project>
|
||||
22
.idea/gradle.xml
generated
22
.idea/gradle.xml
generated
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="/usr/share/java/gradle" />
|
||||
<option name="gradleJvm" value="11" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="AndroidLintNewerVersionAvailable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
100
.idea/jarRepositories.xml
generated
100
.idea/jarRepositories.xml
generated
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven2" />
|
||||
<option name="name" value="maven2" />
|
||||
<option name="url" value="http://guardian.github.com/maven/repo-releases" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="BintrayJCenter" />
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven" />
|
||||
<option name="name" value="maven" />
|
||||
<option name="url" value="https://jitpack.io" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="Google" />
|
||||
<option name="name" value="Google" />
|
||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenRepo" />
|
||||
<option name="name" value="MavenRepo" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven2" />
|
||||
<option name="name" value="maven2" />
|
||||
<option name="url" value="https://guardian.github.com/maven/repo-releases" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven3" />
|
||||
<option name="name" value="maven3" />
|
||||
<option name="url" value="https://s3.amazonaws.com/fabric-artifacts-private/internal-snapshots" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven4" />
|
||||
<option name="name" value="maven4" />
|
||||
<option name="url" value="https://maven.fabric.io/public" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenLocal" />
|
||||
<option name="name" value="MavenLocal" />
|
||||
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenLocal" />
|
||||
<option name="name" value="MavenLocal" />
|
||||
<option name="url" value="file:/$USER_HOME$/.m2/repository" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven3" />
|
||||
<option name="name" value="maven3" />
|
||||
<option name="url" value="https://dl.bintray.com/tom5079/maven" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven3" />
|
||||
<option name="name" value="maven3" />
|
||||
<option name="url" value="http://dl.bintray.com/piasy/maven" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven2" />
|
||||
<option name="name" value="maven2" />
|
||||
<option name="url" value="https://guardian.github.io/maven/repo-releases/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenLocal" />
|
||||
<option name="name" value="MavenLocal" />
|
||||
<option name="url" value="file:$USER_HOME$/.m2/repository/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven2" />
|
||||
<option name="name" value="maven2" />
|
||||
<option name="url" value="https://oss.sonatype.org/content/repositories/snapshots" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven3" />
|
||||
<option name="name" value="maven3" />
|
||||
<option name="url" value="https://maven.mozilla.org/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven3" />
|
||||
<option name="name" value="maven3" />
|
||||
<option name="url" value="https://oss.sonatype.org/content/repositories/snapshots/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/kotlinCodeInsightSettings.xml
generated
7
.idea/kotlinCodeInsightSettings.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinCodeInsightWorkspaceSettings">
|
||||
<option name="addUnambiguousImportsOnTheFly" value="true" />
|
||||
<option name="optimizeImportsOnTheFly" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/kotlinc.xml
generated
6
.idea/kotlinc.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
</component>
|
||||
</project>
|
||||
20
.idea/misc.xml
generated
20
.idea/misc.xml
generated
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DesignSurface">
|
||||
<option name="filePathToZoomLevelMap">
|
||||
<map>
|
||||
<entry key="../../../../layout/custom_preview.xml" value="0.2564814814814815" />
|
||||
<entry key="app/src/main/res/layout/reader_activity.xml" value="0.14351851851851852" />
|
||||
<entry key="app/src/main/res/xml/lock_preferences.xml" value="0.5119791666666667" />
|
||||
<entry key="app/src/main/res/xml/manage_storage_preferences.xml" value="0.2604166666666667" />
|
||||
<entry key="app/src/main/res/xml/root_preferences.xml" value="0.5119791666666667" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
3
.idea/scopes/Pupil.xml
generated
3
.idea/scopes/Pupil.xml
generated
@@ -1,3 +0,0 @@
|
||||
<component name="DependencyValidationManager">
|
||||
<scope name="Pupil" pattern="file[app]:*/" />
|
||||
</component>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -2,7 +2,7 @@
|
||||
*Pupil, Hitomi.la viewer for Android*
|
||||
|
||||

|
||||
[](https://github.com/tom5079/Pupil/releases/download/5.2.23/Pupil-v5.2.23.apk)
|
||||
[](https://github.com/tom5079/Pupil/releases/download/5.3.11/Pupil-v5.3.11.apk)
|
||||
[](https://discord.gg/Stj4b5v)
|
||||
|
||||
# Features
|
||||
|
||||
@@ -32,13 +32,13 @@ configurations {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
defaultConfig {
|
||||
applicationId "xyz.quaver.pupil"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 31
|
||||
compileSdk 34
|
||||
targetSdkVersion 34
|
||||
versionCode 69
|
||||
versionName "5.3.0"
|
||||
versionName "5.3.11"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
@@ -79,13 +79,14 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.4.1"
|
||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.4.0"
|
||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.4.1"
|
||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
|
||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||
@@ -94,15 +95,15 @@ dependencies {
|
||||
|
||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||
|
||||
implementation "com.google.android.material:material:1.5.0"
|
||||
implementation "com.google.android.material:material:1.11.0"
|
||||
|
||||
implementation platform('com.google.firebase:firebase-bom:29.0.3')
|
||||
implementation platform('com.google.firebase:firebase-bom:32.7.0')
|
||||
implementation "com.google.firebase:firebase-analytics-ktx"
|
||||
implementation "com.google.firebase:firebase-crashlytics-ktx"
|
||||
implementation "com.google.firebase:firebase-perf-ktx"
|
||||
|
||||
implementation "com.google.android.gms:play-services-oss-licenses:17.0.0"
|
||||
implementation "com.google.android.gms:play-services-mlkit-face-detection:17.0.0"
|
||||
implementation "com.google.android.gms:play-services-oss-licenses:17.0.1"
|
||||
implementation "com.google.android.gms:play-services-mlkit-face-detection:17.1.0"
|
||||
|
||||
implementation "com.github.clans:fab:1.6.4"
|
||||
|
||||
@@ -128,15 +129,11 @@ dependencies {
|
||||
|
||||
implementation "org.jsoup:jsoup:1.14.3"
|
||||
|
||||
implementation ("app.cash.zipline:zipline:1.0.0-SNAPSHOT") {
|
||||
exclude group: "com.squareup.okio", module: "okio"
|
||||
}
|
||||
|
||||
implementation "xyz.quaver:documentfilex:0.7.2"
|
||||
implementation "xyz.quaver:floatingsearchview:1.1.7"
|
||||
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.3"
|
||||
androidTestImplementation "androidx.test:rules:1.4.0"
|
||||
androidTestImplementation "androidx.test:runner:1.4.0"
|
||||
|
||||
1
app/proguard-rules.pro
vendored
1
app/proguard-rules.pro
vendored
@@ -34,3 +34,4 @@
|
||||
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
||||
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
||||
-keep class xyz.quaver.pupil.** { *; }
|
||||
-keep class app.cash.zipline.** { *; }
|
||||
@@ -12,7 +12,7 @@
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 69,
|
||||
"versionName": "5.3.0",
|
||||
"versionName": "5.3.11",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -102,19 +102,19 @@ class ExampleInstrumentedTest {
|
||||
Log.d("PUPILD", r.take(10).toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getBlock() {
|
||||
val galleryBlock = getGalleryBlock(2097576)
|
||||
|
||||
print(galleryBlock)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getGallery() {
|
||||
val gallery = getGallery(2097751)
|
||||
|
||||
print(gallery)
|
||||
}
|
||||
// @Test
|
||||
// fun test_getBlock() {
|
||||
// val galleryBlock = getGalleryBlock(2097576)
|
||||
//
|
||||
// print(galleryBlock)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun test_getGallery() {
|
||||
// val gallery = getGallery(2097751)
|
||||
//
|
||||
// print(gallery)
|
||||
// }
|
||||
|
||||
@Test
|
||||
fun test_getGalleryInfo() {
|
||||
@@ -125,42 +125,44 @@ class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
fun test_getReader() {
|
||||
val reader = getGalleryInfo(1722144)
|
||||
val reader = getGalleryInfo(2128654)
|
||||
|
||||
print(reader)
|
||||
Log.d("PUPILD", reader.toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_getImages() {
|
||||
val galleryID = 2099306
|
||||
fun test_getImages() { runBlocking {
|
||||
val galleryID = 2128654
|
||||
|
||||
val images = getGalleryInfo(galleryID).files.map {
|
||||
imageUrlFromImage(galleryID, it,false)
|
||||
}
|
||||
|
||||
images.forEachIndexed { index, image ->
|
||||
println("Testing $index/${images.size}: $image")
|
||||
val response = client.newCall(
|
||||
Request.Builder()
|
||||
.url(image)
|
||||
.header("Referer", "https://hitomi.la/")
|
||||
.build()
|
||||
).execute()
|
||||
Log.d("PUPILD", images.toString())
|
||||
|
||||
assertEquals(200, response.code())
|
||||
// images.forEachIndexed { index, image ->
|
||||
// println("Testing $index/${images.size}: $image")
|
||||
// val response = client.newCall(
|
||||
// Request.Builder()
|
||||
// .url(image)
|
||||
// .header("Referer", "https://hitomi.la/")
|
||||
// .build()
|
||||
// ).execute()
|
||||
//
|
||||
// assertEquals(200, response.code())
|
||||
//
|
||||
// println("$index/${images.size} Passed")
|
||||
// }
|
||||
} }
|
||||
|
||||
println("$index/${images.size} Passed")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_urlFromUrlFromHash() {
|
||||
val url = urlFromUrlFromHash(1531795, GalleryFiles(
|
||||
212, "719d46a7556be0d0021c5105878507129b5b3308b02cf67f18901b69dbb3b5ef", 1, "00.jpg", 300
|
||||
), "webp")
|
||||
|
||||
print(url)
|
||||
}
|
||||
// @Test
|
||||
// fun test_urlFromUrlFromHash() {
|
||||
// val url = urlFromUrlFromHash(1531795, GalleryFiles(
|
||||
// 212, "719d46a7556be0d0021c5105878507129b5b3308b02cf67f18901b69dbb3b5ef", 1, "00.jpg", 300
|
||||
// ), "webp")
|
||||
//
|
||||
// print(url)
|
||||
// }
|
||||
|
||||
// @Test
|
||||
// suspend fun test_doSearch_extreme() {
|
||||
@@ -173,9 +175,9 @@ class ExampleInstrumentedTest {
|
||||
// print(doSearch("-male:yaoi -female:yaoi -female:loli").size)
|
||||
// }
|
||||
|
||||
@Test
|
||||
fun test_subdomainFromUrl() {
|
||||
val galleryInfo = getGalleryInfo(1929109).files[2]
|
||||
print(urlFromUrlFromHash(1929109, galleryInfo, "webp", null, "a"))
|
||||
}
|
||||
// @Test
|
||||
// fun test_subdomainFromUrl() {
|
||||
// val galleryInfo = getGalleryInfo(1929109).files[2]
|
||||
// print(urlFromUrlFromHash(1929109, galleryInfo, "webp", null, "a"))
|
||||
// }
|
||||
}
|
||||
@@ -11,6 +11,8 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
@@ -45,7 +47,8 @@
|
||||
</provider>
|
||||
|
||||
<service android:name=".services.DownloadService"
|
||||
android:exported="false"/>
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse" />
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.UpdateBroadcastReceiver"
|
||||
@@ -61,165 +64,107 @@
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:parentActivityName=".ui.MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/galleries"
|
||||
android:scheme="http" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="*.hasha.in"/>
|
||||
<data android:pathPrefix="/reader"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/manga"
|
||||
android:scheme="http" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="hitomi.la"/>
|
||||
<data android:pathPrefix="/galleries"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/doujinshi"
|
||||
android:scheme="http" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="hitomi.la" />
|
||||
<data android:pathPrefix="/manga" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/cg"
|
||||
android:scheme="http" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="hitomi.la" />
|
||||
<data android:pathPrefix="/doujinshi" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/reader"
|
||||
android:scheme="http" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="hitomi.la" />
|
||||
<data android:pathPrefix="/cg" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/galleries"
|
||||
android:scheme="https" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="hitomi.la" />
|
||||
<data android:pathPrefix="/imageset" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/manga"
|
||||
android:scheme="https" />
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="hitomi.la" />
|
||||
<data android:pathPrefix="/reader" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/doujinshi"
|
||||
android:scheme="https" />
|
||||
<data android:scheme="http" />
|
||||
<data android:host="e-hentai.org" />
|
||||
<data android:pathPrefix="/g" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/cg"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hitomi.la"
|
||||
android:pathPrefix="/reader"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hiyobi.me"
|
||||
android:scheme="http"
|
||||
android:pathPrefix="/reader" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="hiyobi.me"
|
||||
android:pathPrefix="/reader"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="e-hentai.org"
|
||||
android:pathPrefix="/g"
|
||||
android:scheme="http" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="e-hentai.org"
|
||||
android:pathPrefix="/g"
|
||||
android:scheme="https" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="e-hentai.org" />
|
||||
<data android:pathPrefix="/g" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.SettingsActivity"
|
||||
android:label="@string/settings_title">
|
||||
<tools:validation testUrl="http://ix.io/eer" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
@@ -232,17 +177,6 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:scheme="http"
|
||||
android:host="ix.io"
|
||||
android:pathPattern="/..*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
||||
</application>
|
||||
|
||||
@@ -30,7 +30,6 @@ import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import app.cash.zipline.QuickJs
|
||||
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory
|
||||
import com.github.piasy.biv.BigImageViewer
|
||||
import com.github.piasy.biv.loader.fresco.FrescoImageLoader
|
||||
@@ -51,9 +50,14 @@ import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.util.*
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
||||
@@ -77,45 +81,33 @@ val client: OkHttpClient
|
||||
clientHolder = it
|
||||
}
|
||||
|
||||
private var version = ""
|
||||
var runtimeReady = false
|
||||
private set
|
||||
lateinit var runtime: QuickJs
|
||||
private set
|
||||
fun getSSLContext(context: Context): SSLContext {
|
||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||
keyStore.load(null, null)
|
||||
|
||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
val certificate = context.resources.openRawResource(R.raw.isrgrootx1).use {
|
||||
certificateFactory.generateCertificate(it)
|
||||
}
|
||||
|
||||
keyStore.setCertificateEntry("isrgrootx1", certificate)
|
||||
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
trustManagerFactory.init(keyStore)
|
||||
|
||||
val sslContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(null, trustManagerFactory.trustManagers, SecureRandom())
|
||||
|
||||
return sslContext
|
||||
}
|
||||
|
||||
class Pupil : Application() {
|
||||
|
||||
companion object {
|
||||
lateinit var instance: Pupil
|
||||
private set
|
||||
}
|
||||
|
||||
init {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
withContext(evaluationContext) {
|
||||
runtime = QuickJs.create()
|
||||
}
|
||||
while (true) {
|
||||
kotlin.runCatching {
|
||||
val newVersion = URL("https://tom5079.github.io/PupilSources/hitomi.html.ver").readText()
|
||||
|
||||
if (version != newVersion) {
|
||||
runtimeReady = false
|
||||
version = newVersion
|
||||
evaluationContext.cancelChildren()
|
||||
withContext(evaluationContext) {
|
||||
Log.d("PUPILD", "UPDATE!")
|
||||
runtime.evaluate(URL("https://tom5079.github.io/PupilSources/assets/js/gg.js").readText())
|
||||
runtimeReady = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delay(10000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
instance = this
|
||||
|
||||
@@ -134,7 +126,8 @@ class Pupil : Application() {
|
||||
val proxyInfo = getProxyInfo()
|
||||
|
||||
clientBuilder = OkHttpClient.Builder()
|
||||
.connectTimeout(0, TimeUnit.SECONDS)
|
||||
// .connectTimeout(0, TimeUnit.SECONDS)
|
||||
.sslSocketFactory(getSSLContext(this).socketFactory)
|
||||
.readTimeout(0, TimeUnit.SECONDS)
|
||||
.proxyInfo(proxyInfo)
|
||||
.addInterceptor { chain ->
|
||||
|
||||
@@ -16,19 +16,24 @@
|
||||
|
||||
package xyz.quaver.pupil.hitomi
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.datetime.Clock.System.now
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.runtime
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
const val protocol = "https:"
|
||||
|
||||
@@ -84,11 +89,11 @@ data class GalleryInfo(
|
||||
val groups: List<Group>? = null,
|
||||
val parodys: List<Parody>? = null,
|
||||
val tags: List<Tag>? = null,
|
||||
val related: List<Int>,
|
||||
val languages: List<Language>,
|
||||
val related: List<Int> = emptyList(),
|
||||
val languages: List<Language> = emptyList(),
|
||||
val characters: List<Character>? = null,
|
||||
val scene_indexes: List<Int>,
|
||||
val files: List<GalleryFiles>
|
||||
val scene_indexes: List<Int>? = emptyList(),
|
||||
val files: List<GalleryFiles> = emptyList()
|
||||
)
|
||||
|
||||
val json = Json {
|
||||
@@ -130,19 +135,76 @@ const val galleryblockextension = ".html"
|
||||
const val galleryblockdir = "galleryblock"
|
||||
const val nozomiextension = ".nozomi"
|
||||
|
||||
val evaluationContext = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + Job()
|
||||
val evaluationContext = Dispatchers.Main + Job()
|
||||
|
||||
object gg {
|
||||
private var lastRetrieval: Long? = null
|
||||
|
||||
suspend fun m(g: Int): Int = withContext(evaluationContext) {
|
||||
runtime.evaluate("gg.m($g)").toString().toInt()
|
||||
}
|
||||
suspend fun b(): String = withContext(evaluationContext) {
|
||||
runtime.evaluate("gg.b").toString()
|
||||
private val mutex = Mutex()
|
||||
|
||||
private var mDefault = 0
|
||||
private val mMap = mutableMapOf<Int, Int>()
|
||||
|
||||
private var b = ""
|
||||
|
||||
@OptIn(ExperimentalTime::class, ExperimentalCoroutinesApi::class)
|
||||
private suspend fun refresh() = withContext(Dispatchers.IO) {
|
||||
mutex.withLock {
|
||||
if (lastRetrieval == null || (lastRetrieval!! + 60000) < System.currentTimeMillis()) {
|
||||
val ggjs: String = suspendCancellableCoroutine { continuation ->
|
||||
val call = client.newCall(Request.Builder().url("https://ltn.hitomi.la/gg.js").build())
|
||||
|
||||
call.enqueue(object: Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
if (continuation.isCancelled) return
|
||||
continuation.resumeWithException(e)
|
||||
}
|
||||
|
||||
suspend fun s(h: String): String = withContext(evaluationContext) {
|
||||
runtime.evaluate("gg.s('$h')").toString()
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (!call.isCanceled) {
|
||||
response.body()?.use {
|
||||
continuation.resume(it.string()) {
|
||||
call.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
call.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
mDefault = Regex("var o = (\\d)").find(ggjs)!!.groupValues[1].toInt()
|
||||
val o = Regex("o = (\\d); break;").find(ggjs)!!.groupValues[1].toInt()
|
||||
|
||||
mMap.clear()
|
||||
Regex("case (\\d+):").findAll(ggjs).forEach {
|
||||
val case = it.groupValues[1].toInt()
|
||||
mMap[case] = o
|
||||
}
|
||||
|
||||
b = Regex("b: '(.+)'").find(ggjs)!!.groupValues[1]
|
||||
|
||||
lastRetrieval = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun m(g: Int): Int {
|
||||
refresh()
|
||||
|
||||
return mMap[g] ?: mDefault
|
||||
}
|
||||
|
||||
suspend fun b(): String {
|
||||
refresh()
|
||||
return b
|
||||
}
|
||||
fun s(h: String): String {
|
||||
val m = Regex("(..)(.)$").find(h)
|
||||
return m!!.groupValues.let { it[2]+it[1] }.toInt(16).toString(10)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,14 +259,15 @@ suspend fun rewriteTnPaths(html: String) {
|
||||
}
|
||||
|
||||
suspend fun imageUrlFromImage(galleryID: Int, image: GalleryFiles, noWebp: Boolean) : String {
|
||||
return when {
|
||||
noWebp ->
|
||||
urlFromUrlFromHash(galleryID, image)
|
||||
// image.hasavif != 0 ->
|
||||
// urlFromUrlFromHash(galleryID, image, "avif", null, "a")
|
||||
image.haswebp != 0 ->
|
||||
urlFromUrlFromHash(galleryID, image, "webp", null, "a")
|
||||
else ->
|
||||
urlFromUrlFromHash(galleryID, image)
|
||||
}
|
||||
return urlFromUrlFromHash(galleryID, image, "webp", null, "a")
|
||||
// return when {
|
||||
// noWebp ->
|
||||
// urlFromUrlFromHash(galleryID, image)
|
||||
//// image.hasavif != 0 ->
|
||||
//// urlFromUrlFromHash(galleryID, image, "avif", null, "a")
|
||||
// image.haswebp != 0 ->
|
||||
// urlFromUrlFromHash(galleryID, image, "webp", null, "a")
|
||||
// else ->
|
||||
// urlFromUrlFromHash(galleryID, image)
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ data class GalleryBlock(
|
||||
val type: String,
|
||||
val language: String,
|
||||
val relatedTags: List<String>,
|
||||
val groups: List<String>
|
||||
val groups: List<String> = emptyList()
|
||||
)
|
||||
|
||||
suspend fun getGalleryBlock(galleryID: Int) : GalleryBlock {
|
||||
|
||||
@@ -166,11 +166,7 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : Set<
|
||||
else -> "$protocol//$domain/$compressed_nozomi_prefix/$area/$tag-$language$nozomiextension"
|
||||
}
|
||||
|
||||
val bytes = try {
|
||||
URL(nozomiAddress).readBytes()
|
||||
} catch (e: Exception) {
|
||||
return emptySet()
|
||||
}
|
||||
val bytes = URL(nozomiAddress).readBytes()
|
||||
|
||||
val nozomi = mutableSetOf<Int>()
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
@@ -100,13 +101,15 @@ class DownloadService : Service() {
|
||||
notify(galleryID)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@SuppressLint("RestrictedApi", "MissingPermission")
|
||||
private fun notify(galleryID: Int) {
|
||||
val max = progress[galleryID]?.size ?: 0
|
||||
val progress = progress[galleryID]?.count { it == Float.POSITIVE_INFINITY } ?: 0
|
||||
|
||||
val notification = notification[galleryID] ?: return
|
||||
|
||||
if (!checkNotificationEnabled(this)) return
|
||||
|
||||
if (isCompleted(galleryID)) {
|
||||
notification
|
||||
.setContentText(getString(R.string.reader_notification_complete))
|
||||
@@ -168,19 +171,26 @@ class DownloadService : Service() {
|
||||
private val interceptor: PupilInterceptor = { chain ->
|
||||
val request = chain.request()
|
||||
|
||||
var response = chain.proceed(request)
|
||||
var limit = 5
|
||||
var response = kotlin.runCatching {
|
||||
chain.proceed(request)
|
||||
}.getOrNull()
|
||||
var limit = 10
|
||||
|
||||
while (!response.isSuccessful) {
|
||||
if (response.code() == 503) {
|
||||
while (response?.isSuccessful != true) {
|
||||
if (response?.code() == 503) {
|
||||
Thread.sleep(200)
|
||||
} else if (--limit > 0)
|
||||
} else if (--limit < 0)
|
||||
break
|
||||
|
||||
response = chain.proceed(request)
|
||||
response = kotlin.runCatching {
|
||||
chain.proceed(request)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
response.newBuilder()
|
||||
if (response == null)
|
||||
response = chain.proceed(request)
|
||||
|
||||
response!!.newBuilder()
|
||||
.body(response.body()?.let {
|
||||
ProgressResponseBody(request.tag(), it, progressListener)
|
||||
}).build()
|
||||
@@ -207,6 +217,7 @@ class DownloadService : Service() {
|
||||
private val callback = object: Callback {
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
Log.d("PUPILD", "ONFAILURE ${call.request().tag()}, ${e}")
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
|
||||
if (e.message?.contains("cancel", true) == false) {
|
||||
@@ -215,20 +226,17 @@ class DownloadService : Service() {
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
Log.d("PUPILD", "ONRESPONSE ${call.request().tag()}")
|
||||
val (galleryID, index, startId) = call.request().tag() as Tag
|
||||
val ext = call.request().url().encodedPath().split('.').last()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
runCatching {
|
||||
response.also {
|
||||
if (it.code() != 200) throw IOException(
|
||||
"$galleryID $index ${response.request().url()} CODE ${it.code()}"
|
||||
)
|
||||
}.body()?.use {
|
||||
val image = response.also { if (it.code() != 200) throw IOException( "$galleryID $index ${response.request().url()} CODE ${it.code()}" ) }.body()?.use { it.bytes() } ?: throw Exception("Response null")
|
||||
val padding = ceil(progress[galleryID]?.size?.let { log10(it.toFloat()) } ?: 0F).toInt()
|
||||
|
||||
Cache.getInstance(this@DownloadService, galleryID)
|
||||
.putImage(index, "${index.toString().padStart(padding, '0')}.$ext", it.byteStream())
|
||||
.putImage(index, "${index.toString().padStart(padding, '0')}.$ext", image)
|
||||
|
||||
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
||||
notify(galleryID)
|
||||
@@ -241,7 +249,6 @@ class DownloadService : Service() {
|
||||
|
||||
startId?.let { stopSelf(it) }
|
||||
}
|
||||
} ?: throw Exception("Response null")
|
||||
}.onFailure {
|
||||
FirebaseCrashlytics.getInstance().recordException(it)
|
||||
}
|
||||
@@ -329,6 +336,7 @@ class DownloadService : Service() {
|
||||
}
|
||||
|
||||
if (isCompleted(galleryID)) {
|
||||
if (DownloadManager.getInstance(this@DownloadService).getDownloadFolder(galleryID) != null)
|
||||
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
||||
|
||||
notificationManager.cancel(galleryID)
|
||||
@@ -398,7 +406,11 @@ class DownloadService : Service() {
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
|
||||
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
||||
} else {
|
||||
startForeground(R.id.downloader_notification_id, serviceNotification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
}
|
||||
|
||||
when (intent?.getStringExtra(KEY_COMMAND)) {
|
||||
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
||||
@@ -419,7 +431,11 @@ class DownloadService : Service() {
|
||||
override fun onBind(p0: Intent?) = binder
|
||||
|
||||
override fun onCreate() {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
|
||||
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
||||
} else {
|
||||
startForeground(R.id.downloader_notification_id, serviceNotification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
|
||||
}
|
||||
interceptors[Tag::class] = interceptor
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
|
||||
package xyz.quaver.pupil.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -31,7 +33,9 @@ import android.view.View
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
@@ -58,10 +62,12 @@ import xyz.quaver.pupil.ui.view.MainView
|
||||
import xyz.quaver.pupil.ui.view.ProgressCard
|
||||
import xyz.quaver.pupil.util.ItemClickSupport
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.requestNotificationPermission
|
||||
import xyz.quaver.pupil.util.checkUpdate
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import xyz.quaver.pupil.util.restore
|
||||
import xyz.quaver.pupil.util.showNotificationPermissionExplanationDialog
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
@@ -107,6 +113,12 @@ class MainActivity :
|
||||
|
||||
private lateinit var binding: MainActivityBinding
|
||||
|
||||
private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||
if (!isGranted) {
|
||||
showNotificationPermissionExplanationDialog(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = MainActivityBinding.inflate(layoutInflater)
|
||||
@@ -118,12 +130,14 @@ class MainActivity :
|
||||
onFailure = {
|
||||
Snackbar.make(binding.contents.recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
||||
}, onSuccess = {
|
||||
Snackbar.make(binding.contents.recyclerview, getString(R.string.settings_restore_success, it.size), Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(binding.contents.recyclerview, getString(R.string.settings_restore_success, it), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
requestNotificationPermission(this, requestNotificationPermssionLauncher, false) {}
|
||||
|
||||
if (Preferences["download_folder", ""].isEmpty())
|
||||
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
||||
|
||||
@@ -392,6 +406,10 @@ class MainActivity :
|
||||
onDownloadClickedHandler = { position ->
|
||||
val galleryID = galleries[position]
|
||||
|
||||
requestNotificationPermission(
|
||||
this@MainActivity,
|
||||
requestNotificationPermssionLauncher
|
||||
) {
|
||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
||||
DownloadService.cancel(this@MainActivity, galleryID)
|
||||
}
|
||||
@@ -399,6 +417,7 @@ class MainActivity :
|
||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||
DownloadService.download(this@MainActivity, galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
closeAllItems()
|
||||
}
|
||||
|
||||
@@ -57,9 +57,12 @@ import xyz.quaver.pupil.favorites
|
||||
import xyz.quaver.pupil.services.DownloadService
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.camera
|
||||
import xyz.quaver.pupil.util.checkNotificationEnabled
|
||||
import xyz.quaver.pupil.util.closeCamera
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import xyz.quaver.pupil.util.requestNotificationPermission
|
||||
import xyz.quaver.pupil.util.showNotificationPermissionExplanationDialog
|
||||
import xyz.quaver.pupil.util.startCamera
|
||||
|
||||
class ReaderActivity : BaseActivity() {
|
||||
@@ -117,6 +120,12 @@ class ReaderActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ReaderActivityBinding
|
||||
|
||||
private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||
if (!isGranted) {
|
||||
showNotificationPermissionExplanationDialog(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ReaderActivityBinding.inflate(layoutInflater)
|
||||
@@ -148,10 +157,11 @@ class ReaderActivity : BaseActivity() {
|
||||
val uri = intent.data
|
||||
val lastPathSegment = uri?.lastPathSegment
|
||||
if (uri != null && lastPathSegment != null) {
|
||||
galleryID = when (uri.host) {
|
||||
galleryID = if (uri.host?.endsWith("hasha.in") == true) {
|
||||
lastPathSegment?.toInt() ?: 0
|
||||
} else when (uri.host) {
|
||||
"hitomi.la" ->
|
||||
Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1)?.toIntOrNull() ?: 0
|
||||
"hiyobi.me" -> lastPathSegment.toInt()
|
||||
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
||||
else -> 0
|
||||
}
|
||||
@@ -360,6 +370,10 @@ class ReaderActivity : BaseActivity() {
|
||||
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
||||
|
||||
setOnClickListener {
|
||||
requestNotificationPermission(
|
||||
this@ReaderActivity,
|
||||
requestNotificationPermssionLauncher
|
||||
) {
|
||||
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
|
||||
|
||||
if (downloadManager.isDownloading(galleryID)) {
|
||||
@@ -372,6 +386,7 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(binding.retryFab) {
|
||||
setImageResource(R.drawable.refresh)
|
||||
|
||||
@@ -18,25 +18,72 @@
|
||||
|
||||
package xyz.quaver.pupil.ui.fragment
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import okhttp3.*
|
||||
import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.readText
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.favoriteTags
|
||||
import xyz.quaver.pupil.favorites
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.util.get
|
||||
import xyz.quaver.pupil.util.restore
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private val requestBackupFileLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode != Activity.RESULT_OK) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val uri = result.data?.data ?: return@registerForActivityResult
|
||||
val context = context ?: return@registerForActivityResult
|
||||
val view = view ?: return@registerForActivityResult
|
||||
|
||||
val backupData = runCatching {
|
||||
FileX(context, uri).readText()?.let { Json.parseToJsonElement(it) }
|
||||
}.getOrNull() ?: run{
|
||||
Snackbar.make(view, context.getString(R.string.error), Toast.LENGTH_LONG).show()
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val newFavorites = backupData["favorites"]?.let { Json.decodeFromJsonElement<List<Int>>(it) }.orEmpty()
|
||||
val newFavoriteTags = backupData["favorite_tags"]?.let { Json.decodeFromJsonElement<List<Tag>>(it) }.orEmpty()
|
||||
|
||||
favorites.addAll(newFavorites)
|
||||
favoriteTags.addAll(newFavoriteTags)
|
||||
|
||||
Snackbar.make(view, context.getString(R.string.settings_restore_success, newFavorites.size + newFavoriteTags.size), Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.manage_favorites_preferences, rootKey)
|
||||
|
||||
@@ -47,57 +94,44 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
||||
val context = context ?: return
|
||||
|
||||
findPreference<Preference>("backup")?.setOnPreferenceClickListener {
|
||||
val request = Request.Builder()
|
||||
.url(context.getString(R.string.backup_url))
|
||||
.post(
|
||||
FormBody.Builder()
|
||||
.add("f:1", File(ContextCompat.getDataDir(context), "favorites.json").readText())
|
||||
.build()
|
||||
).build()
|
||||
val favorites = runCatching {
|
||||
Json.parseToJsonElement(File(ContextCompat.getDataDir(context), "favorites.json").readText())
|
||||
}.getOrNull()
|
||||
val favoriteTags = kotlin.runCatching {
|
||||
Json.parseToJsonElement(File(ContextCompat.getDataDir(context), "favorites_tags.json").readText())
|
||||
}.getOrNull()
|
||||
|
||||
client.newCall(request).enqueue(object: Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
val view = view ?: return
|
||||
Snackbar.make(view, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
||||
val favoriteJson = buildJsonObject {
|
||||
favorites?.let {
|
||||
put("favorites", it)
|
||||
}
|
||||
favoriteTags?.let {
|
||||
put("favorite_tags", it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (response.code() != 200) {
|
||||
response.close()
|
||||
return
|
||||
val backupFile = File(context.filesDir, "pupil-backup.json").also {
|
||||
it.writeText(favoriteJson.toString())
|
||||
}
|
||||
|
||||
Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", ""))
|
||||
val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", backupFile)
|
||||
setDataAndType(uri, "application/json")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
}.let {
|
||||
getContext()?.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share)))
|
||||
context.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share)))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
true
|
||||
}
|
||||
findPreference<Preference>("restore")?.setOnPreferenceClickListener {
|
||||
val editText = EditText(context).apply {
|
||||
setText(context.getString(R.string.backup_url), TextView.BufferType.EDITABLE)
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "*/*"
|
||||
}
|
||||
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.settings_restore_title)
|
||||
.setView(editText)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
restore(editText.text.toString(),
|
||||
onFailure = onFailure@{
|
||||
val view = view ?: return@onFailure
|
||||
Snackbar.make(view, R.string.settings_restore_failed, Snackbar.LENGTH_LONG).show()
|
||||
}, onSuccess = onSuccess@{
|
||||
val view = view ?: return@onSuccess
|
||||
Snackbar.make(view, context.getString(R.string.settings_restore_success, it.size), Snackbar.LENGTH_LONG).show()
|
||||
})
|
||||
}.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
// Do Nothing
|
||||
}.show()
|
||||
requestBackupFileLauncher.launch(intent)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import xyz.quaver.io.util.readText
|
||||
import xyz.quaver.io.util.writeText
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.histories
|
||||
import xyz.quaver.pupil.hitomi.json
|
||||
import xyz.quaver.pupil.util.byteToString
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
@@ -60,12 +61,10 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
||||
initPreferences()
|
||||
}
|
||||
|
||||
override fun onPreferenceClick(preference: Preference?): Boolean {
|
||||
override fun onPreferenceClick(preference: Preference): Boolean {
|
||||
val context = context ?: return false
|
||||
|
||||
with(preference) {
|
||||
this ?: return false
|
||||
|
||||
when (key) {
|
||||
"delete_cache" -> {
|
||||
val dir = File(context.cacheDir, "imageCache")
|
||||
@@ -118,7 +117,9 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
||||
if (!metadataFile.exists()) return@forEach
|
||||
|
||||
val metadata = metadataFile.readText()?.let {
|
||||
Json.decodeFromString<Metadata>(it)
|
||||
runCatching {
|
||||
json.decodeFromString<Metadata>(it)
|
||||
}.getOrNull()
|
||||
} ?: return@forEach
|
||||
|
||||
val galleryID = metadata.galleryBlock?.id ?: metadata.galleryInfo?.id?.toIntOrNull() ?: return@forEach
|
||||
|
||||
@@ -80,10 +80,8 @@ class SettingsFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceClick(preference: Preference?): Boolean {
|
||||
override fun onPreferenceClick(preference: Preference): Boolean {
|
||||
with (preference) {
|
||||
this ?: return false
|
||||
|
||||
when (key) {
|
||||
"app_version" -> {
|
||||
checkUpdate(activity as SettingsActivity, true)
|
||||
@@ -122,10 +120,8 @@ class SettingsFragment :
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean {
|
||||
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
|
||||
with (preference) {
|
||||
this ?: return false
|
||||
|
||||
when (key) {
|
||||
"tag_translation" -> {
|
||||
updateTranslations()
|
||||
@@ -163,7 +159,7 @@ class SettingsFragment :
|
||||
|
||||
when (key) {
|
||||
"proxy" -> {
|
||||
summary = context?.let { getProxyInfo().type.name }
|
||||
summary = context.let { getProxyInfo().type.name }
|
||||
}
|
||||
"download_folder" -> {
|
||||
summary = FileX(context, Preferences.get<String>("download_folder")).canonicalPath
|
||||
@@ -300,7 +296,7 @@ class SettingsFragment :
|
||||
}
|
||||
"oss" -> {
|
||||
setOnPreferenceClickListener {
|
||||
context?.startActivity(Intent(context, OssLicensesMenuActivity::class.java))
|
||||
context.startActivity(Intent(context, OssLicensesMenuActivity::class.java))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,9 @@ package xyz.quaver.pupil.util.downloader
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
@@ -35,40 +32,84 @@ import okhttp3.Request
|
||||
import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.*
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.hitomi.GalleryBlock
|
||||
import xyz.quaver.pupil.hitomi.GalleryInfo
|
||||
import xyz.quaver.pupil.hitomi.getGalleryBlock
|
||||
import xyz.quaver.pupil.hitomi.getGalleryInfo
|
||||
import xyz.quaver.pupil.hitomi.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@Serializable
|
||||
data class OldGalleryBlock(
|
||||
data class OldReader(
|
||||
val code: String,
|
||||
val id: Int,
|
||||
val galleryUrl: String,
|
||||
val thumbnails: List<String>,
|
||||
val title: String,
|
||||
val artists: List<String>,
|
||||
val series: List<String>,
|
||||
val type: String,
|
||||
val language: String,
|
||||
val relatedTags: List<String>
|
||||
val galleryInfo: OldGalleryInfo
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class OldGalleryInfo(
|
||||
val language_localname: String? = null,
|
||||
val language: String? = null,
|
||||
val date: String? = null,
|
||||
val files: List<OldGalleryFiles>,
|
||||
val id: Int? = null,
|
||||
val type: String? = null,
|
||||
val title: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class OldGalleryFiles(
|
||||
val width: Int,
|
||||
val hash: String,
|
||||
val haswebp: Int = 0,
|
||||
val name: String,
|
||||
val height: Int,
|
||||
val hasavif: Int = 0,
|
||||
val hasavifsmalltn: Int? = 0
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class OldMetadata(
|
||||
var galleryBlock: GalleryBlock? = null,
|
||||
var reader: OldReader? = null,
|
||||
var imageList: MutableList<String?>? = null
|
||||
) {
|
||||
fun copy(): OldMetadata = OldMetadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
var galleryBlock: GalleryBlock? = null,
|
||||
var galleryInfo: GalleryInfo? = null,
|
||||
var imageList: MutableList<String?>? = null
|
||||
) {
|
||||
constructor(old: OldMetadata) : this(
|
||||
old.galleryBlock,
|
||||
old.reader?.galleryInfo?.let { oldGalleryInfo ->
|
||||
GalleryInfo(
|
||||
oldGalleryInfo.id.toString(),
|
||||
oldGalleryInfo.title ?: "",
|
||||
null,
|
||||
oldGalleryInfo.language,
|
||||
oldGalleryInfo.type ?: "",
|
||||
oldGalleryInfo.date ?: "",
|
||||
files = oldGalleryInfo.files.map {
|
||||
GalleryFiles(
|
||||
it.width,
|
||||
it.hash,
|
||||
it.haswebp,
|
||||
it.name,
|
||||
it.height,
|
||||
it.hasavif,
|
||||
it.hasavifsmalltn
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
old.imageList
|
||||
)
|
||||
|
||||
fun copy(): Metadata = Metadata(galleryBlock, galleryInfo, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
||||
}
|
||||
|
||||
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
||||
|
||||
companion object {
|
||||
val instances = ConcurrentHashMap<Int, Cache>()
|
||||
|
||||
@@ -90,9 +131,13 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
|
||||
var metadata = kotlin.runCatching {
|
||||
findFile(".metadata")?.readText()?.let { metadata ->
|
||||
kotlin.runCatching {
|
||||
Json.decodeFromString<Metadata>(metadata)
|
||||
}.getOrElse {
|
||||
Metadata(json.decodeFromString<OldMetadata>(metadata))
|
||||
}
|
||||
}.getOrNull() ?: Metadata()
|
||||
}
|
||||
}.onFailure { it.printStackTrace() }.getOrNull() ?: Metadata()
|
||||
|
||||
val downloadFolder: FileX?
|
||||
get() = DownloadManager.getInstance(this).getDownloadFolder(galleryID)
|
||||
@@ -179,14 +224,13 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
metadata.imageList?.getOrNull(index)?.let { findFile(it) }
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
fun putImage(index: Int, fileName: String, data: InputStream) {
|
||||
suspend fun putImage(index: Int, fileName: String, data: ByteArray) = coroutineScope {
|
||||
val file = cacheFolder.getChild(fileName)
|
||||
|
||||
if (!file.exists())
|
||||
file.createNewFile()
|
||||
file.outputStream()?.use {
|
||||
data.copyTo(it)
|
||||
}
|
||||
|
||||
file.writeBytes(data)
|
||||
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,21 @@
|
||||
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import kotlinx.serialization.json.*
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.hitomi.GalleryBlock
|
||||
import xyz.quaver.pupil.hitomi.GalleryInfo
|
||||
import xyz.quaver.pupil.hitomi.imageUrlFromImage
|
||||
@@ -77,8 +87,8 @@ fun OkHttpClient.Builder.proxyInfo(proxyInfo: ProxyInfo) = this.apply {
|
||||
val formatMap = mapOf<String, GalleryBlock.() -> (String)>(
|
||||
"-id-" to { id.toString() },
|
||||
"-title-" to { title },
|
||||
"-artist-" to { artists.joinToString() },
|
||||
"-group-" to { groups.joinToString() }
|
||||
"-artist-" to { if (artists.isNotEmpty()) artists.joinToString() else "N/A" },
|
||||
"-group-" to { if (groups.isNotEmpty()) groups.joinToString() else "N/A" }
|
||||
// TODO
|
||||
)
|
||||
/**
|
||||
@@ -100,13 +110,11 @@ fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
||||
|
||||
suspend fun GalleryInfo.getRequestBuilders(): List<Request.Builder> {
|
||||
val galleryID = this.id.toIntOrNull() ?: 0
|
||||
val lowQuality = Preferences["low_quality", true]
|
||||
|
||||
return this.files.map {
|
||||
Request.Builder()
|
||||
.url(
|
||||
runCatching {
|
||||
imageUrlFromImage(galleryID, it, !lowQuality)
|
||||
imageUrlFromImage(galleryID, it, false)
|
||||
}
|
||||
.onFailure {
|
||||
FirebaseCrashlytics.getInstance().recordException(it)
|
||||
@@ -135,3 +143,30 @@ fun JsonElement.getOrNull(tag: String) = kotlin.runCatching {
|
||||
|
||||
val JsonElement.content
|
||||
get() = this.jsonPrimitive.contentOrNull
|
||||
|
||||
fun checkNotificationEnabled(context: Context) =
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
||||
ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
fun showNotificationPermissionExplanationDialog(context: Context) {
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(R.string.notification_denied)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun requestNotificationPermission(
|
||||
activity: Activity,
|
||||
requestPermissionLauncher: ActivityResultLauncher<String>,
|
||||
showRationale: Boolean = true,
|
||||
ifGranted: () -> Unit,
|
||||
) {
|
||||
when {
|
||||
checkNotificationEnabled(activity) -> ifGranted()
|
||||
showRationale && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.POST_NOTIFICATIONS) ->
|
||||
showNotificationPermissionExplanationDialog(activity)
|
||||
else ->
|
||||
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
}
|
||||
@@ -27,17 +27,14 @@ import androidx.preference.PreferenceManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.*
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import ru.noties.markwon.Markwon
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.favorites
|
||||
import xyz.quaver.pupil.*
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
@@ -138,6 +135,11 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
||||
val msg = extractReleaseNote(update, Locale.getDefault())
|
||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (!checkNotificationEnabled(context)) {
|
||||
showNotificationPermissionExplanationDialog(context)
|
||||
return@setPositiveButton
|
||||
}
|
||||
|
||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
//Cancel any download queued before
|
||||
@@ -173,7 +175,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
||||
}
|
||||
}
|
||||
|
||||
fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List<Int>) -> Unit)? = null) {
|
||||
fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((Int) -> Unit)? = null) {
|
||||
if (!URLUtil.isValidUrl(url)) {
|
||||
onFailure?.invoke(IllegalArgumentException())
|
||||
return
|
||||
@@ -191,9 +193,20 @@ fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
kotlin.runCatching {
|
||||
Json.decodeFromString<List<Int>>(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]").let {
|
||||
favorites.addAll(it)
|
||||
onSuccess?.invoke(it)
|
||||
val data = Json.parseToJsonElement(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]")
|
||||
|
||||
when (data) {
|
||||
is JsonArray -> favorites.addAll(data.map { it.jsonPrimitive.int })
|
||||
is JsonObject -> {
|
||||
val newFavorites = data["favorites"]?.let { Json.decodeFromJsonElement<List<Int>>(it) }.orEmpty()
|
||||
val newFavoriteTags = data["favorite_tags"]?.let { Json.decodeFromJsonElement<List<Tag>>(it) }.orEmpty()
|
||||
|
||||
favorites.addAll(newFavorites)
|
||||
favoriteTags.addAll(newFavoriteTags)
|
||||
|
||||
onSuccess?.invoke(favorites.size + favoriteTags.size)
|
||||
}
|
||||
else -> error("data is neither JsonArray or JsonObject")
|
||||
}
|
||||
}.onFailure { onFailure?.invoke(it) }
|
||||
}
|
||||
|
||||
31
app/src/main/res/raw/isrgrootx1.pem
Normal file
31
app/src/main/res/raw/isrgrootx1.pem
Normal file
@@ -0,0 +1,31 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -22,6 +22,7 @@
|
||||
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
|
||||
<string name="settings_clear_history_summary">履歴数: %1$d</string>
|
||||
<string name="main_drawer_history">履歴</string>
|
||||
<string name="notification_denied">通知を無効にするとバックグラウンドダウンロード及びアプリのアップデート機能が使用不可になります。</string>
|
||||
<string name="main_drawer_home">トップ</string>
|
||||
<string name="update_release_note"># リリースノート(v%1$s)\n%2$s</string>
|
||||
<string name="settings_security_mode_title">セキュリティーモード</string>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string>
|
||||
<string name="settings_clear_history_summary">기록 %1$d개 저장됨</string>
|
||||
<string name="main_drawer_history">기록</string>
|
||||
<string name="notification_denied">백그라운드 다운로드를 위해서는 알림을 활성화할 필요가 있습니다. 알림을 비활성화하면 백그라운드 다운로드와 앱 업데이트 기능을 사용할 수 없습니다.</string>
|
||||
<string name="main_drawer_home">홈</string>
|
||||
<string name="update_release_note"># 릴리즈 노트(v%1$s)\n%2$s</string>
|
||||
<string name="settings_security_mode_summary">최근 앱 목록 창에서 앱 화면을 보이지 않게 합니다</string>
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
|
||||
<string name="unaccessible_download_folder">From Android 11 and above, current Download folder cannot be accessed by outside apps. Would you like to change the download folder?</string>
|
||||
|
||||
<string name="notification_denied">Notification permission is required for background downloads. If you deny notifications from this app, in-app update and background download will be disabled.</string>
|
||||
|
||||
<string name="main_drawer_home">Home</string>
|
||||
<string name="main_drawer_history">History</string>
|
||||
<string name="main_drawer_downloads">Downloads</string>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
-->
|
||||
|
||||
<paths>
|
||||
<external-path name="external" path="/"/>
|
||||
<external-files-path name="files" path="/"/>
|
||||
<external-path name="external" path="."/>
|
||||
<external-files-path name="files" path="."/>
|
||||
<files-path name="files" path="." />
|
||||
</paths>
|
||||
@@ -56,12 +56,6 @@
|
||||
app:key="nomedia"
|
||||
app:title="@string/settings_nomedia_title"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:key="low_quality"
|
||||
app:title="@string/settings_low_quality"
|
||||
app:summary="@string/settings_low_quality_summary"
|
||||
app:defaultValue="true"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
||||
10
build.gradle
10
build.gradle
@@ -6,16 +6,16 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.0'
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath "com.google.gms:google-services:4.3.10"
|
||||
classpath "com.google.gms:google-services:4.3.15"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
classpath "com.google.firebase:firebase-crashlytics-gradle:2.8.1"
|
||||
classpath "com.google.firebase:perf-plugin:1.4.1"
|
||||
classpath "com.google.android.gms:oss-licenses-plugin:0.10.4"
|
||||
classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.9"
|
||||
classpath "com.google.firebase:perf-plugin:1.4.2"
|
||||
classpath "com.google.android.gms:oss-licenses-plugin:0.10.6"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,4 @@ kotlin.code.style=official
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
kotlin_version=1.6.10
|
||||
kotlin_version=1.9.0
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||
|
||||
Reference in New Issue
Block a user