Compare commits
32 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 |
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
|
*.iml
|
||||||
.gradle
|
.idea/
|
||||||
/local.properties
|
misc.xml
|
||||||
/.idea/caches
|
deploymentTargetDropDown.xml
|
||||||
/.idea/libraries
|
render.experimental.xml
|
||||||
/.idea/modules.xml
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/navEditor.xml
|
|
||||||
/.idea/assetWizardSettings.xml
|
|
||||||
.DS_Store
|
|
||||||
/build
|
|
||||||
/captures
|
|
||||||
.externalNativeBuild
|
|
||||||
|
|
||||||
#Github pages
|
# Keystore files
|
||||||
/gh-pages
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|
||||||
#Private files
|
# Google Services (e.g. APIs or Firebase)
|
||||||
**/google-services.json
|
google-services.json
|
||||||
**/credentials.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>
|
|
||||||
17
.idea/deploymentTargetDropDown.xml
generated
17
.idea/deploymentTargetDropDown.xml
generated
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="deploymentTargetDropDown">
|
|
||||||
<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-01T08:00:57.223690Z" />
|
|
||||||
</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*
|
*Pupil, Hitomi.la viewer for Android*
|
||||||
|
|
||||||

|

|
||||||
[](https://github.com/tom5079/Pupil/releases/download/5.3.3/Pupil-v5.3.3.apk)
|
[](https://github.com/tom5079/Pupil/releases/download/5.3.11/Pupil-v5.3.11.apk)
|
||||||
[](https://discord.gg/Stj4b5v)
|
[](https://discord.gg/Stj4b5v)
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ configurations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "xyz.quaver.pupil"
|
applicationId "xyz.quaver.pupil"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 31
|
compileSdk 34
|
||||||
|
targetSdkVersion 34
|
||||||
versionCode 69
|
versionCode 69
|
||||||
versionName "5.3.4"
|
versionName "5.3.11"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
@@ -79,13 +79,14 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
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-serialization-json:1.3.2"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.4.1"
|
implementation "androidx.appcompat:appcompat:1.4.1"
|
||||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||||
implementation "androidx.fragment:fragment-ktx:1.4.0"
|
implementation "androidx.fragment:fragment-ktx:1.4.1"
|
||||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
|
implementation "androidx.constraintlayout:constraintlayout:2.1.3"
|
||||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||||
@@ -94,15 +95,15 @@ dependencies {
|
|||||||
|
|
||||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
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-analytics-ktx"
|
||||||
implementation "com.google.firebase:firebase-crashlytics-ktx"
|
implementation "com.google.firebase:firebase-crashlytics-ktx"
|
||||||
implementation "com.google.firebase:firebase-perf-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-oss-licenses:17.0.1"
|
||||||
implementation "com.google.android.gms:play-services-mlkit-face-detection:17.0.0"
|
implementation "com.google.android.gms:play-services-mlkit-face-detection:17.1.0"
|
||||||
|
|
||||||
implementation "com.github.clans:fab:1.6.4"
|
implementation "com.github.clans:fab:1.6.4"
|
||||||
|
|
||||||
@@ -128,15 +129,11 @@ dependencies {
|
|||||||
|
|
||||||
implementation "org.jsoup:jsoup:1.14.3"
|
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:documentfilex:0.7.2"
|
||||||
implementation "xyz.quaver:floatingsearchview:1.1.7"
|
implementation "xyz.quaver:floatingsearchview:1.1.7"
|
||||||
|
|
||||||
testImplementation "junit:junit:4.13.2"
|
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.ext:junit:1.1.3"
|
||||||
androidTestImplementation "androidx.test:rules:1.4.0"
|
androidTestImplementation "androidx.test:rules:1.4.0"
|
||||||
androidTestImplementation "androidx.test:runner:1.4.0"
|
androidTestImplementation "androidx.test:runner:1.4.0"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"filters": [],
|
"filters": [],
|
||||||
"attributes": [],
|
"attributes": [],
|
||||||
"versionCode": 69,
|
"versionCode": 69,
|
||||||
"versionName": "5.3.4",
|
"versionName": "5.3.11",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<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" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
@@ -45,7 +47,8 @@
|
|||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<service android:name=".services.DownloadService"
|
<service android:name=".services.DownloadService"
|
||||||
android:exported="false"/>
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="specialUse" />
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receiver.UpdateBroadcastReceiver"
|
android:name=".receiver.UpdateBroadcastReceiver"
|
||||||
@@ -61,165 +64,107 @@
|
|||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
android:parentActivityName=".ui.MainActivity"
|
android:parentActivityName=".ui.MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="http" />
|
||||||
android:host="hitomi.la"
|
<data android:scheme="https" />
|
||||||
android:pathPrefix="/galleries"
|
<data android:host="*.hasha.in"/>
|
||||||
android:scheme="http" />
|
<data android:pathPrefix="/reader"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="http" />
|
||||||
android:host="hitomi.la"
|
<data android:scheme="https" />
|
||||||
android:pathPrefix="/manga"
|
<data android:host="hitomi.la"/>
|
||||||
android:scheme="http" />
|
<data android:pathPrefix="/galleries"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="http" />
|
||||||
android:host="hitomi.la"
|
<data android:scheme="https" />
|
||||||
android:pathPrefix="/doujinshi"
|
<data android:host="hitomi.la" />
|
||||||
android:scheme="http" />
|
<data android:pathPrefix="/manga" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="http" />
|
||||||
android:host="hitomi.la"
|
<data android:scheme="https" />
|
||||||
android:pathPrefix="/cg"
|
<data android:host="hitomi.la" />
|
||||||
android:scheme="http" />
|
<data android:pathPrefix="/doujinshi" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="http" />
|
||||||
android:host="hitomi.la"
|
<data android:scheme="https" />
|
||||||
android:pathPrefix="/reader"
|
<data android:host="hitomi.la" />
|
||||||
android:scheme="http" />
|
<data android:pathPrefix="/cg" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="http" />
|
||||||
android:host="hitomi.la"
|
<data android:scheme="https" />
|
||||||
android:pathPrefix="/galleries"
|
<data android:host="hitomi.la" />
|
||||||
android:scheme="https" />
|
<data android:pathPrefix="/imageset" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="http" />
|
||||||
android:host="hitomi.la"
|
<data android:scheme="https" />
|
||||||
android:pathPrefix="/manga"
|
<data android:host="hitomi.la" />
|
||||||
android:scheme="https" />
|
<data android:pathPrefix="/reader" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="http" />
|
||||||
android:host="hitomi.la"
|
<data android:host="e-hentai.org" />
|
||||||
android:pathPrefix="/doujinshi"
|
<data android:pathPrefix="/g" />
|
||||||
android:scheme="https" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:scheme="https" />
|
||||||
android:host="hitomi.la"
|
<data android:host="e-hentai.org" />
|
||||||
android:pathPrefix="/cg"
|
<data android:pathPrefix="/g" />
|
||||||
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" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.SettingsActivity"
|
android:name=".ui.SettingsActivity"
|
||||||
android:label="@string/settings_title">
|
android:label="@string/settings_title">
|
||||||
<tools:validation testUrl="http://ix.io/eer" />
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
@@ -232,17 +177,6 @@
|
|||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</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>
|
||||||
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import android.util.Log
|
|||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import app.cash.zipline.QuickJs
|
|
||||||
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory
|
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory
|
||||||
import com.github.piasy.biv.BigImageViewer
|
import com.github.piasy.biv.BigImageViewer
|
||||||
import com.github.piasy.biv.loader.fresco.FrescoImageLoader
|
import com.github.piasy.biv.loader.fresco.FrescoImageLoader
|
||||||
@@ -51,9 +50,14 @@ import xyz.quaver.pupil.types.Tag
|
|||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.TrustManagerFactory
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
||||||
@@ -77,10 +81,26 @@ val client: OkHttpClient
|
|||||||
clientHolder = it
|
clientHolder = it
|
||||||
}
|
}
|
||||||
|
|
||||||
private var version = ""
|
fun getSSLContext(context: Context): SSLContext {
|
||||||
var runtimeReady = false
|
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
|
||||||
private set
|
keyStore.load(null, null)
|
||||||
lateinit var runtime: QuickJs
|
|
||||||
|
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() {
|
class Pupil : Application() {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -88,35 +108,6 @@ class Pupil : Application() {
|
|||||||
private set
|
private set
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
runtime = QuickJs.create()
|
|
||||||
}
|
|
||||||
while (true) {
|
|
||||||
kotlin.runCatching {
|
|
||||||
val newVersion = URL("https://tom5079.github.io/PupilSources/hitomi.html.ver").readText()
|
|
||||||
|
|
||||||
if (version != newVersion) {
|
|
||||||
runtimeReady = false
|
|
||||||
evaluationContext.cancelChildren()
|
|
||||||
kotlin.runCatching {
|
|
||||||
URL("https://tom5079.github.io/PupilSources/assets/js/gg.js").readText()
|
|
||||||
}.getOrNull()?.also { gg ->
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
runtime.evaluate(gg)
|
|
||||||
version = newVersion
|
|
||||||
runtimeReady = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(10000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
instance = this
|
instance = this
|
||||||
|
|
||||||
@@ -135,7 +126,8 @@ class Pupil : Application() {
|
|||||||
val proxyInfo = getProxyInfo()
|
val proxyInfo = getProxyInfo()
|
||||||
|
|
||||||
clientBuilder = OkHttpClient.Builder()
|
clientBuilder = OkHttpClient.Builder()
|
||||||
.connectTimeout(0, TimeUnit.SECONDS)
|
// .connectTimeout(0, TimeUnit.SECONDS)
|
||||||
|
.sslSocketFactory(getSSLContext(this).socketFactory)
|
||||||
.readTimeout(0, TimeUnit.SECONDS)
|
.readTimeout(0, TimeUnit.SECONDS)
|
||||||
.proxyInfo(proxyInfo)
|
.proxyInfo(proxyInfo)
|
||||||
.addInterceptor { chain ->
|
.addInterceptor { chain ->
|
||||||
|
|||||||
@@ -17,16 +17,23 @@
|
|||||||
package xyz.quaver.pupil.hitomi
|
package xyz.quaver.pupil.hitomi
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
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.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.runtime
|
|
||||||
import xyz.quaver.pupil.runtimeReady
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
const val protocol = "https:"
|
const val protocol = "https:"
|
||||||
|
|
||||||
@@ -131,19 +138,73 @@ const val nozomiextension = ".nozomi"
|
|||||||
val evaluationContext = Dispatchers.Main + Job()
|
val evaluationContext = Dispatchers.Main + Job()
|
||||||
|
|
||||||
object gg {
|
object gg {
|
||||||
|
private var lastRetrieval: Long? = null
|
||||||
|
|
||||||
suspend fun m(g: Int): Int = withContext(evaluationContext) {
|
private val mutex = Mutex()
|
||||||
while (!runtimeReady) delay(1000)
|
|
||||||
runtime.evaluate("gg.m($g)").toString().toInt()
|
private var mDefault = 0
|
||||||
}
|
private val mMap = mutableMapOf<Int, Int>()
|
||||||
suspend fun b(): String = withContext(evaluationContext) {
|
|
||||||
while (!runtimeReady) delay(1000)
|
private var b = ""
|
||||||
runtime.evaluate("gg.b").toString()
|
|
||||||
|
@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) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
while (!runtimeReady) delay(1000)
|
if (!call.isCanceled) {
|
||||||
runtime.evaluate("gg.s('$h')").toString()
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,11 +166,7 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : Set<
|
|||||||
else -> "$protocol//$domain/$compressed_nozomi_prefix/$area/$tag-$language$nozomiextension"
|
else -> "$protocol//$domain/$compressed_nozomi_prefix/$area/$tag-$language$nozomiextension"
|
||||||
}
|
}
|
||||||
|
|
||||||
val bytes = try {
|
val bytes = URL(nozomiAddress).readBytes()
|
||||||
URL(nozomiAddress).readBytes()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return emptySet()
|
|
||||||
}
|
|
||||||
|
|
||||||
val nozomi = mutableSetOf<Int>()
|
val nozomi = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.app.PendingIntent
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
@@ -100,13 +101,15 @@ class DownloadService : Service() {
|
|||||||
notify(galleryID)
|
notify(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi", "MissingPermission")
|
||||||
private fun notify(galleryID: Int) {
|
private fun notify(galleryID: Int) {
|
||||||
val max = progress[galleryID]?.size ?: 0
|
val max = progress[galleryID]?.size ?: 0
|
||||||
val progress = progress[galleryID]?.count { it == Float.POSITIVE_INFINITY } ?: 0
|
val progress = progress[galleryID]?.count { it == Float.POSITIVE_INFINITY } ?: 0
|
||||||
|
|
||||||
val notification = notification[galleryID] ?: return
|
val notification = notification[galleryID] ?: return
|
||||||
|
|
||||||
|
if (!checkNotificationEnabled(this)) return
|
||||||
|
|
||||||
if (isCompleted(galleryID)) {
|
if (isCompleted(galleryID)) {
|
||||||
notification
|
notification
|
||||||
.setContentText(getString(R.string.reader_notification_complete))
|
.setContentText(getString(R.string.reader_notification_complete))
|
||||||
@@ -168,19 +171,26 @@ class DownloadService : Service() {
|
|||||||
private val interceptor: PupilInterceptor = { chain ->
|
private val interceptor: PupilInterceptor = { chain ->
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
|
|
||||||
var response = chain.proceed(request)
|
var response = kotlin.runCatching {
|
||||||
var limit = 5
|
chain.proceed(request)
|
||||||
|
}.getOrNull()
|
||||||
|
var limit = 10
|
||||||
|
|
||||||
while (!response.isSuccessful) {
|
while (response?.isSuccessful != true) {
|
||||||
if (response.code() == 503) {
|
if (response?.code() == 503) {
|
||||||
Thread.sleep(200)
|
Thread.sleep(200)
|
||||||
} else if (--limit > 0)
|
} else if (--limit < 0)
|
||||||
break
|
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 {
|
.body(response.body()?.let {
|
||||||
ProgressResponseBody(request.tag(), it, progressListener)
|
ProgressResponseBody(request.tag(), it, progressListener)
|
||||||
}).build()
|
}).build()
|
||||||
@@ -207,6 +217,7 @@ class DownloadService : Service() {
|
|||||||
private val callback = object: Callback {
|
private val callback = object: Callback {
|
||||||
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
Log.d("PUPILD", "ONFAILURE ${call.request().tag()}, ${e}")
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
|
||||||
if (e.message?.contains("cancel", true) == false) {
|
if (e.message?.contains("cancel", true) == false) {
|
||||||
@@ -215,6 +226,7 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
Log.d("PUPILD", "ONRESPONSE ${call.request().tag()}")
|
||||||
val (galleryID, index, startId) = call.request().tag() as Tag
|
val (galleryID, index, startId) = call.request().tag() as Tag
|
||||||
val ext = call.request().url().encodedPath().split('.').last()
|
val ext = call.request().url().encodedPath().split('.').last()
|
||||||
|
|
||||||
@@ -394,7 +406,11 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
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())
|
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)) {
|
when (intent?.getStringExtra(KEY_COMMAND)) {
|
||||||
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
||||||
@@ -415,7 +431,11 @@ class DownloadService : Service() {
|
|||||||
override fun onBind(p0: Intent?) = binder
|
override fun onBind(p0: Intent?) = binder
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
|
||||||
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
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
|
interceptors[Tag::class] = interceptor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -31,7 +33,9 @@ import android.view.View
|
|||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.core.view.ViewCompat
|
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.ui.view.ProgressCard
|
||||||
import xyz.quaver.pupil.util.ItemClickSupport
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import xyz.quaver.pupil.util.requestNotificationPermission
|
||||||
import xyz.quaver.pupil.util.checkUpdate
|
import xyz.quaver.pupil.util.checkUpdate
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.restore
|
import xyz.quaver.pupil.util.restore
|
||||||
|
import xyz.quaver.pupil.util.showNotificationPermissionExplanationDialog
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
@@ -107,6 +113,12 @@ class MainActivity :
|
|||||||
|
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: MainActivityBinding
|
||||||
|
|
||||||
|
private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
|
if (!isGranted) {
|
||||||
|
showNotificationPermissionExplanationDialog(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = MainActivityBinding.inflate(layoutInflater)
|
binding = MainActivityBinding.inflate(layoutInflater)
|
||||||
@@ -118,12 +130,14 @@ class MainActivity :
|
|||||||
onFailure = {
|
onFailure = {
|
||||||
Snackbar.make(binding.contents.recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(binding.contents.recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
||||||
}, onSuccess = {
|
}, 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())
|
if (Preferences["download_folder", ""].isEmpty())
|
||||||
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
||||||
|
|
||||||
@@ -392,6 +406,10 @@ class MainActivity :
|
|||||||
onDownloadClickedHandler = { position ->
|
onDownloadClickedHandler = { position ->
|
||||||
val galleryID = galleries[position]
|
val galleryID = galleries[position]
|
||||||
|
|
||||||
|
requestNotificationPermission(
|
||||||
|
this@MainActivity,
|
||||||
|
requestNotificationPermssionLauncher
|
||||||
|
) {
|
||||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
||||||
DownloadService.cancel(this@MainActivity, galleryID)
|
DownloadService.cancel(this@MainActivity, galleryID)
|
||||||
}
|
}
|
||||||
@@ -399,6 +417,7 @@ class MainActivity :
|
|||||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||||
DownloadService.download(this@MainActivity, galleryID)
|
DownloadService.download(this@MainActivity, galleryID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
closeAllItems()
|
closeAllItems()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,9 +57,12 @@ import xyz.quaver.pupil.favorites
|
|||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.camera
|
import xyz.quaver.pupil.util.camera
|
||||||
|
import xyz.quaver.pupil.util.checkNotificationEnabled
|
||||||
import xyz.quaver.pupil.util.closeCamera
|
import xyz.quaver.pupil.util.closeCamera
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
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
|
import xyz.quaver.pupil.util.startCamera
|
||||||
|
|
||||||
class ReaderActivity : BaseActivity() {
|
class ReaderActivity : BaseActivity() {
|
||||||
@@ -117,6 +120,12 @@ class ReaderActivity : BaseActivity() {
|
|||||||
|
|
||||||
private lateinit var binding: ReaderActivityBinding
|
private lateinit var binding: ReaderActivityBinding
|
||||||
|
|
||||||
|
private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
|
if (!isGranted) {
|
||||||
|
showNotificationPermissionExplanationDialog(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ReaderActivityBinding.inflate(layoutInflater)
|
binding = ReaderActivityBinding.inflate(layoutInflater)
|
||||||
@@ -148,10 +157,11 @@ class ReaderActivity : BaseActivity() {
|
|||||||
val uri = intent.data
|
val uri = intent.data
|
||||||
val lastPathSegment = uri?.lastPathSegment
|
val lastPathSegment = uri?.lastPathSegment
|
||||||
if (uri != null && lastPathSegment != null) {
|
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" ->
|
"hitomi.la" ->
|
||||||
Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1)?.toIntOrNull() ?: 0
|
Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1)?.toIntOrNull() ?: 0
|
||||||
"hiyobi.me" -> lastPathSegment.toInt()
|
|
||||||
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
@@ -360,6 +370,10 @@ class ReaderActivity : BaseActivity() {
|
|||||||
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
|
requestNotificationPermission(
|
||||||
|
this@ReaderActivity,
|
||||||
|
requestNotificationPermssionLauncher
|
||||||
|
) {
|
||||||
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
|
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
|
||||||
|
|
||||||
if (downloadManager.isDownloading(galleryID)) {
|
if (downloadManager.isDownloading(galleryID)) {
|
||||||
@@ -372,6 +386,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with(binding.retryFab) {
|
with(binding.retryFab) {
|
||||||
setImageResource(R.drawable.refresh)
|
setImageResource(R.drawable.refresh)
|
||||||
|
|||||||
@@ -18,25 +18,72 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.fragment
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffColorFilter
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
import com.google.android.material.snackbar.Snackbar
|
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 okhttp3.*
|
||||||
|
import xyz.quaver.io.FileX
|
||||||
|
import xyz.quaver.io.util.readText
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
|
import xyz.quaver.pupil.types.Tag
|
||||||
|
import xyz.quaver.pupil.util.get
|
||||||
import xyz.quaver.pupil.util.restore
|
import xyz.quaver.pupil.util.restore
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
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?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.manage_favorites_preferences, rootKey)
|
setPreferencesFromResource(R.xml.manage_favorites_preferences, rootKey)
|
||||||
|
|
||||||
@@ -47,57 +94,44 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
|||||||
val context = context ?: return
|
val context = context ?: return
|
||||||
|
|
||||||
findPreference<Preference>("backup")?.setOnPreferenceClickListener {
|
findPreference<Preference>("backup")?.setOnPreferenceClickListener {
|
||||||
val request = Request.Builder()
|
val favorites = runCatching {
|
||||||
.url(context.getString(R.string.backup_url))
|
Json.parseToJsonElement(File(ContextCompat.getDataDir(context), "favorites.json").readText())
|
||||||
.post(
|
}.getOrNull()
|
||||||
FormBody.Builder()
|
val favoriteTags = kotlin.runCatching {
|
||||||
.add("f:1", File(ContextCompat.getDataDir(context), "favorites.json").readText())
|
Json.parseToJsonElement(File(ContextCompat.getDataDir(context), "favorites_tags.json").readText())
|
||||||
.build()
|
}.getOrNull()
|
||||||
).build()
|
|
||||||
|
|
||||||
client.newCall(request).enqueue(object: Callback {
|
val favoriteJson = buildJsonObject {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
favorites?.let {
|
||||||
val view = view ?: return
|
put("favorites", it)
|
||||||
Snackbar.make(view, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
}
|
||||||
|
favoriteTags?.let {
|
||||||
|
put("favorite_tags", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
val backupFile = File(context.filesDir, "pupil-backup.json").also {
|
||||||
if (response.code() != 200) {
|
it.writeText(favoriteJson.toString())
|
||||||
response.close()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent(Intent.ACTION_SEND).apply {
|
Intent(Intent.ACTION_SEND).apply {
|
||||||
type = "text/plain"
|
val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", backupFile)
|
||||||
putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", ""))
|
setDataAndType(uri, "application/json")
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
}.let {
|
}.let {
|
||||||
getContext()?.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share)))
|
context.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share)))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
findPreference<Preference>("restore")?.setOnPreferenceClickListener {
|
findPreference<Preference>("restore")?.setOnPreferenceClickListener {
|
||||||
val editText = EditText(context).apply {
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
setText(context.getString(R.string.backup_url), TextView.BufferType.EDITABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
type = "*/*"
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder(context)
|
requestBackupFileLauncher.launch(intent)
|
||||||
.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()
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,12 +61,10 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
initPreferences()
|
initPreferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreferenceClick(preference: Preference?): Boolean {
|
override fun onPreferenceClick(preference: Preference): Boolean {
|
||||||
val context = context ?: return false
|
val context = context ?: return false
|
||||||
|
|
||||||
with(preference) {
|
with(preference) {
|
||||||
this ?: return false
|
|
||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
"delete_cache" -> {
|
"delete_cache" -> {
|
||||||
val dir = File(context.cacheDir, "imageCache")
|
val dir = File(context.cacheDir, "imageCache")
|
||||||
@@ -119,7 +117,9 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
if (!metadataFile.exists()) return@forEach
|
if (!metadataFile.exists()) return@forEach
|
||||||
|
|
||||||
val metadata = metadataFile.readText()?.let {
|
val metadata = metadataFile.readText()?.let {
|
||||||
|
runCatching {
|
||||||
json.decodeFromString<Metadata>(it)
|
json.decodeFromString<Metadata>(it)
|
||||||
|
}.getOrNull()
|
||||||
} ?: return@forEach
|
} ?: return@forEach
|
||||||
|
|
||||||
val galleryID = metadata.galleryBlock?.id ?: metadata.galleryInfo?.id?.toIntOrNull() ?: 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) {
|
with (preference) {
|
||||||
this ?: return false
|
|
||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
"app_version" -> {
|
"app_version" -> {
|
||||||
checkUpdate(activity as SettingsActivity, true)
|
checkUpdate(activity as SettingsActivity, true)
|
||||||
@@ -122,10 +120,8 @@ class SettingsFragment :
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean {
|
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
|
||||||
with (preference) {
|
with (preference) {
|
||||||
this ?: return false
|
|
||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
"tag_translation" -> {
|
"tag_translation" -> {
|
||||||
updateTranslations()
|
updateTranslations()
|
||||||
@@ -163,7 +159,7 @@ class SettingsFragment :
|
|||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
"proxy" -> {
|
"proxy" -> {
|
||||||
summary = context?.let { getProxyInfo().type.name }
|
summary = context.let { getProxyInfo().type.name }
|
||||||
}
|
}
|
||||||
"download_folder" -> {
|
"download_folder" -> {
|
||||||
summary = FileX(context, Preferences.get<String>("download_folder")).canonicalPath
|
summary = FileX(context, Preferences.get<String>("download_folder")).canonicalPath
|
||||||
@@ -300,7 +296,7 @@ class SettingsFragment :
|
|||||||
}
|
}
|
||||||
"oss" -> {
|
"oss" -> {
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
context?.startActivity(Intent(context, OssLicensesMenuActivity::class.java))
|
context.startActivity(Intent(context, OssLicensesMenuActivity::class.java))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,9 +35,14 @@ import xyz.quaver.pupil.client
|
|||||||
import xyz.quaver.pupil.hitomi.*
|
import xyz.quaver.pupil.hitomi.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OldReader(
|
||||||
|
val code: String,
|
||||||
|
val galleryInfo: OldGalleryInfo
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class OldGalleryInfo(
|
data class OldGalleryInfo(
|
||||||
val language_localname: String? = null,
|
val language_localname: String? = null,
|
||||||
@@ -63,7 +68,7 @@ data class OldGalleryFiles(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class OldMetadata(
|
data class OldMetadata(
|
||||||
var galleryBlock: GalleryBlock? = null,
|
var galleryBlock: GalleryBlock? = null,
|
||||||
var reader: OldGalleryInfo? = null,
|
var reader: OldReader? = null,
|
||||||
var imageList: MutableList<String?>? = null
|
var imageList: MutableList<String?>? = null
|
||||||
) {
|
) {
|
||||||
fun copy(): OldMetadata = OldMetadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
fun copy(): OldMetadata = OldMetadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
||||||
@@ -75,12 +80,36 @@ data class Metadata(
|
|||||||
var galleryInfo: GalleryInfo? = null,
|
var galleryInfo: GalleryInfo? = null,
|
||||||
var imageList: MutableList<String?>? = null
|
var imageList: MutableList<String?>? = null
|
||||||
) {
|
) {
|
||||||
constructor(old: OldMetadata) : this(old.galleryBlock, getGalleryInfo(old.galleryBlock?.id ?: throw Exception()), old.imageList)
|
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] } })
|
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) {
|
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val instances = ConcurrentHashMap<Int, Cache>()
|
val instances = ConcurrentHashMap<Int, Cache>()
|
||||||
|
|
||||||
@@ -103,7 +132,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
var metadata = kotlin.runCatching {
|
var metadata = kotlin.runCatching {
|
||||||
findFile(".metadata")?.readText()?.let { metadata ->
|
findFile(".metadata")?.readText()?.let { metadata ->
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
json.decodeFromString<Metadata>(metadata)
|
Json.decodeFromString<Metadata>(metadata)
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
Metadata(json.decodeFromString<OldMetadata>(metadata))
|
Metadata(json.decodeFromString<OldMetadata>(metadata))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,21 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
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 com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.hitomi.GalleryBlock
|
import xyz.quaver.pupil.hitomi.GalleryBlock
|
||||||
import xyz.quaver.pupil.hitomi.GalleryInfo
|
import xyz.quaver.pupil.hitomi.GalleryInfo
|
||||||
import xyz.quaver.pupil.hitomi.imageUrlFromImage
|
import xyz.quaver.pupil.hitomi.imageUrlFromImage
|
||||||
@@ -133,3 +143,30 @@ fun JsonElement.getOrNull(tag: String) = kotlin.runCatching {
|
|||||||
|
|
||||||
val JsonElement.content
|
val JsonElement.content
|
||||||
get() = this.jsonPrimitive.contentOrNull
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import ru.noties.markwon.Markwon
|
import ru.noties.markwon.Markwon
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.*
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.client
|
|
||||||
import xyz.quaver.pupil.favorites
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@@ -138,6 +135,11 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
val msg = extractReleaseNote(update, Locale.getDefault())
|
val msg = extractReleaseNote(update, Locale.getDefault())
|
||||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
if (!checkNotificationEnabled(context)) {
|
||||||
|
showNotificationPermissionExplanationDialog(context)
|
||||||
|
return@setPositiveButton
|
||||||
|
}
|
||||||
|
|
||||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
|
||||||
//Cancel any download queued before
|
//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)) {
|
if (!URLUtil.isValidUrl(url)) {
|
||||||
onFailure?.invoke(IllegalArgumentException())
|
onFailure?.invoke(IllegalArgumentException())
|
||||||
return
|
return
|
||||||
@@ -191,9 +193,20 @@ fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((
|
|||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
Json.decodeFromString<List<Int>>(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]").let {
|
val data = Json.parseToJsonElement(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]")
|
||||||
favorites.addAll(it)
|
|
||||||
onSuccess?.invoke(it)
|
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) }
|
}.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_alert_message">履歴を削除しますか?</string>
|
||||||
<string name="settings_clear_history_summary">履歴数: %1$d</string>
|
<string name="settings_clear_history_summary">履歴数: %1$d</string>
|
||||||
<string name="main_drawer_history">履歴</string>
|
<string name="main_drawer_history">履歴</string>
|
||||||
|
<string name="notification_denied">通知を無効にするとバックグラウンドダウンロード及びアプリのアップデート機能が使用不可になります。</string>
|
||||||
<string name="main_drawer_home">トップ</string>
|
<string name="main_drawer_home">トップ</string>
|
||||||
<string name="update_release_note"># リリースノート(v%1$s)\n%2$s</string>
|
<string name="update_release_note"># リリースノート(v%1$s)\n%2$s</string>
|
||||||
<string name="settings_security_mode_title">セキュリティーモード</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_alert_message">기록을 삭제하시겠습니까?</string>
|
||||||
<string name="settings_clear_history_summary">기록 %1$d개 저장됨</string>
|
<string name="settings_clear_history_summary">기록 %1$d개 저장됨</string>
|
||||||
<string name="main_drawer_history">기록</string>
|
<string name="main_drawer_history">기록</string>
|
||||||
|
<string name="notification_denied">백그라운드 다운로드를 위해서는 알림을 활성화할 필요가 있습니다. 알림을 비활성화하면 백그라운드 다운로드와 앱 업데이트 기능을 사용할 수 없습니다.</string>
|
||||||
<string name="main_drawer_home">홈</string>
|
<string name="main_drawer_home">홈</string>
|
||||||
<string name="update_release_note"># 릴리즈 노트(v%1$s)\n%2$s</string>
|
<string name="update_release_note"># 릴리즈 노트(v%1$s)\n%2$s</string>
|
||||||
<string name="settings_security_mode_summary">최근 앱 목록 창에서 앱 화면을 보이지 않게 합니다</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="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_home">Home</string>
|
||||||
<string name="main_drawer_history">History</string>
|
<string name="main_drawer_history">History</string>
|
||||||
<string name="main_drawer_downloads">Downloads</string>
|
<string name="main_drawer_downloads">Downloads</string>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<paths>
|
<paths>
|
||||||
<external-path name="external" path="/"/>
|
<external-path name="external" path="."/>
|
||||||
<external-files-path name="files" path="/"/>
|
<external-files-path name="files" path="."/>
|
||||||
|
<files-path name="files" path="." />
|
||||||
</paths>
|
</paths>
|
||||||
10
build.gradle
10
build.gradle
@@ -6,16 +6,16 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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-gradle-plugin:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
classpath "com.google.firebase:firebase-crashlytics-gradle:2.8.1"
|
classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.9"
|
||||||
classpath "com.google.firebase:perf-plugin:1.4.1"
|
classpath "com.google.firebase:perf-plugin:1.4.2"
|
||||||
classpath "com.google.android.gms:oss-licenses-plugin:0.10.4"
|
classpath "com.google.android.gms:oss-licenses-plugin:0.10.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ kotlin.code.style=official
|
|||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.useAndroidX=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
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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