Compare commits
188 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
547b6e8e3b | ||
|
|
d88ac27e72 | ||
|
|
e551a40d08 | ||
|
|
e810abe33a | ||
|
|
6172a73719 | ||
|
|
7455e68a45 | ||
|
|
748495ca64 | ||
|
|
f6d9c7f550 | ||
|
|
384e6c61b0 | ||
|
|
d49c9cec20 | ||
|
|
4b27f1aba1 | ||
|
|
a0a989c785 | ||
|
|
ecaecc1b91 | ||
|
|
938156aa71 | ||
|
|
d30c51bb3a | ||
|
|
874606bff9 | ||
|
|
07643e4b4c | ||
|
|
20bc5423cf | ||
|
|
b84cddffdc | ||
|
|
e46d1123df | ||
|
|
48f90faf4e | ||
|
|
615b52c4fa | ||
|
|
2c9c8e223c | ||
|
|
01a653835e | ||
|
|
9d80857a38 | ||
|
|
8a9ab6b36c | ||
|
|
4edc87c197 | ||
|
|
10712e6e62 | ||
|
|
d73dc19d3d | ||
|
|
c204353220 | ||
|
|
37123a2cd5 | ||
|
|
a39484b6ea | ||
|
|
e81b5a4e3a | ||
|
|
0b87c57fbf | ||
|
|
5fd985ba39 | ||
|
|
8c64548513 | ||
|
|
a6de64ceb9 | ||
|
|
16ebb437a3 | ||
|
|
683118a3f4 | ||
|
|
08e38ed45c | ||
|
|
7abf08f1fb | ||
|
|
f3019e9b84 | ||
|
|
9ea55664b6 | ||
|
|
c468764234 | ||
|
|
31c3178430 | ||
|
|
e81c189afc | ||
|
|
e0ccac13c1 | ||
|
|
93228459d7 | ||
|
|
63e07f56e0 | ||
|
|
ee87122bb2 | ||
|
|
290dda9018 | ||
|
|
1d3d78b936 | ||
|
|
a947bc6415 | ||
|
|
9ca891b2f5 | ||
|
|
48e0ebc8ae | ||
|
|
b323353006 | ||
|
|
c85d3ebe81 | ||
|
|
ce843abec8 | ||
|
|
6b43faa70e | ||
|
|
2d0c997b2e | ||
|
|
1db5118377 | ||
|
|
26b53ed7ac | ||
|
|
2c85ea6443 | ||
|
|
cbc2b30f47 | ||
|
|
0b58deb92c | ||
|
|
ed1cf23c91 | ||
|
|
6fbb644e4b | ||
|
|
774867502d | ||
|
|
c8b1439aeb | ||
|
|
38c16adffe | ||
|
|
18aede2701 | ||
|
|
c59d08a0a1 | ||
|
|
66ae29eb5b | ||
|
|
7d9cb3e150 | ||
|
|
9922a9f82a | ||
|
|
445b9b4673 | ||
|
|
0ef7b358e0 | ||
|
|
2d3fb75576 | ||
|
|
d55ff6d68e | ||
|
|
079654a9c7 | ||
|
|
30263c6260 | ||
|
|
3159c343c1 | ||
|
|
ceaa930623 | ||
|
|
6a8539106b | ||
|
|
7a24c3c08e | ||
|
|
251abeb090 | ||
|
|
a61fe9f98c | ||
|
|
d29c7bf91a | ||
|
|
ed4911c441 | ||
|
|
d40b4f3748 | ||
|
|
f3c4fe1914 | ||
|
|
55ee841bd0 | ||
|
|
657fb488ee | ||
|
|
4eef0b93fb | ||
|
|
f2be56435c | ||
|
|
fa6b3ad7ba | ||
|
|
52c05e6888 | ||
|
|
865bf0ba83 | ||
|
|
3f827d1bad | ||
|
|
0561d5f55c | ||
|
|
1bf2e1dacc | ||
|
|
db5a221b56 | ||
|
|
295285f132 | ||
|
|
5052b6c074 | ||
|
|
f98f45dc54 | ||
|
|
8d16950f46 | ||
|
|
74033b9f4a | ||
|
|
e497d47374 | ||
|
|
a97af59260 | ||
|
|
2197de98ea | ||
|
|
c004c7f71a | ||
|
|
69fc3ad4e8 | ||
|
|
678a8f0914 | ||
|
|
08c4c0bf1f | ||
|
|
f2a2656837 | ||
|
|
2011572270 | ||
|
|
3b682667e1 | ||
|
|
6da8de6463 | ||
|
|
039d415871 | ||
|
|
776f53bde0 | ||
|
|
58e535595e | ||
|
|
96ad5f6a6c | ||
|
|
043f7bedd8 | ||
|
|
69bcd8f7c0 | ||
|
|
8a58564812 | ||
|
|
d346cf431f | ||
|
|
c0bce4f3b1 | ||
|
|
94d258ddbb | ||
|
|
6bdba49284 | ||
|
|
9b99baf4bc | ||
|
|
5ad2a538bc | ||
|
|
28703e9bf2 | ||
|
|
e664efefe9 | ||
|
|
27a8694938 | ||
|
|
e0a6102d4d | ||
|
|
7106cf04ed | ||
|
|
2afdc5591a | ||
|
|
8eed4b67c3 | ||
|
|
edacef0f2b | ||
|
|
d28894f8cd | ||
|
|
ee8e921e1a | ||
|
|
480d4b1e9a | ||
|
|
a79c023220 | ||
|
|
efc50df243 | ||
|
|
905ea766b1 | ||
|
|
bce26f4557 | ||
|
|
474d3ad80a | ||
|
|
a74b2c9b49 | ||
|
|
22bdf61bb3 | ||
|
|
69f9b099b7 | ||
|
|
1d812487a6 | ||
|
|
7c2bf8fb9d | ||
|
|
dfb78bed69 | ||
|
|
fb42b48880 | ||
|
|
bb0988a188 | ||
|
|
c64b6f112b | ||
|
|
9ac7fb490e | ||
|
|
bd88a8a8d3 | ||
|
|
1eb75acb40 | ||
|
|
5ccc96aeb9 | ||
|
|
ef72d10344 | ||
|
|
573f0b40d1 | ||
|
|
48f49edb19 | ||
|
|
aa22d9fdd8 | ||
|
|
8410a2fdb3 | ||
|
|
ec98e4e9a4 | ||
|
|
dca6ba457b | ||
|
|
5b10a781a6 | ||
|
|
b103188faf | ||
|
|
29637b234c | ||
|
|
34dc238ef1 | ||
|
|
3c2675e650 | ||
|
|
7e87bb6838 | ||
|
|
3992a07340 | ||
|
|
bd4b61d7ac | ||
|
|
2046d87031 | ||
|
|
0618d8c6f8 | ||
|
|
5bfc27835b | ||
|
|
cdc545ea32 | ||
|
|
449db97a2b | ||
|
|
e01380090d | ||
|
|
6d1505241e | ||
|
|
f303e49e97 | ||
|
|
0e6b50e302 | ||
|
|
868af1e6a2 | ||
|
|
34f7b111ee | ||
|
|
df27907c57 | ||
|
|
75583b9e65 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,3 +14,6 @@
|
|||||||
|
|
||||||
#Github pages
|
#Github pages
|
||||||
/gh-pages
|
/gh-pages
|
||||||
|
|
||||||
|
#Private files
|
||||||
|
**/google-services.json
|
||||||
18
.idea/codeStyles/Project.xml
generated
18
.idea/codeStyles/Project.xml
generated
@@ -1,18 +1,13 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="RIGHT_MARGIN" value="120" />
|
||||||
<AndroidXmlCodeStyleSettings>
|
<AndroidXmlCodeStyleSettings>
|
||||||
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
||||||
</AndroidXmlCodeStyleSettings>
|
</AndroidXmlCodeStyleSettings>
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
</JetCodeStyleSettings>
|
</JetCodeStyleSettings>
|
||||||
<XML>
|
|
||||||
<option name="XML_KEEP_LINE_BREAKS" value="false" />
|
|
||||||
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
|
||||||
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
|
||||||
</XML>
|
|
||||||
<codeStyleSettings language="XML">
|
<codeStyleSettings language="XML">
|
||||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
@@ -23,6 +18,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>xmlns:android</NAME>
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -33,6 +29,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>xmlns:.*</NAME>
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -44,6 +41,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*:id</NAME>
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -54,6 +52,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*:name</NAME>
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -64,6 +63,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>name</NAME>
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>style</NAME>
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -84,6 +85,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -95,6 +97,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
@@ -106,6 +109,7 @@
|
|||||||
<match>
|
<match>
|
||||||
<AND>
|
<AND>
|
||||||
<NAME>.*</NAME>
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
</AND>
|
</AND>
|
||||||
</match>
|
</match>
|
||||||
|
|||||||
6
.idea/copyright/Apache.xml
generated
Normal file
6
.idea/copyright/Apache.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="CopyrightManager">
|
||||||
|
<copyright>
|
||||||
|
<option name="notice" value=" Copyright &#36;today.year tom5079 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License." />
|
||||||
|
<option name="myName" value="Apache" />
|
||||||
|
</copyright>
|
||||||
|
</component>
|
||||||
6
.idea/copyright/GPL.xml
generated
Normal file
6
.idea/copyright/GPL.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<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>
|
||||||
8
.idea/copyright/profiles_settings.xml
generated
Normal file
8
.idea/copyright/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="CopyrightManager">
|
||||||
|
<settings>
|
||||||
|
<module2copyright>
|
||||||
|
<element module="Pupil" copyright="GPL" />
|
||||||
|
<element module="libpupil" copyright="Apache" />
|
||||||
|
</module2copyright>
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -13,7 +13,6 @@
|
|||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
<option name="useQualifiedModuleNames" value="true" />
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
10
.idea/inspectionProfiles/Project_Default.xml
generated
10
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,10 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
|
||||||
<option name="processCode" value="true" />
|
|
||||||
<option name="processLiterals" value="true" />
|
|
||||||
<option name="processComments" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
7
.idea/kotlinCodeInsightSettings.xml
generated
Normal file
7
.idea/kotlinCodeInsightSettings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?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
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Kotlin2JvmCompilerArguments">
|
||||||
|
<option name="jvmTarget" value="1.8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@@ -1,6 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
3
.idea/scopes/Pupil.xml
generated
Normal file
3
.idea/scopes/Pupil.xml
generated
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<component name="DependencyValidationManager">
|
||||||
|
<scope name="Pupil" pattern="file[app]:*/" />
|
||||||
|
</component>
|
||||||
3
.idea/scopes/libpupil.xml
generated
Normal file
3
.idea/scopes/libpupil.xml
generated
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<component name="DependencyValidationManager">
|
||||||
|
<scope name="libpupil" pattern="file[libpupil]:*/" />
|
||||||
|
</component>
|
||||||
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
27
README.md
27
README.md
@@ -1,2 +1,27 @@
|
|||||||
# Pupil
|
# Pupil
|
||||||
Hitomi.la viewer for Android
|
|
||||||
|

|
||||||
|
*Pupil, Hitomi.la viewer for Android*
|
||||||
|
|
||||||
|
# Screenshot
|
||||||
|

|
||||||
|
*Main Screen*
|
||||||
|
|
||||||
|

|
||||||
|
*Reader Screen*
|
||||||
|
|
||||||
|
Images are censored to be SFW
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Go [Releases page](https://github.com/tom5079/Pupil/releases) and get latest version or
|
||||||
|
Visit [github page](https://tom5079.github.io/Pupil/) (only available in Korean)
|
||||||
|
or Build app yourself
|
||||||
|
|
||||||
|
# Manual
|
||||||
|
|
||||||
|
[Manual](https://tom5079.github.io/Pupil/2019/06/06/manual-kr.html) is only available in Korean. Consider using translator.
|
||||||
|
|
||||||
|
# Contribution
|
||||||
|
|
||||||
|
Any kind of contribution is appriciated. Feel free to leave PR!
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlinx-serialization'
|
apply plugin: 'kotlinx-serialization'
|
||||||
apply plugin: 'com.google.gms.google-services'
|
|
||||||
apply plugin: 'io.fabric'
|
if (file("google-services.json").exists()) {
|
||||||
apply plugin: 'com.google.firebase.firebase-perf'
|
logger.lifecycle("Firebase Enabled")
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
apply plugin: 'io.fabric'
|
||||||
|
apply plugin: 'com.google.firebase.firebase-perf'
|
||||||
|
} else {
|
||||||
|
logger.lifecycle("Firebase Disabled")
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
@@ -12,8 +19,8 @@ android {
|
|||||||
applicationId "xyz.quaver.pupil"
|
applicationId "xyz.quaver.pupil"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 20
|
versionCode 37
|
||||||
versionName "2.11.1"
|
versionName "5.4"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -23,10 +30,21 @@ android {
|
|||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
buildTypes.each {
|
||||||
|
it.buildConfigField('boolean', 'CENSOR', 'false')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
||||||
}
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
|
}
|
||||||
|
buildToolsVersion = '29.0.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -35,26 +53,36 @@ dependencies {
|
|||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.preference:preference:1.1.0-beta01'
|
implementation 'androidx.preference:preference:1.1.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation "androidx.biometric:biometric:1.0.1"
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||||
implementation 'com.google.firebase:firebase-core:17.0.0'
|
implementation 'com.google.android.material:material:1.2.0-alpha04'
|
||||||
implementation 'com.google.firebase:firebase-perf:18.0.1'
|
implementation 'com.google.firebase:firebase-core:17.2.2'
|
||||||
|
implementation 'com.google.firebase:firebase-perf:19.0.5'
|
||||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
||||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
||||||
implementation 'com.github.clans:fab:1.6.4'
|
implementation 'com.github.clans:fab:1.6.4'
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.10.0'
|
||||||
|
implementation('com.github.bumptech.glide:recyclerview-integration:4.11.0') {
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
|
implementation 'net.rdrei.android.dirchooser:library:3.2@aar'
|
||||||
|
implementation 'com.gu:option:1.3'
|
||||||
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||||
testImplementation 'junit:junit:4.12'
|
kapt 'com.github.bumptech.glide:compiler:4.10.0'
|
||||||
|
testImplementation 'junit:junit:4.13'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":20,"versionName":"2.11.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":37,"versionName":"5.4","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
||||||
@@ -1,19 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_VARIABLE")
|
||||||
|
|
||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.rule.ActivityTestRule
|
import androidx.test.rule.ActivityTestRule
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import xyz.quaver.hitomi.fetchNozomi
|
|
||||||
import xyz.quaver.hiyobi.cookie
|
import xyz.quaver.hiyobi.cookie
|
||||||
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.getReader
|
import xyz.quaver.hiyobi.getReader
|
||||||
import xyz.quaver.hiyobi.user_agent
|
import xyz.quaver.hiyobi.user_agent
|
||||||
import xyz.quaver.pupil.ui.LockActivity
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
|
import xyz.quaver.pupil.util.download.DownloadWorker
|
||||||
|
import xyz.quaver.pupil.util.getDownloadDirectory
|
||||||
|
import xyz.quaver.pupil.util.updateOldReaderGalleries
|
||||||
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
@@ -29,28 +58,27 @@ class ExampleInstrumentedTest {
|
|||||||
fun useAppContext() {
|
fun useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
Log.i("PUPILD", getDownloadDirectory(appContext).absolutePath ?: "")
|
||||||
assertEquals("xyz.quaver.pupil", appContext.packageName)
|
assertEquals("xyz.quaver.pupil", appContext.packageName)
|
||||||
|
|
||||||
Log.d("Pupil", fetchNozomi().first.size.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun checkCacheDir() {
|
fun checkCacheDir() {
|
||||||
val activityTestRule = ActivityTestRule<LockActivity>(LockActivity::class.java)
|
val activityTestRule = ActivityTestRule(LockActivity::class.java)
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
|
||||||
activityTestRule.launchActivity(Intent())
|
ContextCompat.getExternalFilesDirs(appContext, null).forEachIndexed { index, file ->
|
||||||
|
Log.i("PUPILD", "$index: ${file?.absolutePath}")
|
||||||
while(true);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_doSearch() {
|
fun test_doSearch() {
|
||||||
val reader = getReader(1426382)
|
val reader = getReader( 1426382)
|
||||||
|
|
||||||
val data: ByteArray
|
val data: ByteArray
|
||||||
|
|
||||||
with(URL(reader[0].url).openConnection() as HttpsURLConnection) {
|
with(URL(createImgList(1426382, reader)[0].path).openConnection() as HttpsURLConnection) {
|
||||||
setRequestProperty("User-Agent", user_agent)
|
setRequestProperty("User-Agent", user_agent)
|
||||||
setRequestProperty("Cookie", cookie)
|
setRequestProperty("Cookie", cookie)
|
||||||
|
|
||||||
@@ -59,4 +87,71 @@ class ExampleInstrumentedTest {
|
|||||||
|
|
||||||
Log.d("Pupil", data.size.toString())
|
Log.d("Pupil", data.size.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||||
|
@Test
|
||||||
|
fun test_deleteCodeFromReader() {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
|
||||||
|
val json = Json(JsonConfiguration.Stable)
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
getDownloadDirectory(context),
|
||||||
|
File(context.cacheDir, "imageCache")
|
||||||
|
).forEach { root ->
|
||||||
|
root.listFiles()?.forEach gallery@{ gallery ->
|
||||||
|
val reader = json.parseJson(File(gallery, "reader.json").apply {
|
||||||
|
if (!exists())
|
||||||
|
return@gallery
|
||||||
|
}.readText())
|
||||||
|
.jsonObject.toMutableMap()
|
||||||
|
|
||||||
|
Log.d("PUPILD", gallery.name)
|
||||||
|
|
||||||
|
reader.remove("code")
|
||||||
|
|
||||||
|
File(gallery, "reader.json").writeText(JsonObject(reader).toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_updateOldReader() {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
|
||||||
|
updateOldReaderGalleries(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_downloadWorker() {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
|
||||||
|
val galleryID = 515515
|
||||||
|
|
||||||
|
val worker = DownloadWorker.getInstance(context)
|
||||||
|
|
||||||
|
worker.queue.add(galleryID)
|
||||||
|
|
||||||
|
while(worker.progress.indexOfKey(galleryID) < 0 || worker.progress[galleryID] != null) {
|
||||||
|
Log.i("PUPILD", worker.progress[galleryID]?.joinToString(" ") ?: "null")
|
||||||
|
|
||||||
|
if (worker.progress[galleryID]?.all { !it.isFinite() } == true)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("PUPILD", "DONE!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getReaderOrNull() {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
|
||||||
|
val galleryID = 1561552
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
Log.i("PUPILD", Cache(context).getReader(galleryID)?.title ?: "null")
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.title ?: "null")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="xyz.quaver.pupil">
|
package="xyz.quaver.pupil">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="21" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Pupil"
|
android:name=".Pupil"
|
||||||
@@ -13,8 +18,22 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme"
|
||||||
<activity android:name=".ui.LockActivity"/>
|
tools:replace="android:theme">
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
<activity android:name=".ui.LockActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ReaderActivity"
|
android:name=".ui.ReaderActivity"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
@@ -37,18 +56,7 @@
|
|||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="히요비.asia"
|
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="xn--9w3b15m8vo.asia"
|
|
||||||
android:pathPrefix="/reader"
|
android:pathPrefix="/reader"
|
||||||
android:scheme="https" />
|
android:scheme="https" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -81,20 +89,9 @@
|
|||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="히요비.asia"
|
android:host="hiyobi.me"
|
||||||
android:pathPrefix="/reader"
|
android:scheme="http"
|
||||||
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="xn--9w3b15m8vo.asia"
|
|
||||||
android:pathPrefix="/reader"
|
|
||||||
android:scheme="http" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
@@ -122,6 +119,7 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,9 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@@ -18,7 +37,6 @@ import java.io.File
|
|||||||
class Pupil : MultiDexApplication() {
|
class Pupil : MultiDexApplication() {
|
||||||
|
|
||||||
lateinit var histories: Histories
|
lateinit var histories: Histories
|
||||||
lateinit var downloads: Histories
|
|
||||||
lateinit var favorites: Histories
|
lateinit var favorites: Histories
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -29,9 +47,19 @@ class Pupil : MultiDexApplication() {
|
|||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
histories = Histories(File(ContextCompat.getDataDir(this), "histories.json"))
|
histories = Histories(File(ContextCompat.getDataDir(this), "histories.json"))
|
||||||
downloads = Histories(File(ContextCompat.getDataDir(this), "downloads.json"))
|
|
||||||
favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json"))
|
favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json"))
|
||||||
|
|
||||||
|
val download = try {
|
||||||
|
preference.getString("dl_location", null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
preference.edit().remove("dl_location").apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (download == null) {
|
||||||
|
val default = ContextCompat.getExternalFilesDirs(this, null)[0]
|
||||||
|
preference.edit().putString("dl_location", Uri.fromFile(default).toString()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ProviderInstaller.installIfNeeded(this)
|
ProviderInstaller.installIfNeeded(this)
|
||||||
} catch (e: GooglePlayServicesRepairableException) {
|
} catch (e: GooglePlayServicesRepairableException) {
|
||||||
@@ -40,21 +68,23 @@ class Pupil : MultiDexApplication() {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preference.getBoolean("channel_created", false)) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_MIN).apply {
|
||||||
val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
|
description = getString(R.string.channel_download_description)
|
||||||
description = getString(R.string.channel_download_description)
|
enableLights(false)
|
||||||
enableLights(false)
|
enableVibration(false)
|
||||||
enableVibration(false)
|
lockscreenVisibility = Notification.VISIBILITY_SECRET
|
||||||
lockscreenVisibility = Notification.VISIBILITY_SECRET
|
|
||||||
}
|
|
||||||
manager.createNotificationChannel(channel)
|
|
||||||
}
|
}
|
||||||
|
manager.createNotificationChannel(channel)
|
||||||
preference.edit().putBoolean("channel_created", true).apply()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
|
AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) {
|
||||||
|
true -> AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
false -> AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
})
|
||||||
|
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.Log
|
import android.util.Base64
|
||||||
import android.util.SparseBooleanArray
|
import android.util.SparseBooleanArray
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.daimajia.swipe.SwipeLayout
|
||||||
|
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
||||||
|
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
|
||||||
import kotlinx.serialization.list
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.ReaderItem
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.util.Histories
|
import xyz.quaver.pupil.util.Histories
|
||||||
import xyz.quaver.pupil.util.getCachedGallery
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
import java.io.File
|
import xyz.quaver.pupil.util.download.DownloadWorker
|
||||||
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryBlock>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||||
|
|
||||||
enum class ViewType {
|
enum class ViewType {
|
||||||
NEXT,
|
NEXT,
|
||||||
@@ -45,10 +63,56 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
PREV
|
PREV
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val glide = Glide.with(context)
|
||||||
private lateinit var favorites: Histories
|
private lateinit var favorites: Histories
|
||||||
|
|
||||||
inner class GalleryViewHolder(private val view: CardView) : RecyclerView.ViewHolder(view) {
|
val timer = Timer()
|
||||||
fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
|
|
||||||
|
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
var timerTask: TimerTask? = null
|
||||||
|
|
||||||
|
fun updateProgress(context: Context, galleryID: Int) = CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
val cache = Cache(context).getCachedGallery(galleryID)
|
||||||
|
val reader = Cache(context).getReaderOrNull(galleryID)
|
||||||
|
|
||||||
|
if (reader == null) {
|
||||||
|
view.galleryblock_progressbar.visibility = View.GONE
|
||||||
|
view.galleryblock_progress_complete.visibility = View.GONE
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
with(view.galleryblock_progressbar) {
|
||||||
|
|
||||||
|
progress = cache?.listFiles()?.count { file ->
|
||||||
|
Regex("^[0-9]+.+\$").matches(file.name!!)
|
||||||
|
} ?: 0
|
||||||
|
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
max = reader.galleryInfo.size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress == max) {
|
||||||
|
if (completeFlag.get(galleryID, false)) {
|
||||||
|
with(view.galleryblock_progress_complete) {
|
||||||
|
setImageResource(R.drawable.ic_progressbar)
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
with(view.galleryblock_progress_complete) {
|
||||||
|
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply {
|
||||||
|
this?.start()
|
||||||
|
})
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
completeFlag.put(galleryID, true)
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
view.galleryblock_progress_complete.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(galleryBlock: GalleryBlock) {
|
||||||
with(view) {
|
with(view) {
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
val languages = resources.getStringArray(R.array.languages).map {
|
val languages = resources.getStringArray(R.array.languages).map {
|
||||||
@@ -57,89 +121,56 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
}
|
}
|
||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
val (galleryBlock: GalleryBlock, thumbnail: Deferred<String>) = item
|
|
||||||
|
|
||||||
val artists = galleryBlock.artists
|
val artists = galleryBlock.artists
|
||||||
val series = galleryBlock.series
|
val series = galleryBlock.series
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
galleryblock_thumbnail.setImageDrawable(CircularProgressDrawable(context).also {
|
||||||
val cache = thumbnail.await()
|
it.start()
|
||||||
|
})
|
||||||
|
|
||||||
if (!File(cache).exists())
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
return@launch
|
val thumbnail = Cache(context).getThumbnail(galleryBlock.id).let {
|
||||||
|
if (it != null)
|
||||||
val bitmap = BitmapFactory.decodeFile(thumbnail.await())
|
Base64.decode(it, Base64.DEFAULT)
|
||||||
|
else
|
||||||
launch(Dispatchers.Main) {
|
null
|
||||||
galleryblock_thumbnail.setImageBitmap(bitmap)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glide
|
||||||
|
.load(thumbnail)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.error(R.drawable.image_broken_variant)
|
||||||
|
.apply {
|
||||||
|
if (BuildConfig.CENSOR)
|
||||||
|
override(5, 8)
|
||||||
|
}
|
||||||
|
.into(galleryblock_thumbnail)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check cache
|
//Check cache
|
||||||
val readerCache = { File(getCachedGallery(context, galleryBlock.id), "reader.json") }
|
val cache = Cache(context).getCachedGallery(galleryBlock.id)
|
||||||
val imageCache = { File(getCachedGallery(context, galleryBlock.id), "images") }
|
val reader = Cache(context).getReaderOrNull(galleryBlock.id)
|
||||||
|
|
||||||
if (readerCache.invoke().exists()) {
|
if (cache != null && reader != null) {
|
||||||
val reader = Json(JsonConfiguration.Stable)
|
val count = cache.listFiles().count {
|
||||||
.parse(ReaderItem.serializer().list, readerCache.invoke().readText())
|
Regex("^[0-9]+.+\$").matches(it.name!!)
|
||||||
|
}
|
||||||
|
|
||||||
with(galleryblock_progressbar) {
|
with(galleryblock_progressbar) {
|
||||||
max = reader.size
|
max = reader.galleryInfo.size
|
||||||
progress = imageCache.invoke().list()?.size ?: 0
|
progress = count
|
||||||
|
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
} else {
|
} else
|
||||||
galleryblock_progressbar.visibility = View.GONE
|
galleryblock_progressbar.visibility = View.GONE
|
||||||
}
|
|
||||||
|
|
||||||
if (refreshTasks[this@GalleryViewHolder] == null) {
|
if (timerTask == null)
|
||||||
val refresh = Timer(false).schedule(0, 1000) {
|
timerTask = timer.schedule(0, 1000) {
|
||||||
post {
|
updateProgress(context, galleryBlock.id)
|
||||||
with(view.galleryblock_progressbar) {
|
|
||||||
progress = imageCache.invoke().list()?.size ?: 0
|
|
||||||
|
|
||||||
if (!readerCache.invoke().exists()) {
|
|
||||||
visibility = View.GONE
|
|
||||||
max = 0
|
|
||||||
progress = 0
|
|
||||||
|
|
||||||
view.galleryblock_progress_complete.visibility = View.INVISIBLE
|
|
||||||
} else {
|
|
||||||
if (visibility == View.GONE) {
|
|
||||||
val reader = Json(JsonConfiguration.Stable)
|
|
||||||
.parse(ReaderItem.serializer().list, readerCache.invoke().readText())
|
|
||||||
max = reader.size
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress == max) {
|
|
||||||
if (completeFlag.get(galleryBlock.id, false)) {
|
|
||||||
with(view.galleryblock_progress_complete) {
|
|
||||||
setImageResource(R.drawable.ic_progressbar)
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
with(view.galleryblock_progress_complete) {
|
|
||||||
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply {
|
|
||||||
this?.start()
|
|
||||||
})
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
completeFlag.put(galleryBlock.id, true)
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
view.galleryblock_progress_complete.visibility = View.INVISIBLE
|
|
||||||
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshTasks[this@GalleryViewHolder] = refresh
|
|
||||||
}
|
|
||||||
|
|
||||||
galleryblock_title.text = galleryBlock.title
|
galleryblock_title.text = galleryBlock.title
|
||||||
with(galleryblock_artist) {
|
with(galleryblock_artist) {
|
||||||
text = artists.joinToString(", ") { it.wordCapitalize() }
|
text = artists.joinToString(", ") { it.wordCapitalize() }
|
||||||
@@ -147,19 +178,6 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
artists.isNotEmpty() -> View.VISIBLE
|
artists.isNotEmpty() -> View.VISIBLE
|
||||||
else -> View.GONE
|
else -> View.GONE
|
||||||
}
|
}
|
||||||
setOnClickListener {
|
|
||||||
if (artists.size > 1) {
|
|
||||||
AlertDialog.Builder(context).apply {
|
|
||||||
setAdapter(ArrayAdapter(context, android.R.layout.select_dialog_item, artists)) { _, index ->
|
|
||||||
for (callback in onChipClickedHandler)
|
|
||||||
callback.invoke(Tag("artist", artists[index]))
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
} else {
|
|
||||||
for(callback in onChipClickedHandler)
|
|
||||||
callback.invoke(Tag("artist", artists.first()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
with(galleryblock_series) {
|
with(galleryblock_series) {
|
||||||
text =
|
text =
|
||||||
@@ -170,31 +188,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
series.isNotEmpty() -> View.VISIBLE
|
series.isNotEmpty() -> View.VISIBLE
|
||||||
else -> View.GONE
|
else -> View.GONE
|
||||||
}
|
}
|
||||||
setOnClickListener {
|
|
||||||
setOnClickListener {
|
|
||||||
if (series.size > 1) {
|
|
||||||
AlertDialog.Builder(context).apply {
|
|
||||||
setAdapter(ArrayAdapter(context, android.R.layout.select_dialog_item, series)) { _, index ->
|
|
||||||
for (callback in onChipClickedHandler)
|
|
||||||
callback.invoke(Tag("series", series[index]))
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
} else {
|
|
||||||
for(callback in onChipClickedHandler)
|
|
||||||
callback.invoke(Tag("series", series.first()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with(galleryblock_type) {
|
|
||||||
text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize()
|
|
||||||
setOnClickListener {
|
|
||||||
setOnClickListener {
|
|
||||||
for(callback in onChipClickedHandler)
|
|
||||||
callback.invoke(Tag("type", galleryBlock.type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
galleryblock_type.text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize()
|
||||||
with(galleryblock_language) {
|
with(galleryblock_language) {
|
||||||
text =
|
text =
|
||||||
resources.getString(R.string.galleryblock_language, languages[galleryBlock.language])
|
resources.getString(R.string.galleryblock_language, languages[galleryBlock.language])
|
||||||
@@ -202,48 +197,38 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
galleryBlock.language.isNotEmpty() -> View.VISIBLE
|
galleryBlock.language.isNotEmpty() -> View.VISIBLE
|
||||||
else -> View.GONE
|
else -> View.GONE
|
||||||
}
|
}
|
||||||
setOnClickListener {
|
|
||||||
setOnClickListener {
|
|
||||||
for(callback in onChipClickedHandler)
|
|
||||||
callback.invoke(Tag("language", galleryBlock.language))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryblock_tag_group.removeAllViews()
|
galleryblock_tag_group.removeAllViews()
|
||||||
galleryBlock.relatedTags.forEach {
|
galleryBlock.relatedTags.forEach {
|
||||||
val tag = Tag.parse(it).let { tag ->
|
galleryblock_tag_group.addView(Chip(context).apply {
|
||||||
when {
|
val tag = Tag.parse(it).let { tag ->
|
||||||
tag.area != null -> tag
|
when {
|
||||||
else -> Tag("tag", it)
|
tag.area != null -> tag
|
||||||
|
else -> Tag("tag", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val chip = LayoutInflater.from(context)
|
chipIcon = when(tag.area) {
|
||||||
.inflate(R.layout.tag_chip, this, false) as Chip
|
"male" -> {
|
||||||
|
setChipBackgroundColorResource(R.color.material_blue_700)
|
||||||
val icon = when(tag.area) {
|
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||||
"male" -> {
|
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
|
||||||
chip.setChipBackgroundColorResource(R.color.material_blue_700)
|
}
|
||||||
chip.setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
"female" -> {
|
||||||
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
|
setChipBackgroundColorResource(R.color.material_pink_600)
|
||||||
|
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
}
|
}
|
||||||
"female" -> {
|
text = tag.tag.wordCapitalize()
|
||||||
chip.setChipBackgroundColorResource(R.color.material_pink_600)
|
setEnsureMinTouchTargetSize(false)
|
||||||
chip.setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
setOnClickListener {
|
||||||
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
|
for (callback in onChipClickedHandler)
|
||||||
|
callback.invoke(tag)
|
||||||
}
|
}
|
||||||
else -> null
|
})
|
||||||
}
|
|
||||||
|
|
||||||
chip.chipIcon = icon
|
|
||||||
chip.text = tag.tag.wordCapitalize()
|
|
||||||
chip.setOnClickListener {
|
|
||||||
for (callback in onChipClickedHandler)
|
|
||||||
callback.invoke(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
galleryblock_tag_group.addView(chip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryblock_id.text = galleryBlock.id.toString()
|
galleryblock_id.text = galleryBlock.id.toString()
|
||||||
@@ -295,19 +280,11 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.wordCapitalize() : String {
|
|
||||||
val result = ArrayList<String>()
|
|
||||||
|
|
||||||
for (word in this.split(" "))
|
|
||||||
result.add(word.capitalize())
|
|
||||||
|
|
||||||
return result.joinToString(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
private val refreshTasks = HashMap<GalleryViewHolder, TimerTask>()
|
|
||||||
val completeFlag = SparseBooleanArray()
|
val completeFlag = SparseBooleanArray()
|
||||||
|
|
||||||
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
|
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
|
||||||
|
var onDownloadClickedHandler: ((Int) -> Unit)? = null
|
||||||
|
var onDeleteClickedHandler: ((Int) -> Unit)? = null
|
||||||
|
|
||||||
var showNext = false
|
var showNext = false
|
||||||
var showPrev = false
|
var showPrev = false
|
||||||
@@ -333,18 +310,56 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
if (holder is GalleryViewHolder)
|
if (holder is GalleryViewHolder) {
|
||||||
holder.bind(galleries[position-(if (showPrev) 1 else 0)])
|
val gallery = galleries[position-(if (showPrev) 1 else 0)]
|
||||||
|
|
||||||
|
holder.bind(gallery)
|
||||||
|
|
||||||
|
with(holder.view.galleryblock_primary) {
|
||||||
|
setOnClickListener {
|
||||||
|
holder.view.performClick()
|
||||||
|
}
|
||||||
|
setOnLongClickListener {
|
||||||
|
holder.view.performLongClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.view.galleryblock_download.setOnClickListener {
|
||||||
|
onDownloadClickedHandler?.invoke(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.view.galleryblock_delete.setOnClickListener {
|
||||||
|
onDeleteClickedHandler?.invoke(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
mItemManger.bindView(holder.view, position)
|
||||||
|
|
||||||
|
holder.view.galleryblock_swipe_layout.addSwipeListener(object: SwipeLayout.SwipeListener {
|
||||||
|
override fun onStartOpen(layout: SwipeLayout?) {
|
||||||
|
mItemManger.closeAllExcept(layout)
|
||||||
|
|
||||||
|
holder.view.galleryblock_download.text =
|
||||||
|
if (DownloadWorker.getInstance(holder.view.context).progress.indexOfKey(gallery.id) < 0)
|
||||||
|
holder.view.context.getString(R.string.main_download)
|
||||||
|
else
|
||||||
|
holder.view.context.getString(android.R.string.cancel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClose(layout: SwipeLayout?) {}
|
||||||
|
override fun onHandRelease(layout: SwipeLayout?, xvel: Float, yvel: Float) {}
|
||||||
|
override fun onOpen(layout: SwipeLayout?) {}
|
||||||
|
override fun onStartClose(layout: SwipeLayout?) {}
|
||||||
|
override fun onUpdate(layout: SwipeLayout?, leftOffset: Int, topOffset: Int) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||||
super.onViewDetachedFromWindow(holder)
|
super.onViewDetachedFromWindow(holder)
|
||||||
|
|
||||||
if (holder is GalleryViewHolder) {
|
if (holder is GalleryViewHolder) {
|
||||||
val task = refreshTasks[holder] ?: return
|
holder.timerTask?.cancel()
|
||||||
|
holder.timerTask = null
|
||||||
task.cancel()
|
|
||||||
refreshTasks.remove(holder)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,4 +375,6 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
else -> ViewType.GALLERY
|
else -> ViewType.GALLERY
|
||||||
}.ordinal
|
}.ordinal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getSwipeLayoutResourceId(position: Int) = R.id.galleryblock_swipe_layout
|
||||||
}
|
}
|
||||||
85
app/src/main/java/xyz/quaver/pupil/adapters/MirrorAdapter.kt
Normal file
85
app/src/main/java/xyz/quaver/pupil/adapters/MirrorAdapter.kt
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.android.synthetic.main.item_mirrors.view.*
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class MirrorAdapter(context: Context) : RecyclerView.Adapter<MirrorAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
|
val mirrors = context.resources.getStringArray(R.array.mirrors).map {
|
||||||
|
it.split('|').let { split ->
|
||||||
|
Pair(split.first(), split.last())
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
val list = mirrors.keys.toMutableList().apply {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getString("mirrors", "")!!
|
||||||
|
.split(">")
|
||||||
|
.reversed()
|
||||||
|
.forEach {
|
||||||
|
if (this.contains(it)) {
|
||||||
|
this.remove(it)
|
||||||
|
this.add(0, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val onItemMove : ((Int, Int) -> Unit) = { from, to ->
|
||||||
|
Collections.swap(list, from, to)
|
||||||
|
notifyItemMoved(from, to)
|
||||||
|
onItemMoved?.invoke(list)
|
||||||
|
}
|
||||||
|
var onStartDrag : ((ViewHolder) -> Unit)? = null
|
||||||
|
var onItemMoved : ((List<String>) -> (Unit))? = null
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
with(holder.view) {
|
||||||
|
mirror_name.text = mirrors[list.elementAt(position)]
|
||||||
|
mirror_button.setOnTouchListener { _, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_DOWN)
|
||||||
|
onStartDrag?.invoke(holder)
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
return LayoutInflater.from(parent.context).inflate(
|
||||||
|
R.layout.item_mirrors, parent, false
|
||||||
|
).let {
|
||||||
|
ViewHolder(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = mirrors.size
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,73 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.crashlytics.android.Crashlytics
|
||||||
|
import io.fabric.sdk.android.Fabric
|
||||||
|
import kotlinx.android.synthetic.main.item_reader.view.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.quaver.hitomi.Reader
|
||||||
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
|
import xyz.quaver.pupil.util.download.DownloadWorker
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
class ReaderAdapter(private val context: Context,
|
||||||
|
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||||
|
|
||||||
var isFullScreen = false
|
var isFullScreen = false
|
||||||
|
|
||||||
|
var reader: Reader? = null
|
||||||
|
private val glide = Glide.with(context)
|
||||||
|
val timer = Timer()
|
||||||
|
|
||||||
|
var onItemClickListener : ((Int) -> (Unit))? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
reader = Cache(context).getReader(galleryID)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
LayoutInflater.from(parent.context).inflate(
|
return LayoutInflater.from(parent.context).inflate(
|
||||||
R.layout.item_reader, parent, false
|
R.layout.item_reader, parent, false
|
||||||
).let {
|
).let {
|
||||||
return ViewHolder(it)
|
ViewHolder(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.view as ConstraintLayout
|
||||||
|
|
||||||
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
|
if (isFullScreen)
|
||||||
// Raw height and width of image
|
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
|
||||||
val (height: Int, width: Int) = options.run { outHeight to outWidth }
|
else
|
||||||
var inSampleSize = 1
|
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
|
||||||
|
|
||||||
if (height > reqHeight || width > reqWidth) {
|
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
||||||
|
onItemClickListener?.invoke(position)
|
||||||
val halfHeight: Int = height / 2
|
|
||||||
val halfWidth: Int = width / 2
|
|
||||||
|
|
||||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
|
||||||
// height and width larger than the requested height and width.
|
|
||||||
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
|
|
||||||
inSampleSize *= 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inSampleSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
with(holder.view as ImageView) {
|
holder.view.container.setOnClickListener {
|
||||||
val options = BitmapFactory.Options()
|
onItemClickListener?.invoke(position)
|
||||||
|
}
|
||||||
|
|
||||||
options.inJustDecodeBounds = true
|
(holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
|
||||||
BitmapFactory.decodeFile(images[position], options)
|
.dimensionRatio = "${reader!!.galleryInfo[position].width}:${reader!!.galleryInfo[position].height}"
|
||||||
|
|
||||||
val (reqWidth, reqHeight) = context.resources.displayMetrics.let {
|
holder.view.reader_index.text = (position+1).toString()
|
||||||
Pair(it.widthPixels, it.heightPixels)
|
|
||||||
|
val images = Cache(context).getImages(galleryID)
|
||||||
|
|
||||||
|
if (images?.get(position) != null) {
|
||||||
|
glide
|
||||||
|
.load(images[position]?.uri)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.error(R.drawable.image_broken_variant)
|
||||||
|
.apply {
|
||||||
|
if (BuildConfig.CENSOR)
|
||||||
|
override(5, 8)
|
||||||
|
}
|
||||||
|
.into(holder.view.image)
|
||||||
|
} else {
|
||||||
|
val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
|
||||||
|
|
||||||
|
if (progress?.isNaN() == true) {
|
||||||
|
|
||||||
|
if (Fabric.isInitialized())
|
||||||
|
Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position))
|
||||||
|
|
||||||
|
glide
|
||||||
|
.load(R.drawable.image_broken_variant)
|
||||||
|
.into(holder.view.image)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
|
holder.view.reader_item_progressbar.progress =
|
||||||
|
if (progress?.isInfinite() == true)
|
||||||
|
100
|
||||||
|
else
|
||||||
|
progress?.roundToInt() ?: 0
|
||||||
|
|
||||||
options.inPreferredConfig = Bitmap.Config.RGB_565
|
holder.view.image.setImageDrawable(null)
|
||||||
|
|
||||||
options.inJustDecodeBounds = false
|
|
||||||
|
|
||||||
val image = BitmapFactory.decodeFile(images[position], options)
|
timer.schedule(1000) {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
setImageBitmap(image)
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = images.size
|
override fun getItemCount() = reader?.galleryInfo?.size ?: 0
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
|
import xyz.quaver.pupil.BuildConfig
|
||||||
|
|
||||||
|
class ThumbnailAdapter(private val glide: RequestManager, private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
return ViewHolder(ImageView(parent.context))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
glide
|
||||||
|
.load(thumbnails[position])
|
||||||
|
.apply {
|
||||||
|
if (BuildConfig.CENSOR)
|
||||||
|
override(5, 8)
|
||||||
|
}
|
||||||
|
.into(holder.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = thumbnails.size
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
@@ -5,7 +23,7 @@ import kotlinx.android.parcel.Parcelize
|
|||||||
import xyz.quaver.hitomi.Suggestion
|
import xyz.quaver.hitomi.Suggestion
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class TagSuggestion constructor(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
|
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
|
||||||
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
||||||
|
|
||||||
override fun getBody(): String {
|
override fun getBody(): String {
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -90,8 +108,8 @@ class Tags(tag: List<Tag?>?) : ArrayList<Tag>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeByArea(area: String) {
|
fun removeByArea(area: String, isNegative: Boolean? = null) {
|
||||||
filter { it.area == area }.forEach {
|
filter { it.area == area && (if(isNegative == null) true else (it.isNegative == isNegative)) }.forEach {
|
||||||
remove(it)
|
remove(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.andrognito.patternlockview.PatternLockView
|
import com.andrognito.patternlockview.PatternLockView
|
||||||
@@ -8,6 +27,7 @@ import com.google.android.material.snackbar.Snackbar
|
|||||||
import kotlinx.android.synthetic.main.activity_lock.*
|
import kotlinx.android.synthetic.main.activity_lock.*
|
||||||
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
|
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.ui.fragment.PatternLockFragment
|
||||||
import xyz.quaver.pupil.util.Lock
|
import xyz.quaver.pupil.util.Lock
|
||||||
import xyz.quaver.pupil.util.LockManager
|
import xyz.quaver.pupil.util.LockManager
|
||||||
|
|
||||||
@@ -17,7 +37,18 @@ class LockActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_lock)
|
setContentView(R.layout.activity_lock)
|
||||||
|
|
||||||
val lockManager = LockManager(this)
|
val lockManager = try {
|
||||||
|
LockManager(this)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
AlertDialog.Builder(this).apply {
|
||||||
|
setTitle(R.string.warning)
|
||||||
|
setMessage(R.string.lock_corrupted)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val mode = intent.getStringExtra("mode")
|
val mode = intent.getStringExtra("mode")
|
||||||
|
|
||||||
@@ -28,9 +59,10 @@ class LockActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
when(mode) {
|
when(mode) {
|
||||||
null -> {
|
null -> {
|
||||||
if (lockManager.empty()) {
|
if (lockManager.isEmpty()) {
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"add_lock" -> {
|
"add_lock" -> {
|
||||||
|
|||||||
@@ -1,15 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.*
|
import android.text.*
|
||||||
import android.text.style.AlignmentSpan
|
import android.text.style.AlignmentSpan
|
||||||
import android.view.*
|
import android.view.KeyEvent
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
@@ -17,7 +37,6 @@ import android.widget.TextView
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
@@ -27,34 +46,33 @@ import com.arlib.floatingsearchview.FloatingSearchView
|
|||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import com.arlib.floatingsearchview.util.view.SearchInputView
|
import com.arlib.floatingsearchview.util.view.SearchInputView
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.activity_main_content.*
|
import kotlinx.android.synthetic.main.activity_main_content.*
|
||||||
import kotlinx.android.synthetic.main.dialog_galleryblock.view.*
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import kotlinx.serialization.json.JsonObject
|
|
||||||
import kotlinx.serialization.json.content
|
|
||||||
import kotlinx.serialization.list
|
import kotlinx.serialization.list
|
||||||
import kotlinx.serialization.stringify
|
import kotlinx.serialization.stringify
|
||||||
import ru.noties.markwon.Markwon
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.*
|
import xyz.quaver.hitomi.doSearch
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||||
|
import xyz.quaver.hitomi.getSuggestionsForQuery
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.types.TagSuggestion
|
import xyz.quaver.pupil.types.TagSuggestion
|
||||||
import xyz.quaver.pupil.types.Tags
|
import xyz.quaver.pupil.types.Tags
|
||||||
|
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
|
import xyz.quaver.pupil.util.download.DownloadWorker
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.net.URL
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.net.ssl.HttpsURLConnection
|
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@@ -67,16 +85,24 @@ class MainActivity : AppCompatActivity() {
|
|||||||
FAVORITE
|
FAVORITE
|
||||||
}
|
}
|
||||||
|
|
||||||
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
|
enum class SortMode {
|
||||||
|
NEWEST,
|
||||||
|
POPULAR
|
||||||
|
}
|
||||||
|
|
||||||
|
private val galleries = ArrayList<GalleryBlock>()
|
||||||
|
|
||||||
private var query = ""
|
private var query = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
findViewById<SearchInputView>(R.id.search_bar_text)
|
with(findViewById<SearchInputView>(R.id.search_bar_text)) {
|
||||||
.setText(query, TextView.BufferType.EDITABLE)
|
if (text.toString() != value)
|
||||||
|
setText(query, TextView.BufferType.EDITABLE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var mode = Mode.SEARCH
|
private var mode = Mode.SEARCH
|
||||||
|
private var sortMode = SortMode.NEWEST
|
||||||
|
|
||||||
private val REQUEST_SETTINGS = 45162
|
private val REQUEST_SETTINGS = 45162
|
||||||
private val REQUEST_LOCK = 561
|
private val REQUEST_LOCK = 561
|
||||||
@@ -87,15 +113,27 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
|
|
||||||
private lateinit var histories: Histories
|
private lateinit var histories: Histories
|
||||||
private lateinit var downloads: Histories
|
|
||||||
private lateinit var favorites: Histories
|
private lateinit var favorites: Histories
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK)
|
val lockManager = try {
|
||||||
|
LockManager(this)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.app.AlertDialog.Builder(this).apply {
|
||||||
|
setTitle(R.string.warning)
|
||||||
|
setMessage(R.string.lock_corrupted)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
|
||||||
checkPermissions()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lockManager.isNotEmpty())
|
||||||
|
startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK)
|
||||||
|
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
@@ -113,13 +151,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
with(application as Pupil) {
|
with(application as Pupil) {
|
||||||
this@MainActivity.histories = histories
|
this@MainActivity.histories = histories
|
||||||
this@MainActivity.downloads = downloads
|
|
||||||
this@MainActivity.favorites = favorites
|
this@MainActivity.favorites = favorites
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
checkUpdate()
|
checkUpdate(this)
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
}
|
}
|
||||||
@@ -132,13 +169,19 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
else -> super.onBackPressed()
|
else -> super.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
|
||||||
|
(main_recyclerview.adapter as GalleryBlockAdapter).timer.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
@@ -155,23 +198,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val perPage = preference.getString("per_page", "25")!!.toInt()
|
val perPage = preference.getString("per_page", "25")!!.toInt()
|
||||||
val maxPage = Math.ceil(totalItems / perPage.toDouble()).roundToInt()
|
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
|
|
||||||
return when(keyCode) {
|
return when(keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
|
||||||
if (currentPage < maxPage) {
|
|
||||||
runOnUiThread {
|
|
||||||
currentPage++
|
|
||||||
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
if (currentPage > 0) {
|
if (currentPage > 0) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -179,7 +208,21 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
|
if (currentPage < maxPage) {
|
||||||
|
runOnUiThread {
|
||||||
|
currentPage++
|
||||||
|
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,7 +240,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,75 +251,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkUpdate() {
|
|
||||||
|
|
||||||
fun extractReleaseNote(update: JsonObject, locale: String) : String {
|
|
||||||
val markdown = update["body"]!!.content
|
|
||||||
|
|
||||||
val target = when(locale) {
|
|
||||||
"ko" -> "한국어"
|
|
||||||
"ja" -> "日本語"
|
|
||||||
else -> "English"
|
|
||||||
}
|
|
||||||
|
|
||||||
val releaseNote = Regex("^# Release Note.+$")
|
|
||||||
val language = Regex("^## $target$")
|
|
||||||
val end = Regex("^#.+$")
|
|
||||||
|
|
||||||
var releaseNoteFlag = false
|
|
||||||
var languageFlag = false
|
|
||||||
|
|
||||||
val result = StringBuilder()
|
|
||||||
|
|
||||||
for(line in markdown.lines()) {
|
|
||||||
if (releaseNote.matches(line)) {
|
|
||||||
releaseNoteFlag = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (releaseNoteFlag) {
|
|
||||||
if (language.matches(line)) {
|
|
||||||
languageFlag = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (languageFlag) {
|
|
||||||
if (end.matches(line))
|
|
||||||
break
|
|
||||||
|
|
||||||
result.append(line+"\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return getString(R.string.update_release_note, update["tag_name"]?.content, result.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
|
||||||
val update =
|
|
||||||
checkUpdate(getString(R.string.release_url), BuildConfig.VERSION_NAME) ?: return@launch
|
|
||||||
|
|
||||||
val dialog = AlertDialog.Builder(this@MainActivity).apply {
|
|
||||||
setTitle(R.string.update_title)
|
|
||||||
val msg = extractReleaseNote(update, Locale.getDefault().language)
|
|
||||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.update))))
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.no) { _, _ ->}
|
|
||||||
}
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
dialog.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkPermissions() {
|
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
|
|
||||||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 13489)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
var prevP1 = 0
|
var prevP1 = 0
|
||||||
main_appbar_layout.addOnOffsetChangedListener(
|
main_appbar_layout.addOnOffsetChangedListener(
|
||||||
@@ -284,6 +258,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
main_searchview.translationY = p1.toFloat()
|
main_searchview.translationY = p1.toFloat()
|
||||||
main_recyclerview.scrollBy(0, prevP1 - p1)
|
main_recyclerview.scrollBy(0, prevP1 - p1)
|
||||||
|
|
||||||
|
with(main_fab) {
|
||||||
|
if (prevP1 > p1)
|
||||||
|
hideMenuButton(true)
|
||||||
|
else if (prevP1 < p1)
|
||||||
|
showMenuButton(true)
|
||||||
|
}
|
||||||
|
|
||||||
prevP1 = p1
|
prevP1 = p1
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -300,7 +281,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
currentPage = 0
|
currentPage = 0
|
||||||
query = ""
|
query = ""
|
||||||
mode = Mode.SEARCH
|
mode = Mode.SEARCH
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
R.id.main_drawer_history -> {
|
R.id.main_drawer_history -> {
|
||||||
@@ -309,7 +290,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
currentPage = 0
|
currentPage = 0
|
||||||
query = ""
|
query = ""
|
||||||
mode = Mode.HISTORY
|
mode = Mode.HISTORY
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
R.id.main_drawer_downloads -> {
|
R.id.main_drawer_downloads -> {
|
||||||
@@ -318,7 +299,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
currentPage = 0
|
currentPage = 0
|
||||||
query = ""
|
query = ""
|
||||||
mode = Mode.DOWNLOAD
|
mode = Mode.DOWNLOAD
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
R.id.main_drawer_favorite -> {
|
R.id.main_drawer_favorite -> {
|
||||||
@@ -327,7 +308,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
currentPage = 0
|
currentPage = 0
|
||||||
query = ""
|
query = ""
|
||||||
mode = Mode.FAVORITE
|
mode = Mode.FAVORITE
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
R.id.main_drawer_help -> {
|
R.id.main_drawer_help -> {
|
||||||
@@ -343,7 +324,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
||||||
}
|
}
|
||||||
R.id.main_drawer_kakaotalk -> {
|
R.id.main_drawer_kakaotalk -> {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.kakaotalk))))
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,15 +332,67 @@ class MainActivity : AppCompatActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(main_fab_jump) {
|
||||||
|
setImageResource(R.drawable.ic_jump)
|
||||||
|
setOnClickListener {
|
||||||
|
val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val perPage = preference.getString("per_page", "25")!!.toInt()
|
||||||
|
val editText = EditText(context)
|
||||||
|
|
||||||
|
AlertDialog.Builder(context).apply {
|
||||||
|
setView(editText)
|
||||||
|
setTitle(R.string.main_jump_title)
|
||||||
|
setMessage(getString(
|
||||||
|
R.string.main_jump_message,
|
||||||
|
currentPage+1,
|
||||||
|
ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
|
))
|
||||||
|
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(main_fab_id) {
|
||||||
|
setImageResource(R.drawable.numeric)
|
||||||
|
setOnClickListener {
|
||||||
|
val editText = EditText(context)
|
||||||
|
|
||||||
|
AlertDialog.Builder(context).apply {
|
||||||
|
setView(editText)
|
||||||
|
setTitle(R.string.main_open_gallery_by_id)
|
||||||
|
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
val galleryID = editText.text.toString().toInt()
|
||||||
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
|
||||||
|
putExtra("galleryID", galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
|
||||||
|
histories.add(galleryID)
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupSearchBar()
|
setupSearchBar()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
with(main_recyclerview) {
|
with(main_recyclerview) {
|
||||||
adapter = GalleryBlockAdapter(galleries).apply {
|
adapter = GalleryBlockAdapter(this@MainActivity, galleries).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
@@ -367,85 +400,95 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
fetchGalleries(query, sortMode)
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onDownloadClickedHandler = { position ->
|
||||||
|
val galleryID = galleries[position].id
|
||||||
|
|
||||||
|
if (!completeFlag.get(galleryID, false)) {
|
||||||
|
val worker = DownloadWorker.getInstance(context)
|
||||||
|
|
||||||
|
if (worker.progress.indexOfKey(galleryID) >= 0) //download in progress
|
||||||
|
worker.cancel(galleryID)
|
||||||
|
else {
|
||||||
|
Cache(context).setDownloading(galleryID, true)
|
||||||
|
|
||||||
|
if (!worker.queue.contains(galleryID))
|
||||||
|
worker.queue.add(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAllItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteClickedHandler = { position ->
|
||||||
|
val galleryID = galleries[position].id
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
DownloadWorker.getInstance(context).cancel(galleryID)
|
||||||
|
|
||||||
|
var cache = Cache(context).getCachedGallery(galleryID)
|
||||||
|
|
||||||
|
while (cache != null) {
|
||||||
|
cache.deleteRecursively()
|
||||||
|
cache = Cache(context).getCachedGallery(galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
histories.remove(galleryID)
|
||||||
|
|
||||||
|
if (this@MainActivity.mode != Mode.SEARCH)
|
||||||
|
runOnUiThread {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
completeFlag.put(galleryID, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAllItems()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ItemClickSupport.addTo(this)
|
ItemClickSupport.addTo(this)
|
||||||
.setOnItemClickListener { _, position, v ->
|
.setOnItemClickListener { _, position, v ->
|
||||||
|
|
||||||
if (v !is CardView)
|
if (v !is CardView)
|
||||||
return@setOnItemClickListener
|
return@setOnItemClickListener
|
||||||
|
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||||
val gallery = galleries[position].first
|
val gallery = galleries[position]
|
||||||
intent.putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery))
|
intent.putExtra("galleryID", gallery.id)
|
||||||
|
|
||||||
//TODO: Maybe sprinke some transitions will be nice :D
|
//TODO: Maybe sprinkling some transitions will be nice :D
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
|
|
||||||
histories.add(gallery.id)
|
histories.add(gallery.id)
|
||||||
}.setOnItemLongClickListener { recyclerView, position, v ->
|
}.setOnItemLongClickListener { _, position, v ->
|
||||||
|
|
||||||
if (v !is CardView)
|
if (v !is CardView)
|
||||||
return@setOnItemLongClickListener true
|
return@setOnItemLongClickListener true
|
||||||
|
|
||||||
val galleryBlock = galleries[position].first
|
val galleryID = galleries[position].id
|
||||||
val view = LayoutInflater.from(this@MainActivity)
|
|
||||||
.inflate(R.layout.dialog_galleryblock, recyclerView, false)
|
|
||||||
|
|
||||||
val dialog = AlertDialog.Builder(this@MainActivity).apply {
|
GalleryDialog(
|
||||||
setView(view)
|
this@MainActivity,
|
||||||
}.create()
|
galleryID
|
||||||
|
).apply {
|
||||||
|
onChipClickedHandler.add {
|
||||||
|
runOnUiThread {
|
||||||
|
query = it.toQuery()
|
||||||
|
currentPage = 0
|
||||||
|
|
||||||
with(view.main_dialog_download) {
|
cancelFetch()
|
||||||
text = when(GalleryDownloader.get(galleryBlock.id)) {
|
clearGalleries()
|
||||||
null -> getString(R.string.reader_fab_download)
|
fetchGalleries(query, sortMode)
|
||||||
else -> getString(R.string.reader_fab_download_cancel)
|
loadBlocks()
|
||||||
}
|
|
||||||
isEnabled = !(adapter as GalleryBlockAdapter).completeFlag.get(galleryBlock.id, false)
|
|
||||||
setOnClickListener {
|
|
||||||
val downloader = GalleryDownloader.get(galleryBlock.id)
|
|
||||||
if (downloader == null)
|
|
||||||
GalleryDownloader(context, galleryBlock, true).start()
|
|
||||||
else {
|
|
||||||
downloader.cancel()
|
|
||||||
downloader.clearNotification()
|
|
||||||
}
|
}
|
||||||
|
dismiss()
|
||||||
dialog.dismiss()
|
|
||||||
}
|
}
|
||||||
}
|
}.show()
|
||||||
|
|
||||||
view.main_dialog_delete.setOnClickListener {
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
|
||||||
with(GalleryDownloader[galleryBlock.id]) {
|
|
||||||
this?.cancelAndJoin()
|
|
||||||
this?.clearNotification()
|
|
||||||
}
|
|
||||||
val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
|
|
||||||
val data = getCachedGallery(context, galleryBlock.id)
|
|
||||||
cache.deleteRecursively()
|
|
||||||
data.deleteRecursively()
|
|
||||||
|
|
||||||
downloads.remove(galleryBlock.id)
|
|
||||||
|
|
||||||
if (mode == Mode.DOWNLOAD) {
|
|
||||||
runOnUiThread {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(adapter as GalleryBlockAdapter).completeFlag.put(galleryBlock.id, false)
|
|
||||||
}
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.show()
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -503,7 +546,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
fetchGalleries(query)
|
|
||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +625,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
//BOTTOM
|
//BOTTOM
|
||||||
|
|
||||||
//Scrolling DOWN
|
//Scrolling DOWN
|
||||||
if (dist < 0 && currentPage != Math.ceil(totalItems.toDouble()/perPage).roundToInt()-1) {
|
if (dist < 0 && currentPage != ceil(totalItems.toDouble()/perPage).roundToInt()-1) {
|
||||||
with(main_recyclerview.adapter as GalleryBlockAdapter) {
|
with(main_recyclerview.adapter as GalleryBlockAdapter) {
|
||||||
if(!showNext) {
|
if(!showNext) {
|
||||||
showNext = true
|
showNext = true
|
||||||
@@ -595,13 +637,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
getChildAt(childCount-1)
|
getChildAt(childCount-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val absDist = Math.abs(dist)
|
val absDist = abs(dist)
|
||||||
|
|
||||||
if (next is LinearLayout) {
|
if (next is LinearLayout) {
|
||||||
val icon = next.findViewById<ImageView>(R.id.icon_next)
|
val icon = next.findViewById<ImageView>(R.id.icon_next)
|
||||||
val text = next.findViewById<TextView>(R.id.text_next).apply {
|
val text = next.findViewById<TextView>(R.id.text_next).apply {
|
||||||
text = getString(R.string.main_move, currentPage+2)
|
text = getString(R.string.main_move, currentPage+2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (absDist < 360) {
|
if (absDist < 360) {
|
||||||
next.layoutParams.height = (absDist/2).roundToInt()
|
next.layoutParams.height = (absDist/2).roundToInt()
|
||||||
icon.layoutParams.height = (absDist/2).roundToInt()
|
icon.layoutParams.height = (absDist/2).roundToInt()
|
||||||
@@ -609,8 +652,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
text.layoutParams.width = absDist.roundToInt()
|
text.layoutParams.width = absDist.roundToInt()
|
||||||
|
|
||||||
target = -1
|
target = -1
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
next.layoutParams.height = 180
|
next.layoutParams.height = 180
|
||||||
icon.layoutParams.height = 180
|
icon.layoutParams.height = 180
|
||||||
icon.rotation = 0f
|
icon.rotation = 0f
|
||||||
@@ -673,9 +715,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
s ?: return
|
s ?: return
|
||||||
|
|
||||||
if (s.any { it.isUpperCase() })
|
if (s.any { it.isUpperCase() })
|
||||||
s.replace(0, s.length, s.toString().toLowerCase())
|
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault()))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
||||||
|
|
||||||
with(main_searchview as FloatingSearchView) {
|
with(main_searchview as FloatingSearchView) {
|
||||||
val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json")
|
val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json")
|
||||||
@@ -690,72 +733,52 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when(it.itemId) {
|
when(it.itemId) {
|
||||||
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
|
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
|
||||||
R.id.main_menu_jump -> {
|
R.id.main_menu_sort_newest -> {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
sortMode = SortMode.NEWEST
|
||||||
val perPage = preference.getString("per_page", "25")!!.toInt()
|
it.isChecked = true
|
||||||
val editText = EditText(context)
|
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
runOnUiThread {
|
||||||
setView(editText)
|
currentPage = 0
|
||||||
setTitle(R.string.main_jump_title)
|
|
||||||
setMessage(getString(
|
|
||||||
R.string.main_jump_message,
|
|
||||||
currentPage+1,
|
|
||||||
Math.ceil(totalItems / perPage.toDouble()).roundToInt()
|
|
||||||
))
|
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
cancelFetch()
|
||||||
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
runOnUiThread {
|
loadBlocks()
|
||||||
cancelFetch()
|
}
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
}
|
}
|
||||||
R.id.main_menu_id -> {
|
R.id.main_menu_sort_popular -> {
|
||||||
val editText = EditText(context)
|
sortMode = SortMode.POPULAR
|
||||||
|
it.isChecked = true
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
runOnUiThread {
|
||||||
setView(editText)
|
currentPage = 0
|
||||||
setTitle(R.string.main_open_gallery_by_id)
|
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
cancelFetch()
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
clearGalleries()
|
||||||
try {
|
fetchGalleries(query, sortMode)
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
loadBlocks()
|
||||||
val gallery =
|
}
|
||||||
getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception()
|
|
||||||
intent.putExtra(
|
|
||||||
"galleryblock",
|
|
||||||
Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery)
|
|
||||||
)
|
|
||||||
|
|
||||||
startActivity(intent)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Snackbar.make(main_layout,
|
|
||||||
R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnQueryChangeListener { _, query ->
|
setOnQueryChangeListener { _, query ->
|
||||||
clearSuggestions()
|
this@MainActivity.query = query
|
||||||
|
|
||||||
if (query.isEmpty() or query.endsWith(' '))
|
|
||||||
return@setOnQueryChangeListener
|
|
||||||
|
|
||||||
val currentQuery = query.split(" ").last().replace('_', ' ')
|
|
||||||
|
|
||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
|
|
||||||
|
clearSuggestions()
|
||||||
|
|
||||||
|
if (query.isEmpty() or query.endsWith(' ')) {
|
||||||
|
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
||||||
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
|
})
|
||||||
|
|
||||||
|
return@setOnQueryChangeListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentQuery = query.split(" ").last().replace('_', ' ')
|
||||||
|
|
||||||
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) })
|
val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) })
|
||||||
|
|
||||||
@@ -774,13 +797,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ ->
|
setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ ->
|
||||||
val suggestion = item as TagSuggestion
|
item as TagSuggestion
|
||||||
val tag = "${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")}"
|
|
||||||
|
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
|
||||||
|
|
||||||
leftIcon.setImageDrawable(
|
leftIcon.setImageDrawable(
|
||||||
ResourcesCompat.getDrawable(
|
ResourcesCompat.getDrawable(
|
||||||
resources,
|
resources,
|
||||||
when(suggestion.n) {
|
when(item.n) {
|
||||||
"female" -> R.drawable.ic_gender_female
|
"female" -> R.drawable.ic_gender_female
|
||||||
"male" -> R.drawable.ic_gender_male
|
"male" -> R.drawable.ic_gender_male
|
||||||
"language" -> R.drawable.ic_translate
|
"language" -> R.drawable.ic_translate
|
||||||
@@ -804,8 +828,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
rotation = 0f
|
rotation = 0f
|
||||||
isEnabled = true
|
isEnabled = true
|
||||||
|
|
||||||
setColorFilter(ContextCompat.getColor(context, R.color.material_orange_500))
|
|
||||||
|
|
||||||
isClickable = true
|
isClickable = true
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val favorites = Tags(json.parse(serializer, favoritesFile.readText()))
|
val favorites = Tags(json.parse(serializer, favoritesFile.readText()))
|
||||||
@@ -827,13 +849,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suggestion.t == -1) {
|
if (item.t == -1) {
|
||||||
textView.text = suggestion.s
|
textView.text = item.s
|
||||||
} else {
|
} else {
|
||||||
val text = "${suggestion.s}\n ${suggestion.t}"
|
val text = "${item.s}\n ${item.t}"
|
||||||
|
|
||||||
val len = text.length
|
val len = text.length
|
||||||
val left = suggestion.s.length
|
val left = item.s.length
|
||||||
|
|
||||||
textView.text = SpannableString(text).apply {
|
textView.text = SpannableString(text).apply {
|
||||||
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
|
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
|
||||||
@@ -846,14 +868,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setOnSearchListener(object : FloatingSearchView.OnSearchListener {
|
setOnSearchListener(object : FloatingSearchView.OnSearchListener {
|
||||||
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
||||||
val suggestion = searchSuggestion as TagSuggestion
|
if (searchSuggestion !is TagSuggestion)
|
||||||
|
return
|
||||||
|
|
||||||
with(searchInputView.text) {
|
with(searchInputView.text) {
|
||||||
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
||||||
append("${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")} ")
|
append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ")
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSuggestions()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchAction(currentQuery: String?) {
|
override fun onSearchAction(currentQuery: String?) {
|
||||||
@@ -863,7 +884,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
||||||
override fun onFocus() {
|
override fun onFocus() {
|
||||||
if (searchInputView.text.isEmpty())
|
if (query.isEmpty() or query.endsWith(' '))
|
||||||
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
swapSuggestions(json.parse(serializer, favoritesFile.readText()).map {
|
||||||
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
})
|
})
|
||||||
@@ -872,18 +893,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onFocusCleared() {
|
override fun onFocusCleared() {
|
||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
|
|
||||||
val query = searchInputView.text.toString()
|
runOnUiThread {
|
||||||
|
cancelFetch()
|
||||||
if (query != this@MainActivity.query) {
|
clearGalleries()
|
||||||
this@MainActivity.query = query
|
currentPage = 0
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
runOnUiThread {
|
loadBlocks()
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
fetchGalleries(query)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -912,9 +927,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
main_progressbar.show()
|
main_progressbar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchGalleries(query: String) {
|
private fun fetchGalleries(query: String, sortMode: SortMode) {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
|
||||||
val defaultQuery = preference.getString("default_query", "")!!
|
val defaultQuery = preference.getString("default_query", "")!!
|
||||||
|
|
||||||
galleryIDs = null
|
galleryIDs = null
|
||||||
@@ -927,12 +941,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
Mode.SEARCH -> {
|
Mode.SEARCH -> {
|
||||||
when {
|
when {
|
||||||
query.isEmpty() and defaultQuery.isEmpty() -> {
|
query.isEmpty() and defaultQuery.isEmpty() -> {
|
||||||
fetchNozomi(start = currentPage*perPage, count = perPage).let {
|
when(sortMode) {
|
||||||
totalItems = it.second
|
SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all")
|
||||||
it.first
|
else -> getGalleryIDsFromNozomi(null, "index", "all")
|
||||||
|
}.apply {
|
||||||
|
totalItems = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> doSearch("$defaultQuery $query").apply {
|
else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).apply {
|
||||||
totalItems = size
|
totalItems = size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -953,8 +969,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode.DOWNLOAD -> {
|
Mode.DOWNLOAD -> {
|
||||||
|
val downloads = getDownloadDirectory(this@MainActivity).listFiles().filter { file ->
|
||||||
|
file.isDirectory && (file.name!!.toIntOrNull() != null) && file.findFile(".metadata") != null
|
||||||
|
}.map {
|
||||||
|
it.name!!.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
query.isEmpty() -> downloads.toList().apply {
|
query.isEmpty() -> downloads.apply {
|
||||||
totalItems = size
|
totalItems = size
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -985,7 +1007,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private fun loadBlocks() {
|
private fun loadBlocks() {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
||||||
val defaultQuery = preference.getString("default_query", "")!!
|
|
||||||
|
|
||||||
loadingJob = CoroutineScope(Dispatchers.IO).launch {
|
loadingJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val galleryIDs = galleryIDs?.await()
|
val galleryIDs = galleryIDs?.await()
|
||||||
@@ -999,57 +1020,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
when {
|
galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks ->
|
||||||
query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) ->
|
|
||||||
galleryIDs
|
|
||||||
else ->
|
|
||||||
galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size))
|
|
||||||
}.chunked(5).let { chunks ->
|
|
||||||
for (chunk in chunks)
|
for (chunk in chunks)
|
||||||
chunk.map { galleryID ->
|
chunk.map { galleryID ->
|
||||||
async {
|
async {
|
||||||
try {
|
Cache(this@MainActivity).getGalleryBlock(galleryID)
|
||||||
val json = Json(JsonConfiguration.Stable)
|
|
||||||
val serializer = GalleryBlock.serializer()
|
|
||||||
|
|
||||||
val galleryBlock =
|
|
||||||
File(getCachedGallery(this@MainActivity, galleryID), "galleryBlock.json").let { cache ->
|
|
||||||
when {
|
|
||||||
cache.exists() -> json.parse(serializer, cache.readText())
|
|
||||||
else -> {
|
|
||||||
getGalleryBlock(galleryID).apply {
|
|
||||||
this ?: return@apply
|
|
||||||
|
|
||||||
if (cache.parentFile?.exists() == false)
|
|
||||||
cache.parentFile!!.mkdirs()
|
|
||||||
|
|
||||||
cache.writeText(json.stringify(serializer, this))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} ?: return@async null
|
|
||||||
|
|
||||||
val thumbnail = async {
|
|
||||||
val ext = galleryBlock.thumbnails[0].split('.').last()
|
|
||||||
File(getCachedGallery(this@MainActivity, galleryBlock.id), "thumbnail.$ext").apply {
|
|
||||||
if (!exists())
|
|
||||||
try {
|
|
||||||
with(URL(galleryBlock.thumbnails[0]).openConnection() as HttpsURLConnection) {
|
|
||||||
if (this@apply.parentFile?.exists() == false)
|
|
||||||
this@apply.parentFile!!.mkdirs()
|
|
||||||
|
|
||||||
inputStream.copyTo(FileOutputStream(this@apply))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
delete()
|
|
||||||
}
|
|
||||||
}.absolutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
Pair(galleryBlock, thumbnail)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.forEach {
|
}.forEach {
|
||||||
val galleryBlock = it.await()
|
val galleryBlock = it.await()
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -7,6 +25,7 @@ import android.os.Bundle
|
|||||||
import android.view.*
|
import android.view.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.PagerSnapHelper
|
import androidx.recyclerview.widget.PagerSnapHelper
|
||||||
@@ -15,31 +34,24 @@ import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
|||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.crashlytics.android.Crashlytics
|
import com.crashlytics.android.Crashlytics
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import io.fabric.sdk.android.Fabric
|
||||||
import kotlinx.android.synthetic.main.activity_reader.*
|
import kotlinx.android.synthetic.main.activity_reader.*
|
||||||
import kotlinx.android.synthetic.main.activity_reader.view.*
|
import kotlinx.android.synthetic.main.activity_reader.view.*
|
||||||
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.io.IOException
|
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import xyz.quaver.Code
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
|
||||||
import xyz.quaver.hitomi.getGalleryBlock
|
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||||
import xyz.quaver.pupil.util.GalleryDownloader
|
|
||||||
import xyz.quaver.pupil.util.Histories
|
import xyz.quaver.pupil.util.Histories
|
||||||
import xyz.quaver.pupil.util.ItemClickSupport
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
|
import xyz.quaver.pupil.util.download.DownloadWorker
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val images = ArrayList<String>()
|
private var galleryID = 0
|
||||||
private lateinit var galleryBlock: GalleryBlock
|
|
||||||
private var gallerySize = 0
|
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
|
|
||||||
private var isScroll = true
|
private var isScroll = true
|
||||||
@@ -55,7 +67,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var downloader: GalleryDownloader
|
private val timer = Timer()
|
||||||
|
|
||||||
private val snapHelper = PagerSnapHelper()
|
private val snapHelper = PagerSnapHelper()
|
||||||
|
|
||||||
@@ -66,6 +78,9 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
title = getString(R.string.reader_loading)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
|
|
||||||
favorites = (application as Pupil).favorites
|
favorites = (application as Pupil).favorites
|
||||||
|
|
||||||
window.setFlags(
|
window.setFlags(
|
||||||
@@ -76,22 +91,16 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
handleIntent(intent)
|
handleIntent(intent)
|
||||||
|
|
||||||
Crashlytics.setInt("GalleryID", galleryBlock.id)
|
if (Fabric.isInitialized())
|
||||||
|
Crashlytics.setInt("GalleryID", galleryID)
|
||||||
|
|
||||||
if (!::galleryBlock.isInitialized) {
|
if (galleryID == 0) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
supportActionBar?.title = galleryBlock.title
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
|
||||||
|
|
||||||
initDownloader()
|
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
|
initDownloader()
|
||||||
if (!downloader.download)
|
|
||||||
downloader.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
@@ -106,25 +115,16 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
if (uri != null && lastPathSegment != null) {
|
if (uri != null && lastPathSegment != null) {
|
||||||
val nonNumber = Regex("[^-?0-9]+")
|
val nonNumber = Regex("[^-?0-9]+")
|
||||||
|
|
||||||
val galleryID = when (uri.host) {
|
galleryID = when (uri.host) {
|
||||||
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
|
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
|
||||||
"히요비.asia" -> lastPathSegment.toInt()
|
"히요비.asia" -> lastPathSegment.toInt()
|
||||||
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
|
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
|
||||||
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
runBlocking {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
galleryBlock = getGalleryBlock(galleryID) ?: return@launch
|
|
||||||
}.join()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
galleryBlock = Json(JsonConfiguration.Stable).parse(
|
galleryID = intent.getIntExtra("galleryID", 0)
|
||||||
GalleryBlock.serializer(),
|
|
||||||
intent.getStringExtra("galleryblock")!!
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
with(menu?.findItem(R.id.reader_menu_favorite)) {
|
with(menu?.findItem(R.id.reader_menu_favorite)) {
|
||||||
this ?: return@with
|
this ?: return@with
|
||||||
|
|
||||||
if (favorites.contains(galleryBlock.id))
|
if (favorites.contains(galleryID))
|
||||||
(icon as Animatable).start()
|
(icon as Animatable).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
|
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
|
||||||
with(view.dialog_number_picker) {
|
with(view.dialog_number_picker) {
|
||||||
minValue=1
|
minValue=1
|
||||||
maxValue=gallerySize
|
maxValue=reader_recyclerview?.adapter?.itemCount ?: 0
|
||||||
value=currentPage
|
value=currentPage
|
||||||
}
|
}
|
||||||
val dialog = AlertDialog.Builder(this).apply {
|
val dialog = AlertDialog.Builder(this).apply {
|
||||||
@@ -176,7 +176,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
R.id.reader_menu_favorite -> {
|
R.id.reader_menu_favorite -> {
|
||||||
val id = galleryBlock.id
|
val id = galleryID
|
||||||
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
|
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
|
||||||
|
|
||||||
if (favorites.contains(id)) {
|
if (favorites.contains(id)) {
|
||||||
@@ -195,8 +195,11 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
if (::downloader.isInitialized && !downloader.download)
|
timer.cancel()
|
||||||
downloader.cancel()
|
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
|
||||||
|
|
||||||
|
if (!Cache(this).isDownloading(galleryID))
|
||||||
|
DownloadWorker.getInstance(this@ReaderActivity).cancel(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -214,121 +217,73 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
|
//currentPage is 1-based
|
||||||
|
return when(keyCode) {
|
||||||
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
|
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage-2, 0)
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
|
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage, 0)
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onKeyDown(keyCode, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun initDownloader() {
|
private fun initDownloader() {
|
||||||
var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id)
|
val worker = DownloadWorker.getInstance(this).apply {
|
||||||
|
queue.add(galleryID)
|
||||||
if (d == null) {
|
|
||||||
try {
|
|
||||||
d = GalleryDownloader(this, galleryBlock)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
downloader = d.apply {
|
timer.schedule(0, 1000) {
|
||||||
onReaderLoadedHandler = {
|
if (worker.progress.indexOfKey(galleryID) < 0) //loading
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
return@schedule
|
||||||
with(reader_download_progressbar) {
|
|
||||||
max = it.size
|
|
||||||
progress = 0
|
|
||||||
}
|
|
||||||
with(reader_progressbar) {
|
|
||||||
max = it.size
|
|
||||||
progress = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
gallerySize = it.size
|
if (worker.progress[galleryID] == null) { //Gallery not found
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.size}"
|
timer.cancel()
|
||||||
}
|
Snackbar
|
||||||
|
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
onProgressHandler = {
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
runOnUiThread {
|
||||||
reader_download_progressbar.progress = it
|
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||||
menu?.findItem(R.id.reader_menu_use_hiyobi)?.isVisible = downloader.useHiyobi
|
reader_download_progressbar.progress = worker.progress[galleryID]?.count { !it.isFinite() } ?: 0
|
||||||
}
|
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||||
}
|
|
||||||
onDownloadedHandler = {
|
if (title == getString(R.string.reader_loading)) {
|
||||||
val item = it.toList()
|
val reader = (reader_recyclerview.adapter as ReaderAdapter).reader
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
if (images.isEmpty()) {
|
if (reader != null) {
|
||||||
images.addAll(item)
|
title = reader.title
|
||||||
reader_recyclerview.adapter?.notifyDataSetChanged()
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.size}"
|
||||||
} else {
|
|
||||||
images.add(item.last())
|
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
|
||||||
reader_recyclerview.adapter?.notifyItemInserted(images.size-1)
|
when (reader.code) {
|
||||||
|
Code.HITOMI -> R.drawable.hitomi
|
||||||
|
Code.HIYOBI -> R.drawable.ic_hiyobi
|
||||||
|
else -> android.R.color.transparent
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
onErrorHandler = {
|
if (worker.progress[galleryID]?.all { !it.isFinite() } == true) { //Download finished
|
||||||
if (it is IOException)
|
|
||||||
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
|
|
||||||
downloader.download = false
|
|
||||||
}
|
|
||||||
onCompleteHandler = {
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
reader_download_progressbar.visibility = View.GONE
|
reader_download_progressbar.visibility = View.GONE
|
||||||
|
|
||||||
|
animateDownloadFAB(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onNotifyChangedHandler = { notify ->
|
|
||||||
val fab = reader_fab_download
|
|
||||||
|
|
||||||
runOnUiThread {
|
|
||||||
if (notify) {
|
|
||||||
val icon = AnimatedVectorDrawableCompat.create(this, R.drawable.ic_downloading)
|
|
||||||
icon?.registerAnimationCallback(object: Animatable2Compat.AnimationCallback() {
|
|
||||||
override fun onAnimationEnd(drawable: Drawable?) {
|
|
||||||
if (downloader.download)
|
|
||||||
fab.post {
|
|
||||||
icon.start()
|
|
||||||
fab.labelText = getString(R.string.reader_fab_download_cancel)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
fab.post {
|
|
||||||
fab.setImageResource(R.drawable.ic_download)
|
|
||||||
fab.labelText = getString(R.string.reader_fab_download)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
fab.setImageDrawable(icon)
|
|
||||||
icon?.start()
|
|
||||||
} else {
|
|
||||||
runOnUiThread {
|
|
||||||
fab.setImageResource(R.drawable.ic_download)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloader.download) {
|
|
||||||
downloader.invokeOnReaderLoaded()
|
|
||||||
downloader.invokeOnNotifyChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
with(reader_recyclerview) {
|
with(reader_recyclerview) {
|
||||||
adapter = ReaderAdapter(images)
|
adapter = ReaderAdapter(this@ReaderActivity, galleryID).apply {
|
||||||
|
onItemClickListener = {
|
||||||
addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
|
||||||
|
|
||||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
|
||||||
|
|
||||||
if (layoutManager.findFirstVisibleItemPosition() == -1)
|
|
||||||
return
|
|
||||||
currentPage = layoutManager.findFirstVisibleItemPosition()+1
|
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/$gallerySize"
|
|
||||||
this@ReaderActivity.reader_progressbar.progress = currentPage
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ItemClickSupport.addTo(this)
|
|
||||||
.setOnItemClickListener { _, _, _ ->
|
|
||||||
if (isScroll) {
|
if (isScroll) {
|
||||||
isScroll = false
|
isScroll = false
|
||||||
isFullscreen = true
|
isFullscreen = true
|
||||||
@@ -336,23 +291,54 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
scrollMode(false)
|
scrollMode(false)
|
||||||
fullscreen(true)
|
fullscreen(true)
|
||||||
} else {
|
} else {
|
||||||
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage)
|
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage) //Moves to next page because currentPage is 1-based indexing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
if (dy < 0)
|
||||||
|
this@ReaderActivity.reader_fab.showMenuButton(true)
|
||||||
|
else if (dy > 0)
|
||||||
|
this@ReaderActivity.reader_fab.hideMenuButton(true)
|
||||||
|
|
||||||
|
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||||
|
|
||||||
|
if (layoutManager.findFirstVisibleItemPosition() == -1)
|
||||||
|
return
|
||||||
|
currentPage = layoutManager.findFirstVisibleItemPosition()+1
|
||||||
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}"
|
||||||
|
this@ReaderActivity.reader_progressbar.progress = currentPage
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_fab_fullscreen.setOnClickListener {
|
with(reader_fab_download) {
|
||||||
isFullscreen = true
|
animateDownloadFAB(Cache(context).isDownloading(galleryID)) //If download in progress, animate button
|
||||||
fullscreen(isFullscreen)
|
|
||||||
|
|
||||||
reader_fab.close(true)
|
setOnClickListener {
|
||||||
|
if (Cache(context).isDownloading(galleryID)) {
|
||||||
|
Cache(context).setDownloading(galleryID, false)
|
||||||
|
|
||||||
|
animateDownloadFAB(false)
|
||||||
|
} else {
|
||||||
|
Cache(context).setDownloading(galleryID, true)
|
||||||
|
animateDownloadFAB(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader_fab_download.setOnClickListener {
|
with(reader_fab_fullscreen) {
|
||||||
downloader.download = !downloader.download
|
setImageResource(R.drawable.ic_fullscreen)
|
||||||
|
setOnClickListener {
|
||||||
|
isFullscreen = true
|
||||||
|
fullscreen(isFullscreen)
|
||||||
|
|
||||||
if (!downloader.download)
|
this@ReaderActivity.reader_fab.close(true)
|
||||||
downloader.clearNotification()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,4 +369,34 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
|
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun animateDownloadFAB(animate: Boolean) {
|
||||||
|
with(reader_fab_download) {
|
||||||
|
if (animate) {
|
||||||
|
val icon = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_downloading)
|
||||||
|
|
||||||
|
icon?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
|
||||||
|
override fun onAnimationEnd(drawable: Drawable?) {
|
||||||
|
val worker = DownloadWorker.getInstance(context)
|
||||||
|
if (worker.progress[galleryID]?.all { !it.isFinite() } == true) // If download is finished, stop animating
|
||||||
|
post {
|
||||||
|
setImageResource(R.drawable.ic_download)
|
||||||
|
labelText = getString(R.string.reader_fab_download)
|
||||||
|
}
|
||||||
|
else // Or continue animate
|
||||||
|
post {
|
||||||
|
icon.start()
|
||||||
|
labelText = getString(R.string.reader_fab_download_cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setImageDrawable(icon)
|
||||||
|
icon?.start()
|
||||||
|
} else {
|
||||||
|
setImageResource(R.drawable.ic_download)
|
||||||
|
labelText = getString(R.string.reader_fab_download)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,34 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.preference.Preference
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import kotlinx.android.synthetic.main.dialog_default_query.view.*
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.android.synthetic.main.settings_activity.*
|
||||||
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.parseList
|
||||||
|
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.types.Tags
|
import xyz.quaver.pupil.ui.fragment.LockFragment
|
||||||
import xyz.quaver.pupil.util.Lock
|
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
||||||
import xyz.quaver.pupil.util.LockManager
|
import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER
|
||||||
import xyz.quaver.pupil.util.getDownloadDirectory
|
import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER_OLD
|
||||||
|
import xyz.quaver.pupil.util.REQUEST_LOCK
|
||||||
|
import xyz.quaver.pupil.util.REQUEST_RESTORE
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
val REQUEST_LOCK = 38238
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -56,325 +74,6 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
|
||||||
|
|
||||||
private val suffix = listOf(
|
|
||||||
"B",
|
|
||||||
"kB",
|
|
||||||
"MB",
|
|
||||||
"GB",
|
|
||||||
"TB" //really?
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
val lockManager = LockManager(context!!)
|
|
||||||
|
|
||||||
findPreference<Preference>("app_lock")?.summary = if (lockManager.locks.isNullOrEmpty()) {
|
|
||||||
getString(R.string.settings_lock_none)
|
|
||||||
} else {
|
|
||||||
lockManager.locks?.joinToString(", ") {
|
|
||||||
when(it.type) {
|
|
||||||
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
|
|
||||||
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
|
|
||||||
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDirSize(dir: File) : String {
|
|
||||||
var size = dir.walk().map { it.length() }.sum()
|
|
||||||
var suffixIndex = 0
|
|
||||||
|
|
||||||
while (size >= 1024) {
|
|
||||||
size /= 1024
|
|
||||||
suffixIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
return getString(R.string.settings_clear_summary, size, suffix[suffixIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
|
||||||
|
|
||||||
with(findPreference<Preference>("app_version")) {
|
|
||||||
this!!
|
|
||||||
|
|
||||||
val manager = context.packageManager
|
|
||||||
val info = manager.getPackageInfo(context.packageName, 0)
|
|
||||||
|
|
||||||
summary = info.versionName
|
|
||||||
}
|
|
||||||
|
|
||||||
with(findPreference<Preference>("delete_cache")) {
|
|
||||||
this!!
|
|
||||||
|
|
||||||
val dir = File(context.cacheDir, "imageCache")
|
|
||||||
|
|
||||||
summary = getDirSize(dir)
|
|
||||||
|
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
|
||||||
AlertDialog.Builder(context).apply {
|
|
||||||
setTitle(R.string.warning)
|
|
||||||
setMessage(R.string.settings_clear_cache_alert_message)
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
|
||||||
if (dir.exists())
|
|
||||||
dir.deleteRecursively()
|
|
||||||
|
|
||||||
summary = getDirSize(dir)
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
|
||||||
}.show()
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with(findPreference<Preference>("delete_downloads")) {
|
|
||||||
this!!
|
|
||||||
|
|
||||||
val dir = getDownloadDirectory(context)!!
|
|
||||||
|
|
||||||
summary = getDirSize(dir)
|
|
||||||
|
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
|
||||||
AlertDialog.Builder(context).apply {
|
|
||||||
setTitle(R.string.warning)
|
|
||||||
setMessage(R.string.settings_clear_downloads_alert_message)
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
|
||||||
if (dir.exists())
|
|
||||||
dir.deleteRecursively()
|
|
||||||
|
|
||||||
val downloads = (activity!!.application as Pupil).downloads
|
|
||||||
|
|
||||||
downloads.clear()
|
|
||||||
|
|
||||||
summary = getDirSize(dir)
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
|
||||||
}.show()
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with(findPreference<Preference>("clear_history")) {
|
|
||||||
this!!
|
|
||||||
|
|
||||||
val histories = (activity!!.application as Pupil).histories
|
|
||||||
|
|
||||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
|
||||||
|
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
|
||||||
AlertDialog.Builder(context).apply {
|
|
||||||
setTitle(R.string.warning)
|
|
||||||
setMessage(R.string.settings_clear_history_alert_message)
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
|
||||||
histories.clear()
|
|
||||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
|
||||||
}.show()
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with(findPreference<Preference>("default_query")) {
|
|
||||||
this!!
|
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
|
|
||||||
summary = preferences.getString("default_query", "") ?: ""
|
|
||||||
|
|
||||||
val languages = resources.getStringArray(R.array.languages).map {
|
|
||||||
it.split("|").let { split ->
|
|
||||||
Pair(split[0], split[1])
|
|
||||||
}
|
|
||||||
}.toMap()
|
|
||||||
val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
|
|
||||||
|
|
||||||
val excludeBL = "-male:yaoi"
|
|
||||||
val excludeGuro = listOf("-female:guro", "-male:guro")
|
|
||||||
|
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
|
||||||
val dialogView = LayoutInflater.from(context).inflate(
|
|
||||||
R.layout.dialog_default_query,
|
|
||||||
LinearLayout(context),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
val tags = Tags.parse(
|
|
||||||
preferences.getString("default_query", "") ?: ""
|
|
||||||
)
|
|
||||||
|
|
||||||
summary = tags.toString()
|
|
||||||
|
|
||||||
with(dialogView.default_query_dialog_language_selector) {
|
|
||||||
adapter =
|
|
||||||
ArrayAdapter(
|
|
||||||
context,
|
|
||||||
android.R.layout.simple_spinner_dropdown_item,
|
|
||||||
arrayListOf(
|
|
||||||
getString(R.string.default_query_dialog_language_selector_none)
|
|
||||||
).apply {
|
|
||||||
addAll(languages.values)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (tags.any { it.area == "language" }) {
|
|
||||||
val tag = languages[tags.first { it.area == "language" }.tag]
|
|
||||||
if (tag != null) {
|
|
||||||
setSelection(
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
(adapter as ArrayAdapter<String>).getPosition(tag)
|
|
||||||
)
|
|
||||||
tags.removeByArea("language")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with(dialogView.default_query_dialog_BL_checkbox) {
|
|
||||||
isChecked = tags.contains(excludeBL)
|
|
||||||
if (tags.contains(excludeBL))
|
|
||||||
tags.remove(excludeBL)
|
|
||||||
}
|
|
||||||
|
|
||||||
with(dialogView.default_query_dialog_guro_checkbox) {
|
|
||||||
isChecked = excludeGuro.all { tags.contains(it) }
|
|
||||||
if (excludeGuro.all { tags.contains(it) })
|
|
||||||
excludeGuro.forEach {
|
|
||||||
tags.remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with(dialogView.default_query_dialog_edittext) {
|
|
||||||
setText(tags.toString(), TextView.BufferType.EDITABLE)
|
|
||||||
addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
|
||||||
s ?: return
|
|
||||||
|
|
||||||
if (s.any { it.isUpperCase() })
|
|
||||||
s.replace(0, s.length, s.toString().toLowerCase())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
val dialog = AlertDialog.Builder(context!!).apply {
|
|
||||||
setView(dialogView)
|
|
||||||
}.create()
|
|
||||||
|
|
||||||
dialogView.default_query_dialog_ok.setOnClickListener {
|
|
||||||
val newTags = Tags.parse(dialogView.default_query_dialog_edittext.text.toString())
|
|
||||||
|
|
||||||
with(dialogView.default_query_dialog_language_selector) {
|
|
||||||
if (selectedItemPosition != 0)
|
|
||||||
newTags.add("language:${reverseLanguages[selectedItem]}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialogView.default_query_dialog_BL_checkbox.isChecked)
|
|
||||||
newTags.add(excludeBL)
|
|
||||||
|
|
||||||
if (dialogView.default_query_dialog_guro_checkbox.isChecked)
|
|
||||||
excludeGuro.forEach { tag ->
|
|
||||||
newTags.add(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceManager.sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
|
||||||
summary = preferences.getString("default_query", "") ?: ""
|
|
||||||
tags.clear()
|
|
||||||
tags.addAll(newTags)
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.show()
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with(findPreference<Preference>("app_lock")) {
|
|
||||||
this!!
|
|
||||||
|
|
||||||
val lockManager = LockManager(context)
|
|
||||||
|
|
||||||
summary = if (lockManager.locks.isNullOrEmpty()) {
|
|
||||||
getString(R.string.settings_lock_none)
|
|
||||||
} else {
|
|
||||||
lockManager.locks?.joinToString(", ") {
|
|
||||||
when(it.type) {
|
|
||||||
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
|
|
||||||
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
|
|
||||||
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
|
||||||
val intent = Intent(context, LockActivity::class.java)
|
|
||||||
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_LOCK)
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LockFragment : PreferenceFragmentCompat() {
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
val lockManager = LockManager(context!!)
|
|
||||||
|
|
||||||
findPreference<Preference>("lock_pattern")?.summary =
|
|
||||||
if (lockManager.contains(Lock.Type.PATTERN))
|
|
||||||
getString(R.string.settings_lock_enabled)
|
|
||||||
else
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
setPreferencesFromResource(R.xml.lock_preferences, rootKey)
|
|
||||||
|
|
||||||
with(findPreference<Preference>("lock_pattern")) {
|
|
||||||
this!!
|
|
||||||
|
|
||||||
if (LockManager(context!!).contains(Lock.Type.PATTERN))
|
|
||||||
summary = getString(R.string.settings_lock_enabled)
|
|
||||||
|
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
|
||||||
val lockManager = LockManager(context!!)
|
|
||||||
|
|
||||||
if (lockManager.contains(Lock.Type.PATTERN)) {
|
|
||||||
AlertDialog.Builder(context).apply {
|
|
||||||
setTitle(R.string.warning)
|
|
||||||
setMessage(R.string.settings_lock_remove_message)
|
|
||||||
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
|
||||||
lockManager.remove(Lock.Type.PATTERN)
|
|
||||||
onResume()
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
|
||||||
}.show()
|
|
||||||
} else {
|
|
||||||
val intent = Intent(context, LockActivity::class.java).apply {
|
|
||||||
putExtra("mode", "add_lock")
|
|
||||||
putExtra("type", "pattern")
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||||
when (item?.itemId) {
|
when (item?.itemId) {
|
||||||
android.R.id.home -> onBackPressed()
|
android.R.id.home -> onBackPressed()
|
||||||
@@ -383,6 +82,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
when(requestCode) {
|
when(requestCode) {
|
||||||
REQUEST_LOCK -> {
|
REQUEST_LOCK -> {
|
||||||
@@ -394,6 +94,62 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
.commitAllowingStateLoss()
|
.commitAllowingStateLoss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
REQUEST_RESTORE -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
val uri = data?.data ?: return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val json = contentResolver.openInputStream(uri).use { inputStream ->
|
||||||
|
inputStream!!
|
||||||
|
|
||||||
|
inputStream.readBytes().toString(Charset.defaultCharset())
|
||||||
|
}
|
||||||
|
|
||||||
|
(application as Pupil).favorites.addAll(Json.parseList<Int>(json).also {
|
||||||
|
Snackbar.make(
|
||||||
|
window.decorView,
|
||||||
|
getString(R.string.settings_restore_successful, it.size),
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Snackbar.make(
|
||||||
|
window.decorView,
|
||||||
|
R.string.settings_restore_failed,
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUEST_DOWNLOAD_FOLDER -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
data?.data?.also { uri ->
|
||||||
|
val takeFlags: Int = intent.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||||
|
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
|
|
||||||
|
if (DocumentFile.fromTreeUri(this, uri)?.canWrite() == false)
|
||||||
|
Snackbar.make(settings, R.string.settings_dl_location_not_writable, Snackbar.LENGTH_LONG).show()
|
||||||
|
else
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this).edit()
|
||||||
|
.putString("dl_location", uri.toString())
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUEST_DOWNLOAD_FOLDER_OLD -> {
|
||||||
|
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
|
||||||
|
val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
|
||||||
|
|
||||||
|
if (!File(directory).canWrite())
|
||||||
|
Snackbar.make(settings, R.string.settings_dl_location_not_writable, Snackbar.LENGTH_LONG).show()
|
||||||
|
else
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this).edit()
|
||||||
|
.putString("dl_location", Uri.fromFile(File(directory)).toString())
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import kotlinx.android.synthetic.main.dialog_default_query.*
|
||||||
|
import kotlinx.android.synthetic.main.dialog_default_query.view.*
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.types.Tags
|
||||||
|
|
||||||
|
class DefaultQueryDialog(context : Context) : AlertDialog(context) {
|
||||||
|
|
||||||
|
private val languages = context.resources.getStringArray(R.array.languages).map {
|
||||||
|
it.split("|").let { split ->
|
||||||
|
Pair(split[0], split[1])
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
private val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
|
||||||
|
|
||||||
|
private val excludeBL = "-male:yaoi"
|
||||||
|
private val excludeGuro = listOf("-female:guro", "-male:guro")
|
||||||
|
|
||||||
|
private lateinit var dialogView : View
|
||||||
|
|
||||||
|
var onPositiveButtonClickListener : ((Tags) -> (Unit))? = null
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
initDialog()
|
||||||
|
|
||||||
|
setTitle(R.string.default_query_dialog_title)
|
||||||
|
setView(dialogView)
|
||||||
|
setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ ->
|
||||||
|
val newTags = Tags.parse(default_query_dialog_edittext.text.toString())
|
||||||
|
|
||||||
|
with(default_query_dialog_language_selector) {
|
||||||
|
if (selectedItemPosition != 0)
|
||||||
|
newTags.add("language:${reverseLanguages[selectedItem]}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (default_query_dialog_BL_checkbox.isChecked)
|
||||||
|
newTags.add(excludeBL)
|
||||||
|
|
||||||
|
if (default_query_dialog_guro_checkbox.isChecked)
|
||||||
|
excludeGuro.forEach { tag ->
|
||||||
|
newTags.add(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositiveButtonClickListener?.invoke(newTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
private fun initDialog() {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val tags = Tags.parse(
|
||||||
|
preferences.getString("default_query", "") ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_default_query, null)
|
||||||
|
|
||||||
|
with(dialogView.default_query_dialog_language_selector) {
|
||||||
|
adapter =
|
||||||
|
ArrayAdapter(
|
||||||
|
context,
|
||||||
|
android.R.layout.simple_spinner_dropdown_item,
|
||||||
|
arrayListOf(
|
||||||
|
context.getString(R.string.default_query_dialog_language_selector_none)
|
||||||
|
).apply {
|
||||||
|
addAll(languages.values)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (tags.any { it.area == "language" && !it.isNegative }) {
|
||||||
|
val tag = languages[tags.first { it.area == "language" }.tag]
|
||||||
|
if (tag != null) {
|
||||||
|
setSelection(
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(adapter as ArrayAdapter<String>).getPosition(tag)
|
||||||
|
)
|
||||||
|
tags.removeByArea("language", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(dialogView.default_query_dialog_BL_checkbox) {
|
||||||
|
isChecked = tags.contains(excludeBL)
|
||||||
|
if (tags.contains(excludeBL))
|
||||||
|
tags.remove(excludeBL)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(dialogView.default_query_dialog_guro_checkbox) {
|
||||||
|
isChecked = excludeGuro.all { tags.contains(it) }
|
||||||
|
if (excludeGuro.all { tags.contains(it) })
|
||||||
|
excludeGuro.forEach {
|
||||||
|
tags.remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(dialogView.default_query_dialog_edittext) {
|
||||||
|
setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE)
|
||||||
|
addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(
|
||||||
|
s: CharSequence?,
|
||||||
|
start: Int,
|
||||||
|
count: Int,
|
||||||
|
after: Int
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
s ?: return
|
||||||
|
|
||||||
|
if (s.any { it.isUpperCase() })
|
||||||
|
s.replace(
|
||||||
|
0,
|
||||||
|
s.length,
|
||||||
|
s.toString().toLowerCase(java.util.Locale.getDefault())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.RadioButton
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import kotlinx.android.synthetic.main.item_dl_location.view.*
|
||||||
|
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||||
|
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER
|
||||||
|
import xyz.quaver.pupil.util.REQUEST_DOWNLOAD_FOLDER_OLD
|
||||||
|
import xyz.quaver.pupil.util.byteToString
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
class DownloadLocationDialog(val activity: Activity) : AlertDialog(activity) {
|
||||||
|
|
||||||
|
private val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
private val buttons = mutableListOf<Pair<RadioButton, Uri?>>()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
val view = layoutInflater.inflate(R.layout.dialog_dl_location, null) as LinearLayout
|
||||||
|
|
||||||
|
val externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null)
|
||||||
|
|
||||||
|
externalFilesDirs.forEachIndexed { index, dir ->
|
||||||
|
|
||||||
|
dir ?: return@forEachIndexed
|
||||||
|
|
||||||
|
view.addView(layoutInflater.inflate(R.layout.item_dl_location, view, false).apply {
|
||||||
|
location_type.text = context.getString(when (index) {
|
||||||
|
0 -> R.string.settings_dl_location_internal
|
||||||
|
else -> R.string.settings_dl_location_removable
|
||||||
|
})
|
||||||
|
location_available.text = context.getString(
|
||||||
|
R.string.settings_dl_location_available,
|
||||||
|
byteToString(dir.freeSpace)
|
||||||
|
)
|
||||||
|
setOnClickListener {
|
||||||
|
buttons.forEach { pair ->
|
||||||
|
pair.first.isChecked = false
|
||||||
|
}
|
||||||
|
button.performClick()
|
||||||
|
preference.edit().putString("dl_location", Uri.fromFile(dir).toString()).apply()
|
||||||
|
}
|
||||||
|
buttons.add(button to Uri.fromFile(dir))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
view.addView(layoutInflater.inflate(R.layout.item_dl_location, view, false).apply {
|
||||||
|
location_type.text = context.getString(R.string.settings_dl_location_custom)
|
||||||
|
setOnClickListener {
|
||||||
|
buttons.forEach { pair ->
|
||||||
|
pair.first.isChecked = false
|
||||||
|
}
|
||||||
|
button.performClick()
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||||
|
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, REQUEST_DOWNLOAD_FOLDER)
|
||||||
|
|
||||||
|
dismiss()
|
||||||
|
} else { // Can't use SAF on old Androids!
|
||||||
|
val config = DirectoryChooserConfig.builder()
|
||||||
|
.newDirectoryName("Pupil")
|
||||||
|
.allowNewDirectoryNameModification(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val intent = Intent(context, DirectoryChooserActivity::class.java).apply {
|
||||||
|
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.startActivityForResult(intent, REQUEST_DOWNLOAD_FOLDER_OLD)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buttons.add(button to null)
|
||||||
|
})
|
||||||
|
|
||||||
|
val pref = Uri.parse(preference.getString("dl_location", null))
|
||||||
|
val index = externalFilesDirs.indexOfFirst {
|
||||||
|
Uri.fromFile(it).toString() == pref.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
buttons.last().first.isChecked = true
|
||||||
|
else
|
||||||
|
buttons[index].first.isChecked = true
|
||||||
|
|
||||||
|
setTitle(R.string.settings_dl_location)
|
||||||
|
|
||||||
|
setView(view)
|
||||||
|
|
||||||
|
setButton(Dialog.BUTTON_POSITIVE, context.getText(android.R.string.ok)) { _, _ ->
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
284
app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt
Normal file
284
app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout.LayoutParams
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.android.synthetic.main.dialog_gallery.*
|
||||||
|
import kotlinx.android.synthetic.main.gallery_details.view.*
|
||||||
|
import kotlinx.android.synthetic.main.item_gallery_details.view.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.quaver.hitomi.Gallery
|
||||||
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
|
import xyz.quaver.hitomi.getGallery
|
||||||
|
import xyz.quaver.pupil.BuildConfig
|
||||||
|
import xyz.quaver.pupil.Pupil
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||||
|
import xyz.quaver.pupil.adapters.ThumbnailAdapter
|
||||||
|
import xyz.quaver.pupil.types.Tag
|
||||||
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
|
||||||
|
class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) {
|
||||||
|
|
||||||
|
private val languages = context.resources.getStringArray(R.array.languages).map {
|
||||||
|
it.split("|").let { split ->
|
||||||
|
Pair(split[0], split[1])
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
private val glide = Glide.with(context)
|
||||||
|
|
||||||
|
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.dialog_gallery)
|
||||||
|
|
||||||
|
window?.attributes.apply {
|
||||||
|
this ?: return@apply
|
||||||
|
|
||||||
|
width = LayoutParams.MATCH_PARENT
|
||||||
|
height = LayoutParams.MATCH_PARENT
|
||||||
|
}
|
||||||
|
|
||||||
|
with(gallery_fab) {
|
||||||
|
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right))
|
||||||
|
setOnClickListener {
|
||||||
|
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||||
|
putExtra("galleryID", galleryID)
|
||||||
|
})
|
||||||
|
(context.applicationContext as Pupil).histories.add(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
try {
|
||||||
|
val gallery = getGallery(galleryID)
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
gallery_progressbar.visibility = View.GONE
|
||||||
|
gallery_title.text = gallery.title
|
||||||
|
gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
|
||||||
|
|
||||||
|
with(gallery_type) {
|
||||||
|
text = gallery.type.wordCapitalize()
|
||||||
|
setOnClickListener {
|
||||||
|
gallery.type.let {
|
||||||
|
when (it) {
|
||||||
|
"artist CG" -> "artistcg"
|
||||||
|
"game CG" -> "gamecg"
|
||||||
|
else -> it
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
onChipClickedHandler.forEach { handler ->
|
||||||
|
handler.invoke(Tag("type", it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Glide.with(context)
|
||||||
|
.load(gallery.cover)
|
||||||
|
.apply {
|
||||||
|
if (BuildConfig.CENSOR)
|
||||||
|
override(5, 8)
|
||||||
|
}.into(gallery_cover)
|
||||||
|
|
||||||
|
addDetails(gallery)
|
||||||
|
addThumbnails(gallery)
|
||||||
|
addRelated(gallery)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addDetails(gallery: Gallery) {
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
|
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
|
||||||
|
gallery_details.setText(R.string.gallery_details)
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
R.string.gallery_artists,
|
||||||
|
R.string.gallery_groups,
|
||||||
|
R.string.gallery_language,
|
||||||
|
R.string.gallery_series,
|
||||||
|
R.string.gallery_characters,
|
||||||
|
R.string.gallery_tags
|
||||||
|
).zip(
|
||||||
|
listOf(
|
||||||
|
gallery.artists.map { Tag("artist", it) },
|
||||||
|
gallery.groups.map { Tag("group", it) },
|
||||||
|
listOf(gallery.language).map { Tag("language", it) },
|
||||||
|
gallery.series.map { Tag("series", it) },
|
||||||
|
gallery.characters.map { Tag("character", it) },
|
||||||
|
gallery.tags.map {
|
||||||
|
Tag.parse(it).let { tag ->
|
||||||
|
when {
|
||||||
|
tag.area != null -> tag
|
||||||
|
else -> Tag("tag", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).filter {
|
||||||
|
(_, content) -> content.isNotEmpty()
|
||||||
|
}.forEach { (title, content) ->
|
||||||
|
inflater.inflate(R.layout.item_gallery_details, gallery_details_contents, false).apply {
|
||||||
|
gallery_details_type.setText(title)
|
||||||
|
|
||||||
|
content.forEach { tag ->
|
||||||
|
gallery_details_tags.addView(
|
||||||
|
Chip(context).apply {
|
||||||
|
chipIcon = when(tag.area) {
|
||||||
|
"male" -> {
|
||||||
|
setChipBackgroundColorResource(R.color.material_blue_700)
|
||||||
|
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
|
||||||
|
}
|
||||||
|
"female" -> {
|
||||||
|
setChipBackgroundColorResource(R.color.material_pink_600)
|
||||||
|
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
text = when (tag.area) {
|
||||||
|
"language" -> languages[tag.tag]
|
||||||
|
else -> tag.tag.wordCapitalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnsureMinTouchTargetSize(false)
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
onChipClickedHandler.forEach { handler ->
|
||||||
|
handler.invoke(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
gallery_details_contents.addView(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
gallery_contents.addView(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addThumbnails(gallery: Gallery) {
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
|
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
|
||||||
|
gallery_details.setText(R.string.gallery_thumbnails)
|
||||||
|
|
||||||
|
RecyclerView(context).apply {
|
||||||
|
layoutManager = GridLayoutManager(context, 3)
|
||||||
|
adapter = ThumbnailAdapter(glide, gallery.thumbnails)
|
||||||
|
}.let {
|
||||||
|
gallery_details_contents.addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
gallery_contents.addView(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addRelated(gallery: Gallery) {
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
val galleries = ArrayList<GalleryBlock>()
|
||||||
|
|
||||||
|
val adapter = GalleryBlockAdapter(context, galleries).apply {
|
||||||
|
onChipClickedHandler.add { tag ->
|
||||||
|
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||||
|
handler.invoke(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
gallery.related.forEachIndexed { i, galleryID ->
|
||||||
|
async(Dispatchers.IO) {
|
||||||
|
Cache(context).getGalleryBlock(galleryID)
|
||||||
|
}.let {
|
||||||
|
val galleryBlock = it.await() ?: return@let
|
||||||
|
|
||||||
|
galleries.add(galleryBlock)
|
||||||
|
adapter.notifyItemInserted(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
|
||||||
|
gallery_details.setText(R.string.gallery_related)
|
||||||
|
|
||||||
|
RecyclerView(context).apply {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
this.adapter = adapter
|
||||||
|
|
||||||
|
ItemClickSupport.addTo(this)
|
||||||
|
.setOnItemClickListener { _, position, _ ->
|
||||||
|
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||||
|
putExtra("galleryID", galleries[position].id)
|
||||||
|
})
|
||||||
|
(context.applicationContext as Pupil).histories.add(galleries[position].id)
|
||||||
|
}
|
||||||
|
.setOnItemLongClickListener { _, position, _ ->
|
||||||
|
GalleryDialog(
|
||||||
|
context,
|
||||||
|
galleries[position].id
|
||||||
|
).apply {
|
||||||
|
onChipClickedHandler.add { tag ->
|
||||||
|
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
gallery_details_contents.addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
gallery_contents.addView(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
97
app/src/main/java/xyz/quaver/pupil/ui/dialog/MirrorDialog.kt
Normal file
97
app/src/main/java/xyz/quaver/pupil/ui/dialog/MirrorDialog.kt
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.adapters.MirrorAdapter
|
||||||
|
|
||||||
|
class MirrorDialog(context: Context) : AlertDialog(context) {
|
||||||
|
|
||||||
|
class ItemTouchHelperCallback : ItemTouchHelper.Callback() {
|
||||||
|
|
||||||
|
var onMoveItem : ((Int, Int) -> (Unit))? = null
|
||||||
|
|
||||||
|
override fun getMovementFlags(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder
|
||||||
|
) = makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
|
||||||
|
|
||||||
|
override fun onMove(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
target: RecyclerView.ViewHolder
|
||||||
|
): Boolean {
|
||||||
|
onMoveItem?.invoke(viewHolder.adapterPosition, target.adapterPosition)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var recyclerView: RecyclerView
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
initDialog()
|
||||||
|
|
||||||
|
setTitle(R.string.settings_mirror_title)
|
||||||
|
setView(recyclerView)
|
||||||
|
setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> }
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initDialog() {
|
||||||
|
recyclerView = RecyclerView(context).apply recyclerview@{
|
||||||
|
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
adapter = MirrorAdapter(context).apply adapter@{
|
||||||
|
val itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback().apply {
|
||||||
|
onMoveItem = this@adapter.onItemMove
|
||||||
|
}).apply {
|
||||||
|
attachToRecyclerView(this@recyclerview)
|
||||||
|
}
|
||||||
|
|
||||||
|
onStartDrag = {
|
||||||
|
itemTouchHelper.startDrag(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
onItemMoved = {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.edit()
|
||||||
|
.putString("mirrors", it.joinToString(">"))
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
|
import xyz.quaver.pupil.util.Lock
|
||||||
|
import xyz.quaver.pupil.util.LockManager
|
||||||
|
|
||||||
|
class LockFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
val lockManager = LockManager(context!!)
|
||||||
|
|
||||||
|
findPreference<Preference>("lock_pattern")?.summary =
|
||||||
|
if (lockManager.contains(Lock.Type.PATTERN))
|
||||||
|
getString(R.string.settings_lock_enabled)
|
||||||
|
else
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
setPreferencesFromResource(R.xml.lock_preferences, rootKey)
|
||||||
|
|
||||||
|
with(findPreference<Preference>("lock_pattern")) {
|
||||||
|
this!!
|
||||||
|
|
||||||
|
if (LockManager(context!!).contains(Lock.Type.PATTERN))
|
||||||
|
summary = getString(R.string.settings_lock_enabled)
|
||||||
|
|
||||||
|
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
val lockManager = LockManager(context!!)
|
||||||
|
|
||||||
|
if (lockManager.contains(Lock.Type.PATTERN)) {
|
||||||
|
AlertDialog.Builder(context).apply {
|
||||||
|
setTitle(R.string.warning)
|
||||||
|
setMessage(R.string.settings_lock_remove_message)
|
||||||
|
|
||||||
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
lockManager.remove(Lock.Type.PATTERN)
|
||||||
|
onResume()
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||||
|
}.show()
|
||||||
|
} else {
|
||||||
|
val intent = Intent(context, LockActivity::class.java).apply {
|
||||||
|
putExtra("mode", "add_lock")
|
||||||
|
putExtra("type", "pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
package xyz.quaver.pupil.ui
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@@ -11,8 +29,6 @@ import com.andrognito.patternlockview.utils.PatternLockUtils
|
|||||||
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
|
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
|
||||||
import kotlinx.android.synthetic.main.fragment_pattern_lock.view.*
|
import kotlinx.android.synthetic.main.fragment_pattern_lock.view.*
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.util.hash
|
|
||||||
import xyz.quaver.pupil.util.hashWithSalt
|
|
||||||
|
|
||||||
class PatternLockFragment : Fragment(), PatternLockViewListener {
|
class PatternLockFragment : Fragment(), PatternLockViewListener {
|
||||||
|
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceCategory
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import xyz.quaver.pupil.Pupil
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
|
import xyz.quaver.pupil.ui.SettingsActivity
|
||||||
|
import xyz.quaver.pupil.ui.dialog.DefaultQueryDialog
|
||||||
|
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog
|
||||||
|
import xyz.quaver.pupil.ui.dialog.MirrorDialog
|
||||||
|
import xyz.quaver.pupil.util.*
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsFragment :
|
||||||
|
PreferenceFragmentCompat(),
|
||||||
|
Preference.OnPreferenceClickListener,
|
||||||
|
Preference.OnPreferenceChangeListener,
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
val lockManager = LockManager(context!!)
|
||||||
|
|
||||||
|
findPreference<Preference>("app_lock")?.summary = if (lockManager.locks.isNullOrEmpty()) {
|
||||||
|
getString(R.string.settings_lock_none)
|
||||||
|
} else {
|
||||||
|
lockManager.locks?.joinToString(", ") {
|
||||||
|
when(it.type) {
|
||||||
|
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
|
||||||
|
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
|
||||||
|
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDirSize(dir: DocumentFile) : String {
|
||||||
|
val size = dir.walk().map { it.length() }.sum()
|
||||||
|
|
||||||
|
return getString(R.string.settings_clear_summary, byteToString(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreferenceClick(preference: Preference?): Boolean {
|
||||||
|
with (preference) {
|
||||||
|
this ?: return false
|
||||||
|
|
||||||
|
when (key) {
|
||||||
|
"app_version" -> {
|
||||||
|
checkUpdate(activity as SettingsActivity, true)
|
||||||
|
}
|
||||||
|
"delete_cache" -> {
|
||||||
|
val dir = DocumentFile.fromFile(File(context.cacheDir, "imageCache"))
|
||||||
|
|
||||||
|
AlertDialog.Builder(context).apply {
|
||||||
|
setTitle(R.string.warning)
|
||||||
|
setMessage(R.string.settings_clear_cache_alert_message)
|
||||||
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
if (dir.exists())
|
||||||
|
dir.deleteRecursively()
|
||||||
|
|
||||||
|
summary = getDirSize(dir)
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
"delete_downloads" -> {
|
||||||
|
val dir = getDownloadDirectory(context)
|
||||||
|
|
||||||
|
AlertDialog.Builder(context).apply {
|
||||||
|
setTitle(R.string.warning)
|
||||||
|
setMessage(R.string.settings_clear_downloads_alert_message)
|
||||||
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
if (dir.exists())
|
||||||
|
dir.deleteRecursively()
|
||||||
|
|
||||||
|
summary = getDirSize(dir)
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
"clear_history" -> {
|
||||||
|
val histories = (context.applicationContext as Pupil).histories
|
||||||
|
|
||||||
|
AlertDialog.Builder(context).apply {
|
||||||
|
setTitle(R.string.warning)
|
||||||
|
setMessage(R.string.settings_clear_history_alert_message)
|
||||||
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
histories.clear()
|
||||||
|
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
"dl_location" -> {
|
||||||
|
DownloadLocationDialog(activity!!).show()
|
||||||
|
}
|
||||||
|
"default_query" -> {
|
||||||
|
DefaultQueryDialog(context).apply {
|
||||||
|
onPositiveButtonClickListener = { newTags ->
|
||||||
|
sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
||||||
|
summary = newTags.toString()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
|
"app_lock" -> {
|
||||||
|
val intent = Intent(context, LockActivity::class.java)
|
||||||
|
activity?.startActivityForResult(intent, REQUEST_LOCK)
|
||||||
|
}
|
||||||
|
"mirrors" -> {
|
||||||
|
MirrorDialog(context)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
"backup" -> {
|
||||||
|
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
|
||||||
|
context,
|
||||||
|
getDownloadDirectory(context).let {
|
||||||
|
if (it.findFile("favorites.json") != null)
|
||||||
|
it
|
||||||
|
else
|
||||||
|
it.createFile("null", "favorites.json")!!
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Snackbar.make(this@SettingsFragment.listView, R.string.settings_backup_snackbar, Snackbar.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
"restore" -> {
|
||||||
|
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
type = "*/*"
|
||||||
|
}
|
||||||
|
|
||||||
|
activity?.startActivityForResult(intent, REQUEST_RESTORE)
|
||||||
|
}
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean {
|
||||||
|
with (preference) {
|
||||||
|
this ?: return false
|
||||||
|
|
||||||
|
when (key) {
|
||||||
|
"dark_mode" -> {
|
||||||
|
AppCompatDelegate.setDefaultNightMode(when (newValue as Boolean) {
|
||||||
|
true -> AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
false -> AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
when (key) {
|
||||||
|
"dl_location" -> {
|
||||||
|
findPreference<Preference>(key)?.summary = getDownloadDirectory(context!!).uri.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||||
|
|
||||||
|
initPreferences()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initPreferences() {
|
||||||
|
for (i in 0 until preferenceScreen.preferenceCount) {
|
||||||
|
|
||||||
|
preferenceScreen.getPreference(i).run {
|
||||||
|
if (this is PreferenceCategory)
|
||||||
|
(0 until preferenceCount).map { getPreference(it) }
|
||||||
|
else
|
||||||
|
listOf(this)
|
||||||
|
}.forEach { preference ->
|
||||||
|
with (preference) {
|
||||||
|
|
||||||
|
when (key) {
|
||||||
|
"app_version" -> {
|
||||||
|
val manager = context.packageManager
|
||||||
|
val info = manager.getPackageInfo(context.packageName, 0)
|
||||||
|
summary = context.getString(R.string.settings_app_version_description, info.versionName)
|
||||||
|
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"delete_cache" -> {
|
||||||
|
val dir = DocumentFile.fromFile(File(context.cacheDir, "imageCache"))
|
||||||
|
summary = getDirSize(dir)
|
||||||
|
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"delete_downloads" -> {
|
||||||
|
val dir = getDownloadDirectory(context)
|
||||||
|
summary = getDirSize(dir)
|
||||||
|
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"clear_history" -> {
|
||||||
|
val histories = (activity!!.application as Pupil).histories
|
||||||
|
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||||
|
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"dl_location" -> {
|
||||||
|
summary = getDownloadDirectory(context).uri.path
|
||||||
|
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"default_query" -> {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
summary = preferences.getString("default_query", "") ?: ""
|
||||||
|
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"app_lock" -> {
|
||||||
|
val lockManager = LockManager(context)
|
||||||
|
summary =
|
||||||
|
if (lockManager.locks.isNullOrEmpty()) {
|
||||||
|
getString(R.string.settings_lock_none)
|
||||||
|
} else {
|
||||||
|
lockManager.locks?.joinToString(", ") {
|
||||||
|
when (it.type) {
|
||||||
|
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
|
||||||
|
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
|
||||||
|
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"mirrors" -> {
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"dark_mode" -> {
|
||||||
|
onPreferenceChangeListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"backup" -> {
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"restore" -> {
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt
Normal file
24
app/src/main/java/xyz/quaver/pupil/util/ConstValues.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
|
const val REQUEST_LOCK = 38238
|
||||||
|
const val REQUEST_RESTORE = 16546
|
||||||
|
const val REQUEST_DOWNLOAD_FOLDER = 3874
|
||||||
|
const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425
|
||||||
178
app/src/main/java/xyz/quaver/pupil/util/FileUtils.java
Normal file
178
app/src/main/java/xyz/quaver/pupil/util/FileUtils.java
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2007-2008 OpenIntents.org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import android.content.ContentUris;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 2009-07-03
|
||||||
|
* @author Peli
|
||||||
|
* @version 2013-12-11
|
||||||
|
* @author paulburke (ipaulpro)
|
||||||
|
*/
|
||||||
|
public class FileUtils {
|
||||||
|
/**
|
||||||
|
* Get a file path from a Uri. This will get the the path for Storage Access
|
||||||
|
* Framework Documents, as well as the _data field for the MediaStore and
|
||||||
|
* other file-based ContentProviders.
|
||||||
|
*
|
||||||
|
* @param context The context.
|
||||||
|
* @param uri The Uri to query.
|
||||||
|
* @author paulburke
|
||||||
|
*/
|
||||||
|
public static String getPath(final Context context, final Uri uri) {
|
||||||
|
|
||||||
|
// DocumentProvider
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) {
|
||||||
|
// ExternalStorageProvider
|
||||||
|
if (isExternalStorageDocument(uri)) {
|
||||||
|
final String docId = DocumentsContract.getDocumentId(uri);
|
||||||
|
final String[] split = docId.split(":");
|
||||||
|
final String type = split[0];
|
||||||
|
|
||||||
|
if ("primary".equalsIgnoreCase(type)) {
|
||||||
|
return context.getExternalFilesDir(null).getParentFile().getParentFile().getParentFile().getParent() + "/" + split[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle non-primary volumes
|
||||||
|
}
|
||||||
|
// DownloadsProvider
|
||||||
|
else if (isDownloadsDocument(uri)) {
|
||||||
|
|
||||||
|
final String id = DocumentsContract.getDocumentId(uri);
|
||||||
|
final Uri contentUri = ContentUris.withAppendedId(
|
||||||
|
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||||
|
|
||||||
|
return getDataColumn(context, contentUri, null, null);
|
||||||
|
}
|
||||||
|
// MediaProvider
|
||||||
|
else if (isMediaDocument(uri)) {
|
||||||
|
final String docId = DocumentsContract.getDocumentId(uri);
|
||||||
|
final String[] split = docId.split(":");
|
||||||
|
final String type = split[0];
|
||||||
|
|
||||||
|
Uri contentUri = null;
|
||||||
|
if ("image".equals(type)) {
|
||||||
|
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
} else if ("video".equals(type)) {
|
||||||
|
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
} else if ("audio".equals(type)) {
|
||||||
|
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String selection = "_id=?";
|
||||||
|
final String[] selectionArgs = new String[] {
|
||||||
|
split[1]
|
||||||
|
};
|
||||||
|
|
||||||
|
return getDataColumn(context, contentUri, selection, selectionArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// MediaStore (and general)
|
||||||
|
else if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||||
|
return getDataColumn(context, uri, null, null);
|
||||||
|
}
|
||||||
|
// File
|
||||||
|
else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||||
|
return uri.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the data column for this Uri. This is useful for
|
||||||
|
* MediaStore Uris, and other file-based ContentProviders.
|
||||||
|
*
|
||||||
|
* @param context The context.
|
||||||
|
* @param uri The Uri to query.
|
||||||
|
* @param selection (Optional) Filter used in the query.
|
||||||
|
* @param selectionArgs (Optional) Selection arguments used in the query.
|
||||||
|
* @return The value of the _data column, which is typically a file path.
|
||||||
|
*/
|
||||||
|
public static String getDataColumn(Context context, Uri uri, String selection,
|
||||||
|
String[] selectionArgs) {
|
||||||
|
|
||||||
|
Cursor cursor = null;
|
||||||
|
final String column = "_data";
|
||||||
|
final String[] projection = {
|
||||||
|
column
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
|
||||||
|
null);
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
final int column_index = cursor.getColumnIndexOrThrow(column);
|
||||||
|
return cursor.getString(column_index);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (cursor != null)
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param uri The Uri to check.
|
||||||
|
* @return Whether the Uri authority is ExternalStorageProvider.
|
||||||
|
*/
|
||||||
|
public static boolean isExternalStorageDocument(Uri uri) {
|
||||||
|
return "com.android.externalstorage.documents".equals(uri.getAuthority());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param uri The Uri to check.
|
||||||
|
* @return Whether the Uri authority is DownloadsProvider.
|
||||||
|
*/
|
||||||
|
public static boolean isDownloadsDocument(Uri uri) {
|
||||||
|
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param uri The Uri to check.
|
||||||
|
* @return Whether the Uri authority is MediaProvider.
|
||||||
|
*/
|
||||||
|
public static boolean isMediaDocument(Uri uri) {
|
||||||
|
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
package xyz.quaver.pupil.util
|
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.ContextWrapper
|
|
||||||
import android.content.Intent
|
|
||||||
import android.util.SparseArray
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import androidx.core.app.TaskStackBuilder
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.io.IOException
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
|
||||||
import kotlinx.serialization.list
|
|
||||||
import xyz.quaver.hitomi.*
|
|
||||||
import xyz.quaver.hiyobi.cookie
|
|
||||||
import xyz.quaver.hiyobi.user_agent
|
|
||||||
import xyz.quaver.pupil.R
|
|
||||||
import xyz.quaver.pupil.Pupil
|
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.net.URL
|
|
||||||
import java.util.*
|
|
||||||
import javax.net.ssl.HttpsURLConnection
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.concurrent.schedule
|
|
||||||
|
|
||||||
class GalleryDownloader(
|
|
||||||
base: Context,
|
|
||||||
private val galleryBlock: GalleryBlock,
|
|
||||||
_notify: Boolean = false
|
|
||||||
) : ContextWrapper(base) {
|
|
||||||
|
|
||||||
private val downloads = (applicationContext as Pupil).downloads
|
|
||||||
var useHiyobi = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("use_hiyobi", false)
|
|
||||||
|
|
||||||
var download: Boolean = false
|
|
||||||
set(value) {
|
|
||||||
if (value) {
|
|
||||||
field = true
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
|
||||||
|
|
||||||
val data = getCachedGallery(this, galleryBlock.id)
|
|
||||||
val cache = File(cacheDir, "imageCache/${galleryBlock.id}")
|
|
||||||
|
|
||||||
if (File(cache, "images").exists() && !data.exists()) {
|
|
||||||
cache.copyRecursively(data, true)
|
|
||||||
cache.deleteRecursively()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader?.isActive == false && downloadJob?.isActive != true)
|
|
||||||
field = false
|
|
||||||
|
|
||||||
downloads.add(galleryBlock.id)
|
|
||||||
} else {
|
|
||||||
field = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onNotifyChangedHandler?.invoke(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val reader: Deferred<Reader>?
|
|
||||||
private var downloadJob: Job? = null
|
|
||||||
|
|
||||||
private lateinit var notificationBuilder: NotificationCompat.Builder
|
|
||||||
private lateinit var notificationManager: NotificationManagerCompat
|
|
||||||
|
|
||||||
var onReaderLoadedHandler: ((Reader) -> Unit)? = null
|
|
||||||
var onProgressHandler: ((Int) -> Unit)? = null
|
|
||||||
var onDownloadedHandler: ((List<String>) -> Unit)? = null
|
|
||||||
var onErrorHandler: ((Exception) -> Unit)? = null
|
|
||||||
var onCompleteHandler: (() -> Unit)? = null
|
|
||||||
var onNotifyChangedHandler: ((Boolean) -> Unit)? = null
|
|
||||||
|
|
||||||
companion object : SparseArray<GalleryDownloader>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
put(galleryBlock.id, this)
|
|
||||||
|
|
||||||
initNotification()
|
|
||||||
|
|
||||||
reader = CoroutineScope(Dispatchers.IO).async {
|
|
||||||
download = _notify
|
|
||||||
val json = Json(JsonConfiguration.Stable)
|
|
||||||
val serializer = ReaderItem.serializer().list
|
|
||||||
|
|
||||||
//Check cache
|
|
||||||
val cache = File(getCachedGallery(this@GalleryDownloader, galleryBlock.id), "reader.json")
|
|
||||||
|
|
||||||
if (cache.exists()) {
|
|
||||||
val cached = json.parse(serializer, cache.readText())
|
|
||||||
|
|
||||||
if (cached.isNotEmpty()) {
|
|
||||||
useHiyobi = when {
|
|
||||||
cached.first().url.contains("hitomi.la") -> false
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
|
|
||||||
onReaderLoadedHandler?.invoke(cached)
|
|
||||||
|
|
||||||
return@async cached
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Cache doesn't exist. Load from internet
|
|
||||||
val reader = when {
|
|
||||||
useHiyobi -> {
|
|
||||||
xyz.quaver.hiyobi.getReader(galleryBlock.id).let {
|
|
||||||
when {
|
|
||||||
it.isEmpty() -> {
|
|
||||||
useHiyobi = false
|
|
||||||
getReader(galleryBlock.id)
|
|
||||||
}
|
|
||||||
else -> it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
getReader(galleryBlock.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader.isNotEmpty()) {
|
|
||||||
//Save cache
|
|
||||||
if (cache.parentFile?.exists() == false)
|
|
||||||
cache.parentFile!!.mkdirs()
|
|
||||||
|
|
||||||
cache.writeText(json.stringify(serializer, reader))
|
|
||||||
}
|
|
||||||
|
|
||||||
reader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
downloadJob = CoroutineScope(Dispatchers.Default).launch {
|
|
||||||
val reader = reader!!.await()
|
|
||||||
|
|
||||||
if (reader.isEmpty())
|
|
||||||
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
|
|
||||||
|
|
||||||
val list = ArrayList<String>()
|
|
||||||
|
|
||||||
onReaderLoadedHandler?.invoke(reader)
|
|
||||||
|
|
||||||
notificationBuilder
|
|
||||||
.setProgress(reader.size, 0, false)
|
|
||||||
.setContentText("0/${reader.size}")
|
|
||||||
|
|
||||||
reader.chunked(4).forEachIndexed { chunkIndex, chunked ->
|
|
||||||
chunked.mapIndexed { i, it ->
|
|
||||||
val index = chunkIndex*4+i
|
|
||||||
|
|
||||||
onProgressHandler?.invoke(index)
|
|
||||||
|
|
||||||
notificationBuilder
|
|
||||||
.setProgress(reader.size, index, false)
|
|
||||||
.setContentText("$index/${reader.size}")
|
|
||||||
|
|
||||||
if (download)
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
|
||||||
|
|
||||||
async(Dispatchers.IO) {
|
|
||||||
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
|
||||||
|
|
||||||
val name = "$index".padStart(4, '0')
|
|
||||||
val ext = url.split('.').last()
|
|
||||||
|
|
||||||
val cache = File(getCachedGallery(this@GalleryDownloader, galleryBlock.id), "images/$name.$ext")
|
|
||||||
|
|
||||||
if (!cache.exists())
|
|
||||||
try {
|
|
||||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
|
||||||
if (useHiyobi) {
|
|
||||||
setRequestProperty("User-Agent", user_agent)
|
|
||||||
setRequestProperty("Cookie", cookie)
|
|
||||||
} else
|
|
||||||
setRequestProperty("Referer", getReferer(galleryBlock.id))
|
|
||||||
|
|
||||||
if (cache.parentFile?.exists() == false)
|
|
||||||
cache.parentFile!!.mkdirs()
|
|
||||||
|
|
||||||
inputStream.copyTo(FileOutputStream(cache))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
cache.delete()
|
|
||||||
|
|
||||||
onErrorHandler?.invoke(e)
|
|
||||||
|
|
||||||
notificationBuilder
|
|
||||||
.setContentTitle(galleryBlock.title)
|
|
||||||
.setContentText(getString(R.string.reader_notification_error))
|
|
||||||
.setProgress(0, 0, false)
|
|
||||||
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.absolutePath
|
|
||||||
}
|
|
||||||
}.forEach {
|
|
||||||
list.add(it.await())
|
|
||||||
onDownloadedHandler?.invoke(list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer(false).schedule(1000) {
|
|
||||||
notificationBuilder
|
|
||||||
.setContentTitle(galleryBlock.title)
|
|
||||||
.setContentText(getString(R.string.reader_notification_complete))
|
|
||||||
.setProgress(0, 0, false)
|
|
||||||
|
|
||||||
if (download) {
|
|
||||||
File(cacheDir, "imageCache/${galleryBlock.id}").let {
|
|
||||||
if (it.exists()) {
|
|
||||||
val target = File(getDownloadDirectory(this@GalleryDownloader), galleryBlock.id.toString())
|
|
||||||
|
|
||||||
if (!target.exists())
|
|
||||||
target.mkdirs()
|
|
||||||
|
|
||||||
it.copyRecursively(target, true)
|
|
||||||
it.deleteRecursively()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
|
||||||
|
|
||||||
download = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onCompleteHandler?.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel() {
|
|
||||||
downloadJob?.cancel()
|
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun cancelAndJoin() {
|
|
||||||
downloadJob?.cancelAndJoin()
|
|
||||||
|
|
||||||
remove(galleryBlock.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invokeOnReaderLoaded() {
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
|
||||||
onReaderLoadedHandler?.invoke(reader?.await() ?: return@launch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearNotification() {
|
|
||||||
notificationManager.cancel(galleryBlock.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invokeOnNotifyChanged() {
|
|
||||||
onNotifyChangedHandler?.invoke(download)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initNotification() {
|
|
||||||
val intent = Intent(this, ReaderActivity::class.java).apply {
|
|
||||||
putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), galleryBlock))
|
|
||||||
}
|
|
||||||
val pendingIntent = TaskStackBuilder.create(this).run {
|
|
||||||
addNextIntentWithParentStack(intent)
|
|
||||||
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
|
|
||||||
setContentTitle(galleryBlock.title)
|
|
||||||
setContentText(getString(R.string.reader_notification_text))
|
|
||||||
setSmallIcon(R.drawable.ic_download)
|
|
||||||
setContentIntent(pendingIntent)
|
|
||||||
setProgress(0, 0, true)
|
|
||||||
priority = NotificationCompat.PRIORITY_LOW
|
|
||||||
}
|
|
||||||
notificationManager = NotificationManagerCompat.from(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
237
app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt
Normal file
237
app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util.download
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.util.Base64
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.parse
|
||||||
|
import kotlinx.serialization.stringify
|
||||||
|
import xyz.quaver.Code
|
||||||
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
|
import xyz.quaver.hitomi.Reader
|
||||||
|
import xyz.quaver.pupil.util.*
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
class Cache(context: Context) : ContextWrapper(context) {
|
||||||
|
|
||||||
|
private val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
|
// Search in this order
|
||||||
|
// Download -> Cache
|
||||||
|
fun getCachedGallery(galleryID: Int) : DocumentFile? {
|
||||||
|
var file = getDownloadDirectory(this).findFile(galleryID.toString())
|
||||||
|
|
||||||
|
if (file?.exists() == true)
|
||||||
|
return file
|
||||||
|
|
||||||
|
file = DocumentFile.fromFile(File(cacheDir, "imageCache/$galleryID"))
|
||||||
|
|
||||||
|
return if (file.exists())
|
||||||
|
file
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||||
|
fun getCachedMetadata(galleryID: Int) : Metadata? {
|
||||||
|
val file = (getCachedGallery(galleryID) ?: return null).findFile(".metadata")
|
||||||
|
|
||||||
|
if (file?.exists() != true)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return try {
|
||||||
|
Json.parse(file.readText(this))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
//File corrupted
|
||||||
|
file.delete()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseExperimental(ImplicitReflectionSerializer::class)
|
||||||
|
fun setCachedMetadata(galleryID: Int, metadata: Metadata) {
|
||||||
|
val file = getCachedGallery(galleryID)?.findFile(".metadata") ?:
|
||||||
|
DocumentFile.fromFile(File(cacheDir, "imageCache/$galleryID").also {
|
||||||
|
if (!it.exists())
|
||||||
|
it.mkdirs()
|
||||||
|
}).createFile("null", ".metadata") ?: return
|
||||||
|
|
||||||
|
file.writeText(this, Json.stringify(metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getThumbnail(galleryID: Int): String? {
|
||||||
|
val metadata = Cache(this).getCachedMetadata(galleryID)
|
||||||
|
|
||||||
|
val thumbnail = if (metadata?.thumbnail == null)
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val thumbnails = getGalleryBlock(galleryID)?.thumbnails
|
||||||
|
try {
|
||||||
|
Base64.encodeToString(URL(thumbnails?.firstOrNull()).readBytes(), Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
metadata.thumbnail
|
||||||
|
|
||||||
|
setCachedMetadata(
|
||||||
|
galleryID,
|
||||||
|
Metadata(Cache(this).getCachedMetadata(galleryID), thumbnail = thumbnail)
|
||||||
|
)
|
||||||
|
|
||||||
|
return thumbnail
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getGalleryBlock(galleryID: Int): GalleryBlock? {
|
||||||
|
val metadata = Cache(this).getCachedMetadata(galleryID)
|
||||||
|
|
||||||
|
val source = mapOf(
|
||||||
|
Code.HITOMI to { xyz.quaver.hitomi.getGalleryBlock(galleryID) },
|
||||||
|
Code.HIYOBI to { xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
|
||||||
|
)
|
||||||
|
|
||||||
|
val galleryBlock = if (metadata?.galleryBlock == null)
|
||||||
|
source.entries.map {
|
||||||
|
CoroutineScope(Dispatchers.IO).async {
|
||||||
|
kotlin.runCatching {
|
||||||
|
it.value.invoke()
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
}.firstOrNull {
|
||||||
|
it.await() != null
|
||||||
|
}?.await()
|
||||||
|
else
|
||||||
|
metadata.galleryBlock
|
||||||
|
|
||||||
|
setCachedMetadata(
|
||||||
|
galleryID,
|
||||||
|
Metadata(Cache(this).getCachedMetadata(galleryID), galleryBlock = galleryBlock)
|
||||||
|
)
|
||||||
|
|
||||||
|
return galleryBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReaderOrNull(galleryID: Int): Reader? {
|
||||||
|
return getCachedMetadata(galleryID)?.reader
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getReader(galleryID: Int): Reader? {
|
||||||
|
val metadata = getCachedMetadata(galleryID)
|
||||||
|
val mirrors = preference.getString("mirrors", null)?.split('>') ?: listOf()
|
||||||
|
|
||||||
|
val sources = mapOf(
|
||||||
|
Code.HITOMI to { xyz.quaver.hitomi.getReader(galleryID) },
|
||||||
|
Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) }
|
||||||
|
).let {
|
||||||
|
if (mirrors.isNotEmpty())
|
||||||
|
it.toSortedMap(
|
||||||
|
Comparator { o1, o2 ->
|
||||||
|
mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
|
val reader = if (metadata?.reader == null) {
|
||||||
|
CoroutineScope(Dispatchers.IO).async {
|
||||||
|
var retval: Reader? = null
|
||||||
|
|
||||||
|
for (source in sources) {
|
||||||
|
retval = kotlin.runCatching {
|
||||||
|
source.value.invoke()
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (retval != null)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
retval
|
||||||
|
}.await()
|
||||||
|
} else
|
||||||
|
metadata.reader
|
||||||
|
|
||||||
|
if (reader != null)
|
||||||
|
setCachedMetadata(
|
||||||
|
galleryID,
|
||||||
|
Metadata(Cache(this).getCachedMetadata(galleryID), readers = reader)
|
||||||
|
)
|
||||||
|
|
||||||
|
return reader
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getImages(galleryID: Int): List<DocumentFile?>? {
|
||||||
|
val gallery = getCachedGallery(galleryID) ?: return null
|
||||||
|
val reader = getReaderOrNull(galleryID) ?: return null
|
||||||
|
val images = gallery.listFiles()
|
||||||
|
|
||||||
|
return reader.galleryInfo.indices.map { index ->
|
||||||
|
images.firstOrNull { file -> file.name?.startsWith("%05d".format(index)) == true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun putImage(galleryID: Int, name: String, data: ByteArray) {
|
||||||
|
val cache = getCachedGallery(galleryID) ?:
|
||||||
|
DocumentFile.fromFile(File(cacheDir, "imageCache/$galleryID").also {
|
||||||
|
if (!it.exists())
|
||||||
|
it.mkdirs()
|
||||||
|
}) ?: return
|
||||||
|
|
||||||
|
if (!Regex("""^[0-9]+.+$""").matches(name))
|
||||||
|
throw IllegalArgumentException("File name is not a number")
|
||||||
|
|
||||||
|
cache.let {
|
||||||
|
if (it.findFile(name) != null)
|
||||||
|
it
|
||||||
|
else
|
||||||
|
it.createFile("null", name)
|
||||||
|
}?.writeBytes(this, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveToDownload(galleryID: Int) {
|
||||||
|
val cache = getCachedGallery(galleryID)
|
||||||
|
|
||||||
|
if (cache != null) {
|
||||||
|
val download = getDownloadDirectory(this)
|
||||||
|
|
||||||
|
if (!download.isParentOf(cache)) {
|
||||||
|
cache.copyRecursively(this, download)
|
||||||
|
cache.deleteRecursively()
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
getDownloadDirectory(this).createDirectory(galleryID.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true
|
||||||
|
|
||||||
|
fun setDownloading(galleryID: Int, isDownloading: Boolean) {
|
||||||
|
setCachedMetadata(galleryID, Metadata(getCachedMetadata(galleryID), isDownloading = isDownloading))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util.download
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.util.SparseArray
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.app.TaskStackBuilder
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.crashlytics.android.Crashlytics
|
||||||
|
import io.fabric.sdk.android.Fabric
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import okhttp3.*
|
||||||
|
import okio.*
|
||||||
|
import xyz.quaver.Code
|
||||||
|
import xyz.quaver.hitomi.Reader
|
||||||
|
import xyz.quaver.hitomi.getReferer
|
||||||
|
import xyz.quaver.hitomi.urlFromUrlFromHash
|
||||||
|
import xyz.quaver.hiyobi.cookie
|
||||||
|
import xyz.quaver.hiyobi.createImgList
|
||||||
|
import xyz.quaver.hiyobi.user_agent
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
|
||||||
|
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||||
|
class DownloadWorker private constructor(context: Context) : ContextWrapper(context) {
|
||||||
|
|
||||||
|
private val preferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
|
//region ProgressListener
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private val progressListener = object: ProgressListener {
|
||||||
|
override fun update(tag: Any?, bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||||
|
val (galleryID, index) = (tag as? Pair<Int, Int>) ?: return
|
||||||
|
|
||||||
|
if (!done && progress[galleryID]?.get(index)?.isFinite() == true)
|
||||||
|
progress[galleryID]?.set(index, bytesRead * 100F / contentLength)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProgressListener {
|
||||||
|
fun update(tag: Any?, bytesRead : Long, contentLength: Long, done: Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProgressResponseBody(
|
||||||
|
val tag: Any?,
|
||||||
|
val responseBody: ResponseBody,
|
||||||
|
val progressListener : ProgressListener
|
||||||
|
) : ResponseBody() {
|
||||||
|
private var bufferedSource : BufferedSource? = null
|
||||||
|
|
||||||
|
override fun contentLength() = responseBody.contentLength()
|
||||||
|
override fun contentType() = responseBody.contentType() ?: null
|
||||||
|
|
||||||
|
override fun source(): BufferedSource {
|
||||||
|
if (bufferedSource == null)
|
||||||
|
bufferedSource = Okio.buffer(source(responseBody.source()))
|
||||||
|
|
||||||
|
return bufferedSource!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun source(source: Source) = object: ForwardingSource(source) {
|
||||||
|
|
||||||
|
var totalBytesRead = 0L
|
||||||
|
|
||||||
|
override fun read(sink: Buffer, byteCount: Long): Long {
|
||||||
|
val bytesRead = super.read(sink, byteCount)
|
||||||
|
|
||||||
|
totalBytesRead += if (bytesRead == -1L) 0L else bytesRead
|
||||||
|
progressListener.update(tag, totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
|
||||||
|
|
||||||
|
return bytesRead
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region Singleton
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@Volatile private var instance: DownloadWorker? = null
|
||||||
|
|
||||||
|
fun getInstance(context: Context) =
|
||||||
|
instance ?: synchronized(this) {
|
||||||
|
instance ?: DownloadWorker(context).also { instance = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
|
||||||
|
val queue = LinkedBlockingQueue<Int>()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* KEY
|
||||||
|
* primary galleryID
|
||||||
|
* secondary index
|
||||||
|
* PRIMARY VALUE
|
||||||
|
* MutableList -> Download in progress
|
||||||
|
* null -> Loading / Gallery doesn't exist
|
||||||
|
* SECONDARY VALUE
|
||||||
|
* 0 <= value < 100 -> Download in progress
|
||||||
|
* Float.POSITIVE_INFINITY -> Download completed
|
||||||
|
* Float.NaN -> Exception
|
||||||
|
*/
|
||||||
|
val progress = SparseArray<MutableList<Float>?>()
|
||||||
|
/*
|
||||||
|
* KEY
|
||||||
|
* primary galleryID
|
||||||
|
* secondary index
|
||||||
|
* PRIMARY VALUE
|
||||||
|
* MutableList -> Download in progress / Loading
|
||||||
|
* null -> Gallery doesn't exist
|
||||||
|
* SECONDARY VALUE
|
||||||
|
* Throwable -> Exception
|
||||||
|
* null -> Download in progress / Loading
|
||||||
|
*/
|
||||||
|
val exception = SparseArray<MutableList<Throwable?>?>()
|
||||||
|
val notification = SparseArray<NotificationCompat.Builder>()
|
||||||
|
|
||||||
|
private val loop = loop()
|
||||||
|
private val worker = SparseArray<Job?>()
|
||||||
|
@Volatile var nRunners = 0
|
||||||
|
|
||||||
|
private val client = OkHttpClient.Builder()
|
||||||
|
.addInterceptor { chain ->
|
||||||
|
val request = chain.request()
|
||||||
|
var response = chain.proceed(request)
|
||||||
|
|
||||||
|
var retry = preferences.getInt("retry", 3)
|
||||||
|
while (!response.isSuccessful && retry > 0) {
|
||||||
|
response = chain.proceed(request)
|
||||||
|
retry--
|
||||||
|
}
|
||||||
|
|
||||||
|
response.newBuilder()
|
||||||
|
.body(ProgressResponseBody(request.tag(), response.body(), progressListener))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
.dispatcher(Dispatcher(Executors.newFixedThreadPool(4)))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
queue.clear()
|
||||||
|
|
||||||
|
loop.cancel()
|
||||||
|
for (i in 0..worker.size()) {
|
||||||
|
val galleryID = worker.keyAt(i)
|
||||||
|
|
||||||
|
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
||||||
|
worker[galleryID]?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
client.dispatcher().cancelAll()
|
||||||
|
|
||||||
|
progress.clear()
|
||||||
|
exception.clear()
|
||||||
|
notification.clear()
|
||||||
|
notificationManager.cancelAll()
|
||||||
|
|
||||||
|
nRunners = 0
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(galleryID: Int) {
|
||||||
|
queue.remove(galleryID)
|
||||||
|
worker[galleryID]?.cancel()
|
||||||
|
|
||||||
|
client.dispatcher().queuedCalls()
|
||||||
|
.filter {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(it.request().tag() as? Pair<Int, Int>)?.first == galleryID
|
||||||
|
}
|
||||||
|
.forEach {
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.remove(galleryID)
|
||||||
|
exception.remove(galleryID)
|
||||||
|
notification.remove(galleryID)
|
||||||
|
notificationManager.cancel(galleryID)
|
||||||
|
|
||||||
|
if (progress.indexOfKey(galleryID) >= 0) {
|
||||||
|
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
||||||
|
nRunners--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isCompleted(galleryID: Int) = progress[galleryID]?.all { !it.isFinite() } == true
|
||||||
|
|
||||||
|
private fun queueDownload(galleryID: Int, reader: Reader, index: Int, callback: Callback) {
|
||||||
|
val cache = Cache(this@DownloadWorker).getImages(galleryID)
|
||||||
|
val lowQuality = preferences.getBoolean("low_quality", false)
|
||||||
|
|
||||||
|
//Cache exists :P
|
||||||
|
cache?.get(index)?.let {
|
||||||
|
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
||||||
|
|
||||||
|
notify(galleryID)
|
||||||
|
|
||||||
|
if (isCompleted(galleryID)) {
|
||||||
|
with(Cache(this@DownloadWorker)) {
|
||||||
|
if (isDownloading(galleryID)) {
|
||||||
|
moveToDownload(galleryID)
|
||||||
|
setDownloading(galleryID, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nRunners--
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val request = Request.Builder().apply {
|
||||||
|
when (reader.code) {
|
||||||
|
Code.HITOMI -> {
|
||||||
|
url(
|
||||||
|
urlFromUrlFromHash(
|
||||||
|
galleryID,
|
||||||
|
reader.galleryInfo[index],
|
||||||
|
if (lowQuality) "webp" else null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
addHeader("Referer", getReferer(galleryID))
|
||||||
|
}
|
||||||
|
Code.HIYOBI -> {
|
||||||
|
url(createImgList(galleryID, reader, lowQuality)[index].path)
|
||||||
|
addHeader("User-Agent", user_agent)
|
||||||
|
addHeader("Cookie", cookie)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
//shouldn't be called anyway
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag(galleryID to index)
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val reader = Cache(this@DownloadWorker).getReader(galleryID)
|
||||||
|
|
||||||
|
//gallery doesn't exist
|
||||||
|
if (reader == null) {
|
||||||
|
progress.put(galleryID, null)
|
||||||
|
exception.put(galleryID, null)
|
||||||
|
|
||||||
|
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
||||||
|
nRunners--
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.put(galleryID, reader.galleryInfo.map { 0F }.toMutableList())
|
||||||
|
exception.put(galleryID, reader.galleryInfo.map { null }.toMutableList())
|
||||||
|
|
||||||
|
if (notification[galleryID] == null)
|
||||||
|
initNotification(galleryID)
|
||||||
|
|
||||||
|
notification[galleryID].setContentTitle(reader.title)
|
||||||
|
notify(galleryID)
|
||||||
|
|
||||||
|
for (i in reader.galleryInfo.indices) {
|
||||||
|
val callback = object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
if (Fabric.isInitialized())
|
||||||
|
Crashlytics.logException(e)
|
||||||
|
|
||||||
|
progress[galleryID]?.set(i, Float.NaN)
|
||||||
|
exception[galleryID]?.set(i, e)
|
||||||
|
|
||||||
|
notify(galleryID)
|
||||||
|
|
||||||
|
if (isCompleted(galleryID)) {
|
||||||
|
val cache = Cache(this@DownloadWorker)
|
||||||
|
if (cache.isDownloading(galleryID)) {
|
||||||
|
cache.moveToDownload(galleryID)
|
||||||
|
cache.setDownloading(galleryID, false)
|
||||||
|
}
|
||||||
|
nRunners--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
response.body().use {
|
||||||
|
val res = it.bytes()
|
||||||
|
val ext =
|
||||||
|
call.request().url().encodedPath().split('.').last()
|
||||||
|
|
||||||
|
Cache(this@DownloadWorker).putImage(galleryID, "%05d.%s".format(i, ext), res)
|
||||||
|
progress[galleryID]?.set(i, Float.POSITIVE_INFINITY)
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(galleryID)
|
||||||
|
|
||||||
|
if (isCompleted(galleryID)) {
|
||||||
|
val cache = Cache(this@DownloadWorker)
|
||||||
|
if (cache.isDownloading(galleryID)) {
|
||||||
|
cache.moveToDownload(galleryID)
|
||||||
|
cache.setDownloading(galleryID, false)
|
||||||
|
}
|
||||||
|
nRunners--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queueDownload(galleryID, reader, i, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notify(galleryID: Int) {
|
||||||
|
val max = progress[galleryID]?.size ?: 0
|
||||||
|
val progress = progress[galleryID]?.count { !it.isFinite() } ?: 0
|
||||||
|
|
||||||
|
if (isCompleted(galleryID))
|
||||||
|
notification[galleryID]
|
||||||
|
?.setContentText(getString(R.string.reader_notification_complete))
|
||||||
|
?.setProgress(0, 0, false)
|
||||||
|
else
|
||||||
|
notification[galleryID]
|
||||||
|
?.setProgress(max, progress, false)
|
||||||
|
?.setContentText("$progress/$max")
|
||||||
|
|
||||||
|
if (Cache(this).isDownloading(galleryID) && notification[galleryID] != null)
|
||||||
|
notificationManager.notify(galleryID, notification[galleryID].build())
|
||||||
|
else
|
||||||
|
notificationManager.cancel(galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initNotification(galleryID: Int) {
|
||||||
|
val intent = Intent(this, ReaderActivity::class.java).apply {
|
||||||
|
putExtra("galleryID", galleryID)
|
||||||
|
}
|
||||||
|
val pendingIntent = TaskStackBuilder.create(this).run {
|
||||||
|
addNextIntentWithParentStack(intent)
|
||||||
|
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.put(galleryID, NotificationCompat.Builder(this, "download").apply {
|
||||||
|
setContentTitle(getString(R.string.reader_loading))
|
||||||
|
setContentText(getString(R.string.reader_notification_text))
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download) // had to use this because old android doesn't support VectorDrawable on Notification :P
|
||||||
|
setContentIntent(pendingIntent)
|
||||||
|
setProgress(0, 0, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loop() = CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
while (true) {
|
||||||
|
if (queue.isEmpty() || nRunners > preferences.getInt("max_download", 4))
|
||||||
|
continue
|
||||||
|
|
||||||
|
val galleryID = queue.poll() ?: continue
|
||||||
|
|
||||||
|
if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading!
|
||||||
|
continue
|
||||||
|
|
||||||
|
initNotification(galleryID)
|
||||||
|
if (Cache(this@DownloadWorker).isDownloading(galleryID))
|
||||||
|
notificationManager.notify(galleryID, notification[galleryID].build())
|
||||||
|
worker.put(galleryID, download(galleryID))
|
||||||
|
nRunners++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
44
app/src/main/java/xyz/quaver/pupil/util/download/Metadata.kt
Normal file
44
app/src/main/java/xyz/quaver/pupil/util/download/Metadata.kt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util.download
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
|
import xyz.quaver.hitomi.Reader
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Metadata(
|
||||||
|
val thumbnail: String? = null,
|
||||||
|
val galleryBlock: GalleryBlock? = null,
|
||||||
|
val reader: Reader? = null,
|
||||||
|
val isDownloading: Boolean? = null
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
metadata: Metadata?,
|
||||||
|
thumbnail: String? = null,
|
||||||
|
galleryBlock: GalleryBlock? = null,
|
||||||
|
readers: Reader? = null,
|
||||||
|
isDownloading: Boolean? = null
|
||||||
|
) : this(
|
||||||
|
thumbnail ?: metadata?.thumbnail,
|
||||||
|
galleryBlock ?: metadata?.galleryBlock,
|
||||||
|
readers ?: metadata?.reader,
|
||||||
|
isDownloading ?: metadata?.isDownloading
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,24 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.net.Uri
|
||||||
import android.os.Environment
|
import androidx.core.content.FileProvider
|
||||||
import android.provider.MediaStore
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.preference.PreferenceManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.net.URL
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
fun getCachedGallery(context: Context, galleryID: Int): File {
|
fun getCachedGallery(context: Context, galleryID: Int) =
|
||||||
return File(getDownloadDirectory(context), galleryID.toString()).let {
|
getDownloadDirectory(context).findFile(galleryID.toString()) ?:
|
||||||
when {
|
DocumentFile.fromFile(File(context.cacheDir, "imageCache/$galleryID"))
|
||||||
it.exists() -> it
|
|
||||||
else -> File(context.cacheDir, "imageCache/$galleryID")
|
fun getDownloadDirectory(context: Context) : DocumentFile {
|
||||||
|
val uri = PreferenceManager.getDefaultSharedPreferences(context).getString("dl_location", null).let {
|
||||||
|
if (it != null)
|
||||||
|
Uri.parse(it)
|
||||||
|
else
|
||||||
|
Uri.fromFile(context.getExternalFilesDir(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (uri.toString().startsWith("file"))
|
||||||
|
DocumentFile.fromFile(File(uri.path!!))
|
||||||
|
else
|
||||||
|
DocumentFile.fromTreeUri(context, uri) ?: DocumentFile.fromFile(context.getExternalFilesDir(null)!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun convertUpdateUri(context: Context, uri: Uri) : Uri =
|
||||||
|
if (uri.toString().startsWith("file"))
|
||||||
|
FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", File(uri.path!!.substringAfter("file:///")))
|
||||||
|
else
|
||||||
|
uri
|
||||||
|
|
||||||
|
fun URL.download(context: Context, to: DocumentFile, onDownloadProgress: ((Long, Long) -> Unit)? = null) {
|
||||||
|
context.contentResolver.openOutputStream(to.uri).use { out ->
|
||||||
|
out!!
|
||||||
|
|
||||||
|
with(openConnection()) {
|
||||||
|
val fileSize = contentLength.toLong()
|
||||||
|
|
||||||
|
getInputStream().use {
|
||||||
|
|
||||||
|
var bytesCopied: Long = 0
|
||||||
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
|
|
||||||
|
var bytes = it.read(buffer)
|
||||||
|
while (bytes >= 0) {
|
||||||
|
out.write(buffer, 0, bytes)
|
||||||
|
bytesCopied += bytes
|
||||||
|
onDownloadProgress?.invoke(bytesCopied, fileSize)
|
||||||
|
bytes = it.read(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DocumentFile.isParentOf(file: DocumentFile?) : Boolean {
|
||||||
|
var parent = file?.parentFile
|
||||||
|
while (parent != null) {
|
||||||
|
if (this.uri.path == parent.uri.path)
|
||||||
|
return true
|
||||||
|
|
||||||
|
parent = parent.parentFile
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DocumentFile.reader(context: Context, charset: Charset = Charsets.UTF_8) = context.contentResolver.openInputStream(uri)!!.reader(charset)
|
||||||
|
fun DocumentFile.readBytes(context: Context) = context.contentResolver.openInputStream(uri)!!.readBytes()
|
||||||
|
fun DocumentFile.readText(context: Context, charset: Charset = Charsets.UTF_8) = reader(context, charset).use { it.readText() }
|
||||||
|
|
||||||
|
fun DocumentFile.writeBytes(context: Context, array: ByteArray) = context.contentResolver.openOutputStream(uri)!!.write(array)
|
||||||
|
fun DocumentFile.writeText(context: Context, text: String, charset: Charset = Charsets.UTF_8) = writeBytes(context, text.toByteArray(charset))
|
||||||
|
|
||||||
|
fun DocumentFile.copyRecursively(
|
||||||
|
context: Context,
|
||||||
|
target: DocumentFile
|
||||||
|
) {
|
||||||
|
if (!exists())
|
||||||
|
throw Exception("The source file doesn't exist.")
|
||||||
|
|
||||||
|
if (this.isFile) {
|
||||||
|
target.let {
|
||||||
|
if (it.findFile(name!!) != null)
|
||||||
|
it
|
||||||
|
else
|
||||||
|
createFile("null", name!!)!!
|
||||||
|
}.writeBytes(
|
||||||
|
context,
|
||||||
|
readBytes(context)
|
||||||
|
)
|
||||||
|
} else if (this.isDirectory) {
|
||||||
|
target.createDirectory(name!!).also { newTarget ->
|
||||||
|
listFiles().forEach { child ->
|
||||||
|
child.copyRecursively(context, newTarget!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadDirectory(context: Context): File? {
|
fun DocumentFile.deleteRecursively() {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
|
||||||
context.getExternalFilesDir("Pupil")
|
if (this.isDirectory)
|
||||||
else
|
listFiles().forEach {
|
||||||
File(Environment.getExternalStorageDirectory(), "Pupil")
|
it.deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun DocumentFile.walk(state: LinkedList<DocumentFile> = LinkedList()) : Queue<DocumentFile> {
|
||||||
|
if (state.isEmpty())
|
||||||
|
state.push(this)
|
||||||
|
|
||||||
|
listFiles().forEach {
|
||||||
|
state.push(it)
|
||||||
|
|
||||||
|
if (it.isDirectory) {
|
||||||
|
it.walk(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
fun File.copyTo(context: Context, target: DocumentFile) = target.writeBytes(context, this.readBytes())
|
||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
@@ -11,7 +29,7 @@ class Histories(private val file: File) : ArrayList<Int>() {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
file.parentFile.mkdirs()
|
file.parentFile?.mkdirs()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
load()
|
load()
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -94,10 +112,12 @@ class LockManager(base: Context): ContextWrapper(base) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun empty(): Boolean {
|
fun isEmpty(): Boolean {
|
||||||
return locks.isNullOrEmpty()
|
return locks.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isNotEmpty(): Boolean = !isEmpty()
|
||||||
|
|
||||||
fun contains(type: Lock.Type): Boolean {
|
fun contains(type: Lock.Type): Boolean {
|
||||||
return locks?.any { it.type == type } ?: false
|
return locks?.any { it.type == type } ?: false
|
||||||
}
|
}
|
||||||
|
|||||||
54
app/src/main/java/xyz/quaver/pupil/util/misc.kt
Normal file
54
app/src/main/java/xyz/quaver/pupil/util/misc.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
|
@UseExperimental(ExperimentalStdlibApi::class)
|
||||||
|
fun String.wordCapitalize() : String {
|
||||||
|
val result = ArrayList<String>()
|
||||||
|
|
||||||
|
@SuppressLint("DefaultLocale")
|
||||||
|
for (word in this.split(" "))
|
||||||
|
result.add(word.capitalize(Locale.US))
|
||||||
|
|
||||||
|
return result.joinToString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun byteToString(byte: Long, precision : Int = 1) : String {
|
||||||
|
|
||||||
|
val suffix = listOf(
|
||||||
|
"B",
|
||||||
|
"kB",
|
||||||
|
"MB",
|
||||||
|
"GB",
|
||||||
|
"TB" //really?
|
||||||
|
)
|
||||||
|
var size = byte.toDouble(); var suffixIndex = 0
|
||||||
|
|
||||||
|
while (size >= 1024) {
|
||||||
|
size /= 1024
|
||||||
|
suffixIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
return "%.${precision}f ${suffix[suffixIndex]}".format(size)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,8 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.util.Log
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
import ru.noties.markwon.Markwon
|
||||||
|
import xyz.quaver.pupil.BuildConfig
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
fun getReleases(url: String) : JsonArray {
|
fun getReleases(url: String) : JsonArray {
|
||||||
return try {
|
return try {
|
||||||
@@ -14,26 +48,160 @@ fun getReleases(url: String) : JsonArray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkUpdate(url: String, currentVersion: String) : JsonObject? {
|
fun checkUpdate(context: Context, url: String) : JsonObject? {
|
||||||
val releases = getReleases(url)
|
val releases = getReleases(url)
|
||||||
|
|
||||||
if (releases.isEmpty())
|
if (releases.isEmpty())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
val latestVersion = releases[0].jsonObject["tag_name"]?.content
|
return releases.firstOrNull {
|
||||||
|
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("beta", false))
|
||||||
|
true
|
||||||
|
else
|
||||||
|
it.jsonObject["prerelease"]?.boolean == false
|
||||||
|
}?.let {
|
||||||
|
if (it.jsonObject["tag_name"]?.content == BuildConfig.VERSION_NAME)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
it.jsonObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return when {
|
fun getApkUrl(releases: JsonObject) : String? {
|
||||||
currentVersion.split('-').size == 1 -> {
|
return releases["assets"]?.jsonArray?.firstOrNull {
|
||||||
when {
|
Regex("Pupil-v.+\\.apk").matches(it.jsonObject["name"]?.content ?: "")
|
||||||
currentVersion != latestVersion -> releases[0].jsonObject
|
}.let {
|
||||||
else -> null
|
it?.jsonObject?.get("browser_download_url")?.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const val UPDATE_NOTIFICATION_ID = 384823
|
||||||
|
fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
|
||||||
|
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0)
|
||||||
|
|
||||||
|
if (!force && ignoreUpdateUntil > System.currentTimeMillis())
|
||||||
|
return
|
||||||
|
|
||||||
|
fun extractReleaseNote(update: JsonObject, locale: Locale) : String {
|
||||||
|
val markdown = update["body"]!!.content
|
||||||
|
|
||||||
|
val target = when(locale.language) {
|
||||||
|
"ko" -> "한국어"
|
||||||
|
"ja" -> "日本語"
|
||||||
|
else -> "English"
|
||||||
|
}
|
||||||
|
|
||||||
|
val releaseNote = Regex("^# Release Note.+$")
|
||||||
|
val language = Regex("^## $target$")
|
||||||
|
val end = Regex("^#.+$")
|
||||||
|
|
||||||
|
var releaseNoteFlag = false
|
||||||
|
var languageFlag = false
|
||||||
|
|
||||||
|
val result = StringBuilder()
|
||||||
|
|
||||||
|
for(line in markdown.lines()) {
|
||||||
|
if (releaseNote.matches(line)) {
|
||||||
|
releaseNoteFlag = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (releaseNoteFlag) {
|
||||||
|
if (language.matches(line)) {
|
||||||
|
languageFlag = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languageFlag) {
|
||||||
|
if (end.matches(line))
|
||||||
|
break
|
||||||
|
|
||||||
|
result.append(line+"\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
when {
|
return context.getString(R.string.update_release_note, update["tag_name"]?.content, result.toString())
|
||||||
(currentVersion.split('-')[0] == latestVersion) -> releases[0].jsonObject
|
}
|
||||||
else -> null
|
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
val update =
|
||||||
|
checkUpdate(context, context.getString(R.string.release_url)) ?: return@launch
|
||||||
|
|
||||||
|
val url = getApkUrl(update) ?: return@launch
|
||||||
|
|
||||||
|
val dialog = AlertDialog.Builder(context).apply {
|
||||||
|
setTitle(R.string.update_title)
|
||||||
|
val msg = extractReleaseNote(update, Locale.getDefault())
|
||||||
|
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||||
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
|
||||||
|
val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
val builder = NotificationCompat.Builder(context, "download").apply {
|
||||||
|
setContentTitle(context.getString(R.string.update_notification_description))
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||||
|
priority = NotificationCompat.PRIORITY_LOW
|
||||||
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch io@{
|
||||||
|
val target = getDownloadDirectory(context).let {
|
||||||
|
if (it.findFile("Pupil.apk") != null)
|
||||||
|
it
|
||||||
|
else
|
||||||
|
it.createFile("null", "Pupil.apk")!!
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
URL(url).download(context, target) { progress, fileSize ->
|
||||||
|
builder.setProgress(fileSize.toInt(), progress.toInt(), false)
|
||||||
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
builder.apply {
|
||||||
|
setContentText(context.getString(R.string.update_failed))
|
||||||
|
setMessage(context.getString(R.string.update_failed_message))
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
||||||
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
|
||||||
|
|
||||||
|
return@io
|
||||||
|
}
|
||||||
|
|
||||||
|
val install = Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
setDataAndType(convertUpdateUri(context, target.uri), MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.apply {
|
||||||
|
setContentIntent(PendingIntent.getActivity(context, 0, install, 0))
|
||||||
|
setProgress(0, 0, false)
|
||||||
|
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
setContentTitle(context.getString(R.string.update_download_completed))
|
||||||
|
setContentText(context.getString(R.string.update_download_completed_description))
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
||||||
|
|
||||||
|
if (context.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED))
|
||||||
|
context.startActivity(install)
|
||||||
|
else
|
||||||
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
setNegativeButton(if (force) android.R.string.no else R.string.ignore_update) { _, _ ->
|
||||||
|
if (!force)
|
||||||
|
preferences.edit()
|
||||||
|
.putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
dialog.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#333333">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#333333">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#FFFFFF">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#333333">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#333333">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#333333">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#333333">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#FFFFFF">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="#FFFFFF">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
|
|
||||||
</vector>
|
|
||||||
8
app/src/main/res/drawable/arrow_right.xml
Normal file
8
app/src/main/res/drawable/arrow_right.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/arrow_right.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#fff" android:pathData="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" />
|
||||||
|
</vector>
|
||||||
@@ -13,14 +13,14 @@
|
|||||||
<path
|
<path
|
||||||
android:name="path"
|
android:name="path"
|
||||||
android:pathData="M 12 15.39 L 8.24 17.66 L 9.23 13.38 L 5.91 10.5 L 10.29 10.13 L 12 6.09 L 13.71 10.13 L 18.09 10.5 L 14.77 13.38 L 15.76 17.66 M 22 9.24 L 14.81 8.63 L 12 2 L 9.19 8.63 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 Z"
|
android:pathData="M 12 15.39 L 8.24 17.66 L 9.23 13.38 L 5.91 10.5 L 10.29 10.13 L 12 6.09 L 13.71 10.13 L 18.09 10.5 L 14.77 13.38 L 15.76 17.66 M 22 9.24 L 14.81 8.63 L 12 2 L 9.19 8.63 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 Z"
|
||||||
android:fillColor="#000"/>
|
android:fillColor="@color/material_orange_500"/>
|
||||||
<clip-path
|
<clip-path
|
||||||
android:name="clip"
|
android:name="clip"
|
||||||
android:pathData="M 2 21 L 2 21 L 22 21 L 22 21 Z"/>
|
android:pathData="M 2 21 L 2 21 L 22 21 L 22 21 Z"/>
|
||||||
<path
|
<path
|
||||||
android:name="path_1"
|
android:name="path_1"
|
||||||
android:pathData="M 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 L 14.81 8.62 L 12 2 L 9.19 8.62 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 Z"
|
android:pathData="M 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 L 14.81 8.62 L 12 2 L 9.19 8.62 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 Z"
|
||||||
android:fillColor="#000"/>
|
android:fillColor="@color/material_orange_500"/>
|
||||||
</vector>
|
</vector>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
<target android:name="clip">
|
<target android:name="clip">
|
||||||
|
|||||||
BIN
app/src/main/res/drawable/hitomi.png
Normal file
BIN
app/src/main/res/drawable/hitomi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<vector android:height="24dp" android:tint="#fff"
|
<vector android:height="24dp"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="#ff000000" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
<path android:fillColor="#fff" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<animated-vector
|
<animated-vector
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<!-- drawable/numeric.xml -->
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#000" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<vector android:height="24dp" android:width="24dp"
|
<vector android:height="24dp" android:width="24dp"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:viewportHeight="24.0" android:viewportWidth="24.0" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="#000" android:pathData="M 12 15.39 L 8.24 17.66 L 9.23 13.38 L 5.91 10.5 L 10.29 10.13 L 12 6.09 L 13.71 10.13 L 18.09 10.5 L 14.77 13.38 L 15.76 17.66 M 22 9.24 L 14.81 8.63 L 12 2 L 9.19 8.63 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 Z"/>
|
<path android:fillColor="@color/material_orange_500" android:pathData="M 12 15.39 L 8.24 17.66 L 9.23 13.38 L 5.91 10.5 L 10.29 10.13 L 12 6.09 L 13.71 10.13 L 18.09 10.5 L 14.77 13.38 L 15.76 17.66 M 22 9.24 L 14.81 8.63 L 12 2 L 9.19 8.63 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<vector android:height="24dp" android:width="24dp"
|
<vector android:height="24dp" android:width="24dp"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:viewportHeight="24.0" android:viewportWidth="24.0" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="#000" android:pathData="M 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 L 14.81 8.62 L 12 2 L 9.19 8.62 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 Z"/>
|
<path android:fillColor="@color/material_orange_500" android:pathData="M 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 L 14.81 8.62 L 12 2 L 9.19 8.62 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 Z"/>
|
||||||
</vector>
|
</vector>
|
||||||
8
app/src/main/res/drawable/image_broken_variant.xml
Normal file
8
app/src/main/res/drawable/image_broken_variant.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/image_broken_variant.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#000" android:pathData="M21,5V11.59L18,8.58L14,12.59L10,8.59L6,12.59L3,9.58V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5M18,11.42L21,14.43V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V12.42L6,15.41L10,11.41L14,15.41" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/menu.xml
Normal file
8
app/src/main/res/drawable/menu.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/menu.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#fff" android:pathData="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />
|
||||||
|
</vector>
|
||||||
@@ -4,5 +4,5 @@
|
|||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path android:fillColor="#000" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" />
|
<path android:fillColor="#fff" android:pathData="M4,17V9H2V7H6V17H4M22,15C22,16.11 21.1,17 20,17H16V15H20V13H18V11H20V9H16V7H20A2,2 0 0,1 22,9V10.5A1.5,1.5 0 0,1 20.5,12A1.5,1.5 0 0,1 22,13.5V15M14,15V17H8V13C8,11.89 8.9,11 10,11H12V9H8V7H12A2,2 0 0,1 14,9V11C14,12.11 13.1,13 12,13H10V15H14Z" />
|
||||||
</vector>
|
</vector>
|
||||||
34
app/src/main/res/drawable/reader_item_boundary.xml
Normal file
34
app/src/main/res/drawable/reader_item_boundary.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2020 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:bottom="1dp"
|
||||||
|
android:left="1dp"
|
||||||
|
android:right="1dp"
|
||||||
|
android:top="1dp">
|
||||||
|
<shape android:shape="rectangle" >
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#555555" />
|
||||||
|
|
||||||
|
<solid android:color="@color/transparent" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
8
app/src/main/res/drawable/sort_variant.xml
Normal file
8
app/src/main/res/drawable/sort_variant.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/sort_variant.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#fff" android:pathData="M3,13H15V11H3M3,6V8H21V6M3,18H9V16H3V18Z" />
|
||||||
|
</vector>
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
@@ -11,7 +29,29 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@id/lock_button_layout"/>
|
app:layout_constraintBottom_toTopOf="@id/lock_fingerprint_layout"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/lock_fingerprint_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/lock_content"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/lock_button_layout">
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/lock_fingerprint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/fingerprint"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
app:backgroundTint="@color/dark_gray"
|
||||||
|
app:fabSize="mini"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/lock_button_layout"
|
android:id="@+id/lock_button_layout"
|
||||||
@@ -19,7 +59,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginBottom="32dp"
|
android:layout_marginBottom="32dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/lock_content"
|
app:layout_constraintTop_toBottomOf="@id/lock_fingerprint_layout"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
@@ -41,16 +81,6 @@
|
|||||||
app:backgroundTint="@color/dark_gray"
|
app:backgroundTint="@color/dark_gray"
|
||||||
app:fabSize="mini"/>
|
app:fabSize="mini"/>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/lock_fingerprint"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:srcCompat="@drawable/fingerprint"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
app:backgroundTint="@color/dark_gray"
|
|
||||||
app:fabSize="mini"/>
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/lock_password"
|
android:id="@+id/lock_password"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<androidx.drawerlayout.widget.DrawerLayout
|
<androidx.drawerlayout.widget.DrawerLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/main_layout"
|
android:id="@+id/main_layout"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
@@ -56,12 +74,37 @@
|
|||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
|
<com.github.clans.fab.FloatingActionMenu
|
||||||
|
android:id="@+id/main_fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:menu_colorNormal="@color/colorAccent">
|
||||||
|
|
||||||
|
<com.github.clans.fab.FloatingActionButton
|
||||||
|
android:id="@+id/main_fab_jump"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:fab_label="@string/main_jump_title"
|
||||||
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
|
<com.github.clans.fab.FloatingActionButton
|
||||||
|
android:id="@+id/main_fab_id"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:fab_label="@string/main_open_gallery_by_id"
|
||||||
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
|
</com.github.clans.fab.FloatingActionMenu>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<com.arlib.floatingsearchview.FloatingSearchView
|
<com.arlib.floatingsearchview.FloatingSearchView
|
||||||
android:id="@+id/main_searchview"
|
android:id="@+id/main_searchview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
app:floatingSearch_backgroundColor="?attr/colorSurface"
|
||||||
app:floatingSearch_searchBarMarginLeft="8dp"
|
app:floatingSearch_searchBarMarginLeft="8dp"
|
||||||
app:floatingSearch_searchBarMarginRight="8dp"
|
app:floatingSearch_searchBarMarginRight="8dp"
|
||||||
app:floatingSearch_searchBarMarginTop="8dp"
|
app:floatingSearch_searchBarMarginTop="8dp"
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/reader_layout"
|
android:id="@+id/reader_layout"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
@@ -8,18 +26,11 @@
|
|||||||
android:background="@color/dark_gray"
|
android:background="@color/dark_gray"
|
||||||
tools:context=".ui.ReaderActivity">
|
tools:context=".ui.ReaderActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/reader_recyclerview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center_vertical">
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/reader_recyclerview"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -39,6 +50,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="4dp"
|
android:layout_height="4dp"
|
||||||
android:progressTint="@color/material_green_a700"
|
android:progressTint="@color/material_green_a700"
|
||||||
|
tools:ignore="UnusedAttribute"
|
||||||
android:visibility="gone"/>
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -55,7 +67,6 @@
|
|||||||
android:id="@+id/reader_fab_download"
|
android:id="@+id/reader_fab_download"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:srcCompat="@drawable/ic_downloading"
|
|
||||||
app:fab_label="@string/reader_fab_download"
|
app:fab_label="@string/reader_fab_download"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
@@ -63,7 +74,6 @@
|
|||||||
android:id="@+id/reader_fab_fullscreen"
|
android:id="@+id/reader_fab_fullscreen"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:srcCompat="@drawable/ic_fullscreen"
|
|
||||||
app:fab_label="@string/reader_fab_fullscreen"
|
app:fab_label="@string/reader_fab_fullscreen"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/default_query_dialog_title"
|
|
||||||
style="@style/TextAppearance.AppCompat.Large"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/default_query_dialog_title"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"/>
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
tools:ignore="Autofill"
|
tools:ignore="Autofill"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
@@ -22,7 +31,7 @@
|
|||||||
android:id="@+id/default_query_dialog_edittext"
|
android:id="@+id/default_query_dialog_edittext"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toBottomOf="@id/default_query_dialog_title"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
@@ -98,14 +107,4 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/default_query_dialog_ok"
|
|
||||||
style="?borderlessButtonStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/default_query_dialog_guro_layout"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
android:text="@android:string/ok"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
25
app/src/main/res/layout/dialog_dl_location.xml
Normal file
25
app/src/main/res/layout/dialog_dl_location.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2020 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"/>
|
||||||
132
app/src/main/res/layout/dialog_gallery.xml
Normal file
132
app/src/main/res/layout/dialog_gallery.xml
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/gallery_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/gallery_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/gallery_cover"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/gallery_title"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gallery_title"
|
||||||
|
style="@style/TextAppearance.AppCompat.Headline"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextAppearance.AppCompat.Medium"
|
||||||
|
android:id="@+id/gallery_artist"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/gallery_title"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/gallery_padding"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/gallery_artist"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/gallery_type"/>
|
||||||
|
|
||||||
|
<com.google.android.material.chip.Chip
|
||||||
|
android:id="@+id/gallery_type"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/gallery_contents"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/gallery_progressbar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/gallery_fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
app:layout_anchor="@id/gallery_toolbar"
|
||||||
|
app:layout_anchorGravity="bottom|end"/>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/main_dialog_download"
|
|
||||||
style="?borderlessButtonStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/main_dialog_delete"
|
|
||||||
style="?borderlessButtonStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/main_dialog_delete"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/main_dialog_download"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
@@ -1,10 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:context=".ui.PatternLockFragment">
|
tools:context=".ui.fragment.PatternLockFragment">
|
||||||
|
|
||||||
<com.andrognito.patternlockview.PatternLockView
|
<com.andrognito.patternlockview.PatternLockView
|
||||||
android:id="@+id/lock_pattern_view"
|
android:id="@+id/lock_pattern_view"
|
||||||
|
|||||||
40
app/src/main/res/layout/gallery_details.xml
Normal file
40
app/src/main/res/layout/gallery_details.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gallery_details"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
android:textColor="@color/colorAccent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/gallery_details_contents"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
51
app/src/main/res/layout/item_dl_location.xml
Normal file
51
app/src/main/res/layout/item_dl_location.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2020 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal" android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<RadioButton
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:clickable="false"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_type"
|
||||||
|
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_available"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
40
app/src/main/res/layout/item_gallery_details.xml
Normal file
40
app/src/main/res/layout/item_gallery_details.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
|
android:id="@+id/gallery_details_type"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="8dp"/>
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/gallery_details_tags"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:chipSpacingVertical="4dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
23
app/src/main/res/layout/item_gallery_thumbnails.xml
Normal file
23
app/src/main/res/layout/item_gallery_thumbnails.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:columnCount="3"/>
|
||||||
@@ -1,173 +1,225 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<androidx.cardview.widget.CardView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:paddingLeft="0dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:paddingRight="8dp"
|
|
||||||
app:cardCornerRadius="8dp"
|
app:cardCornerRadius="8dp"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:clipChildren="true">
|
||||||
android:focusable="true"
|
|
||||||
android:clickable="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
<com.daimajia.swipe.SwipeLayout
|
||||||
|
android:id="@+id/galleryblock_swipe_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
app:drag_edge="right"
|
||||||
|
app:show_mode="pull_out">
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
|
||||||
android:id="@+id/galleryblock_progressbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/galleryblock_progress_complete"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:visibility="invisible"
|
|
||||||
android:scaleType="fitXY"
|
|
||||||
android:contentDescription="@string/reader_imageview_description"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/galleryblock_thumbnail"
|
|
||||||
android:layout_width="150dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@string/galleryblock_thumbnail_description"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextAppearance.AppCompat.Headline"
|
|
||||||
android:id="@+id/galleryblock_title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
style="@style/TextAppearance.AppCompat.Medium"
|
|
||||||
android:id="@+id/galleryblock_artist"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_title"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/galleryblock_series"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/galleryblock_type"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/galleryblock_language"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/galleryblock_padding"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/galleryblock_tag_group"/>
|
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
|
||||||
android:id="@+id/galleryblock_tag_group"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
|
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
android:background="@android:color/darker_gray"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/galleryblock_secondary"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:paddingLeft="8dp"
|
android:layout_height="match_parent">
|
||||||
android:paddingRight="8dp"
|
|
||||||
android:paddingBottom="8dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_id"
|
android:id="@+id/galleryblock_download"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="match_parent"
|
||||||
|
android:minWidth="70dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@android:color/holo_blue_dark"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:text="@string/main_download"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true"
|
||||||
|
android:clickable="true"/>
|
||||||
|
|
||||||
<View
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:id="@+id/galleryblock_delete"
|
||||||
android:layout_height="1dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_weight="1"/>
|
android:layout_height="match_parent"
|
||||||
|
android:minWidth="70dp"
|
||||||
<ImageView
|
android:padding="8dp"
|
||||||
android:id="@+id/galleryblock_favorite"
|
android:gravity="center"
|
||||||
android:contentDescription="@string/app_name"
|
android:background="@android:color/holo_red_dark"
|
||||||
android:layout_width="32dp"
|
android:textColor="@android:color/white"
|
||||||
android:layout_height="32dp"
|
android:text="@string/main_delete"
|
||||||
app:srcCompat="@drawable/ic_star_empty"
|
android:foreground="?attr/selectableItemBackground"
|
||||||
app:backgroundTint="@color/material_orange_500"/>
|
android:focusable="true"
|
||||||
|
android:clickable="true"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<LinearLayout
|
||||||
|
android:id="@+id/galleryblock_primary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:focusable="true"
|
||||||
|
android:clickable="true">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:id="@+id/galleryblock_progressbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/galleryblock_progress_complete"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:visibility="invisible"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:contentDescription="@string/reader_imageview_description"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/galleryblock_thumbnail"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/galleryblock_thumbnail_description"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextAppearance.AppCompat.Headline"
|
||||||
|
android:id="@+id/galleryblock_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/TextAppearance.AppCompat.Medium"
|
||||||
|
android:id="@+id/galleryblock_artist"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_title"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/galleryblock_series"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/galleryblock_type"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/galleryblock_language"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/galleryblock_padding"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/galleryblock_tag_group"/>
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/galleryblock_tag_group"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:chipSpacing="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:background="@android:color/darker_gray"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="8dp"
|
||||||
|
android:paddingRight="8dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/galleryblock_id"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/galleryblock_favorite"
|
||||||
|
android:contentDescription="@string/app_name"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
app:srcCompat="@drawable/ic_star_empty"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.daimajia.swipe.SwipeLayout>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
49
app/src/main/res/layout/item_mirrors.xml
Normal file
49
app/src/main/res/layout/item_mirrors.xml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2020 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:paddingStart="32dp"
|
||||||
|
android:paddingLeft="32dp"
|
||||||
|
android:paddingEnd="32dp"
|
||||||
|
android:paddingRight="32dp"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mirror_name"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/mirror_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/menu"
|
||||||
|
app:tint="?attr/colorControlNormal"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -18,7 +36,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:srcCompat="@drawable/ic_navigate_next_black_24dp"
|
app:srcCompat="@drawable/ic_navigate_next_black_24dp"
|
||||||
android:tint="@color/colorAccent"
|
app:tint="@color/colorAccent"
|
||||||
android:rotation="180"/>
|
android:rotation="180"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -18,7 +36,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
app:srcCompat="@drawable/ic_navigate_next_black_24dp"
|
app:srcCompat="@drawable/ic_navigate_next_black_24dp"
|
||||||
android:tint="@color/colorAccent"
|
app:tint="@color/colorAccent"
|
||||||
android:rotation="180"/>
|
android:rotation="180"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
|
||||||
android:layout_width="match_parent" android:layout_height="wrap_content">
|
android:layout_width="match_parent" android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,71 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
<!--
|
||||||
android:contentDescription="@string/reader_imageview_description"
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingBottom="8dp"
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:adjustViewBounds="true"/>
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:background="@drawable/reader_item_boundary">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/reader_item_progressbar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="?android:progressBarStyleHorizontal"
|
||||||
|
android:indeterminate="false"
|
||||||
|
android:progress="0"
|
||||||
|
android:max="100"
|
||||||
|
android:visibility="visible"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/reader_index"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/TextAppearance.AppCompat.Caption"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.github.chrisbanes.photoview.PhotoView
|
||||||
|
android:id="@+id/image"
|
||||||
|
android:contentDescription="@string/reader_imageview_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingBottom="8dp"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,7 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/nav_header_height"
|
android:layout_height="@dimen/nav_header_height"
|
||||||
android:background="@drawable/side_nav_bar"
|
android:background="@drawable/side_nav_bar"
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.chip.Chip
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
app:chipIconSize="16dp"
|
|
||||||
app:chipStartPadding="8dp"
|
|
||||||
app:chipCornerRadius="100dp"/>
|
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<group android:checkableBehavior="single">
|
<group android:checkableBehavior="single">
|
||||||
@@ -35,7 +53,7 @@
|
|||||||
android:title="@string/main_drawer_group_contact_email"
|
android:title="@string/main_drawer_group_contact_email"
|
||||||
android:icon="@drawable/ic_email"/>
|
android:icon="@drawable/ic_email"/>
|
||||||
<item android:id="@+id/main_drawer_kakaotalk"
|
<item android:id="@+id/main_drawer_kakaotalk"
|
||||||
android:title="@string/main_drawer_grouop_contact_kakaotalk"
|
android:title="@string/main_drawer_grouop_contact_discord"
|
||||||
android:icon="@drawable/ic_message"/>
|
android:icon="@drawable/ic_message"/>
|
||||||
</menu>
|
</menu>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
35
app/src/main/res/menu/gallery.xml
Normal file
35
app/src/main/res/menu/gallery.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/gallery_favorite"
|
||||||
|
android:icon="@drawable/ic_star_empty"
|
||||||
|
android:title=""
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/gallery_download"
|
||||||
|
android:icon="@drawable/ic_download"
|
||||||
|
android:title=""
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
|
</menu>
|
||||||
@@ -1,16 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item android:id="@+id/main_menu_jump"
|
<item
|
||||||
android:icon="@drawable/ic_jump"
|
android:id="@+id/main_menu_sort"
|
||||||
android:title="@string/main_jump_title"
|
android:title="@string/main_menu_sort">
|
||||||
app:showAsAction="ifRoom"/>
|
<menu>
|
||||||
|
<group android:checkableBehavior="single">
|
||||||
<item android:id="@+id/main_menu_id"
|
<item android:id="@+id/main_menu_sort_newest"
|
||||||
android:icon="@drawable/ic_numeric"
|
android:title="@string/main_menu_sort_newest"
|
||||||
android:title="@string/main_open_gallery_by_id"
|
android:checked="true"/>
|
||||||
app:showAsAction="ifRoom"/>
|
<item android:id="@+id/main_menu_sort_popular"
|
||||||
|
android:title="@string/main_menu_sort_popular"/>
|
||||||
|
</group>
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/main_menu_settings"
|
android:id="@+id/main_menu_settings"
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2019 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item android:id="@+id/reader_menu_favorite"
|
<item android:id="@+id/reader_menu_favorite"
|
||||||
android:title=""
|
android:title=""
|
||||||
android:iconTint="@color/material_orange_500"
|
|
||||||
android:icon="@drawable/avd_star"
|
android:icon="@drawable/avd_star"
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
<item android:id="@+id/reader_menu_use_hiyobi"
|
<item android:id="@+id/reader_type"
|
||||||
android:title=""
|
android:title=""
|
||||||
android:icon="@drawable/ic_hiyobi"
|
app:showAsAction="ifRoom"/>
|
||||||
app:showAsAction="ifRoom"
|
|
||||||
android:visible="false"/>
|
|
||||||
|
|
||||||
<item android:id="@+id/reader_menu_page_indicator"
|
<item android:id="@+id/reader_menu_page_indicator"
|
||||||
android:title="@string/page_indicator_placeholder"
|
android:title="@string/page_indicator_placeholder"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<string name="search_hint_with_page">ギャラリー検索</string>
|
<string name="search_hint_with_page">ギャラリー検索</string>
|
||||||
<string name="settings_clear_cache">キャッシュクリア</string>
|
<string name="settings_clear_cache">キャッシュクリア</string>
|
||||||
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
|
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
|
||||||
<string name="settings_clear_summary">サイズ: %1$d%2$s</string>
|
<string name="settings_clear_summary">サイズ: %s</string>
|
||||||
<string name="settings_default_query">デフォルトキーワード</string>
|
<string name="settings_default_query">デフォルトキーワード</string>
|
||||||
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
|
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
|
||||||
<string name="settings_search_title">検索設定</string>
|
<string name="settings_search_title">検索設定</string>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<string name="update_title">新しいアップデートがあります</string>
|
<string name="update_title">新しいアップデートがあります</string>
|
||||||
<string name="warning">注意</string>
|
<string name="warning">注意</string>
|
||||||
<string name="settings_miscellaneous_title">その他</string>
|
<string name="settings_miscellaneous_title">その他</string>
|
||||||
<string name="settings_use_hiyobi_title">hiyobi.meからロード</string>
|
<string name="settings_mirror_title">ミラーサーバー</string>
|
||||||
<string name="settings_clear_history">履歴を削除</string>
|
<string name="settings_clear_history">履歴を削除</string>
|
||||||
<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>
|
||||||
@@ -61,16 +61,16 @@
|
|||||||
<string name="main_export_error">エクスポートエラーが発生しました</string>
|
<string name="main_export_error">エクスポートエラーが発生しました</string>
|
||||||
<string name="settings_clear_downloads">ダウンロード削除</string>
|
<string name="settings_clear_downloads">ダウンロード削除</string>
|
||||||
<string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか?</string>
|
<string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか?</string>
|
||||||
<string name="settings_use_hiyobi_summary">ロード速度を向上させるため可能であればhiyobi.meからイメージロード</string>
|
<string name="settings_mirror_summary">ミラーサーバからイメージをロード</string>
|
||||||
<string name="main_drawer_favorite">お気に入り</string>
|
<string name="main_drawer_favorite">お気に入り</string>
|
||||||
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
|
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
|
||||||
<string name="main_open_gallery_by_id_error">エラーが発生しました</string>
|
<string name="reader_failed_to_find_gallery">エラーが発生しました</string>
|
||||||
<string name="settings_storage">ストレージ</string>
|
<string name="settings_storage">ストレージ</string>
|
||||||
<string name="main_drawer_grouop_contact_kakaotalk">カカオトーク</string>
|
<string name="main_drawer_grouop_contact_discord">ディスコード</string>
|
||||||
<string name="settings_app_lock">アプリロック</string>
|
<string name="settings_app_lock">アプリロック</string>
|
||||||
<string name="settings_app_lock_type">アップロックの種類</string>
|
<string name="settings_app_lock_type">アップロックの種類</string>
|
||||||
<string name="settings_app_version_title">バージョン</string>
|
<string name="settings_app_version_title">バージョン(アップデート確認)</string>
|
||||||
<string name="settings_lock_biomatrics">生体認識</string>
|
<string name="settings_lock_biometrics">生体認識</string>
|
||||||
<string name="settings_lock_confirm">ロック確認のためもう一回入力してください。</string>
|
<string name="settings_lock_confirm">ロック確認のためもう一回入力してください。</string>
|
||||||
<string name="settings_lock_enabled">有効</string>
|
<string name="settings_lock_enabled">有効</string>
|
||||||
<string name="settings_lock_fingerprint">指紋</string>
|
<string name="settings_lock_fingerprint">指紋</string>
|
||||||
@@ -79,4 +79,47 @@
|
|||||||
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
|
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
|
||||||
<string name="settings_lock_none">なし</string>
|
<string name="settings_lock_none">なし</string>
|
||||||
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
|
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
|
||||||
|
<string name="reader_loading">ロード中</string>
|
||||||
|
<string name="main_menu_sort">ソート</string>
|
||||||
|
<string name="main_menu_sort_newest">投稿日時順</string>
|
||||||
|
<string name="main_menu_sort_popular">人気順</string>
|
||||||
|
<string name="update_failed">アップデートに失敗しました</string>
|
||||||
|
<string name="update_failed_message">アップデート中エラーが発生しました</string>
|
||||||
|
<string name="ignore_update">無視</string>
|
||||||
|
<string name="lock_corrupted">ロックファイルが破損されています。Pupilを再再インストールしてください。</string>
|
||||||
|
<string name="update_no_permission">権限がないため自動アップデートを行えません。ホームページで直接ダウンロードしてください。</string>
|
||||||
|
<string name="settings_dark_mode_title">ダークモード</string>
|
||||||
|
<string name="settings_dark_mode_summary">夜にシコりたい方々へ</string>
|
||||||
|
<string name="gallery_details">ギャラリー情報</string>
|
||||||
|
<string name="gallery_artists">アーティスト</string>
|
||||||
|
<string name="gallery_characters">キャラクター</string>
|
||||||
|
<string name="gallery_groups">グループ</string>
|
||||||
|
<string name="gallery_language">言語</string>
|
||||||
|
<string name="gallery_series">シリーズ</string>
|
||||||
|
<string name="gallery_tags">タグ</string>
|
||||||
|
<string name="gallery_thumbnails">サムネイル</string>
|
||||||
|
<string name="gallery_related">おすすめ</string>
|
||||||
|
<string name="settings_nomedia_summary">イメージをギャラリーから見えなくする</string>
|
||||||
|
<string name="settings_nomedia_title">イメージを隠す</string>
|
||||||
|
<string name="reader_help">ヘルプ</string>
|
||||||
|
<string name="main_delete">削除</string>
|
||||||
|
<string name="main_download">ダウンロード</string>
|
||||||
|
<string name="settings_backup_title">お気に入りバックアップ</string>
|
||||||
|
<string name="settings_restore_title">お気に入り復元</string>
|
||||||
|
<string name="settings_backup_snackbar">バックアップファイルを作成しました</string>
|
||||||
|
<string name="settings_backup_checkout">確認</string>
|
||||||
|
<string name="settings_restore_failed">復元に失敗しました</string>
|
||||||
|
<string name="settings_restore_successful">%1$d項目を復元しました</string>
|
||||||
|
<string name="settings_dl_location">ダウンロード場所</string>
|
||||||
|
<string name="settings_dl_location_internal">内部ストレージ</string>
|
||||||
|
<string name="settings_dl_location_removable">外部SDカード</string>
|
||||||
|
<string name="settings_dl_location_available">%s 使用可能</string>
|
||||||
|
<string name="update_download_completed">ダウンロードが完了しました</string>
|
||||||
|
<string name="update_download_completed_description">ここをクリックしてアップデートを行えます</string>
|
||||||
|
<string name="settings_beta">ベータチャンネルでアップデートを受信</string>
|
||||||
|
<string name="settings_app_version_description">v%s</string>
|
||||||
|
<string name="settings_low_quality">低解像度イメージ</string>
|
||||||
|
<string name="settings_low_quality_summary">ロード速度とデータ使用料を改善するため低解像度イメージをロード</string>
|
||||||
|
<string name="settings_dl_location_custom">手動で設定</string>
|
||||||
|
<string name="settings_dl_location_not_writable">このフォルダにアクセスできません。他のフォルダを選択してください。</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<string name="settings_default_query">기본 검색어</string>
|
<string name="settings_default_query">기본 검색어</string>
|
||||||
<string name="settings_clear_cache">캐시 정리하기</string>
|
<string name="settings_clear_cache">캐시 정리하기</string>
|
||||||
<string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string>
|
<string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string>
|
||||||
<string name="settings_clear_summary">사용량: %1$d%2$s</string>
|
<string name="settings_clear_summary">사용량: %s</string>
|
||||||
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
|
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
|
||||||
<string name="settings_search_title">검색 설정</string>
|
<string name="settings_search_title">검색 설정</string>
|
||||||
<string name="settings_title">설정</string>
|
<string name="settings_title">설정</string>
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
<string name="main_no_result">결과 없음</string>
|
<string name="main_no_result">결과 없음</string>
|
||||||
<string name="main_search">검색</string>
|
<string name="main_search">검색</string>
|
||||||
<string name="settings_miscellaneous_title">기타</string>
|
<string name="settings_miscellaneous_title">기타</string>
|
||||||
<string name="settings_use_hiyobi_title">hiyobi.me 사용</string>
|
|
||||||
<string name="settings_clear_history">기록 삭제</string>
|
<string name="settings_clear_history">기록 삭제</string>
|
||||||
<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>
|
||||||
@@ -54,23 +53,22 @@
|
|||||||
<string name="unable_to_connect">hitomi.la에 연결할 수 없습니다</string>
|
<string name="unable_to_connect">hitomi.la에 연결할 수 없습니다</string>
|
||||||
<string name="main_move">%1$d 페이지로 이동</string>
|
<string name="main_move">%1$d 페이지로 이동</string>
|
||||||
<string name="https_block_alert_title">접속 불가 현상 안내</string>
|
<string name="https_block_alert_title">접속 불가 현상 안내</string>
|
||||||
<string name="https_block_alert">최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다\n이 경우 플레이스토어에서 SNIper앱을 이용하시면 정상이용이 가능합니다.</string>
|
<string name="https_block_alert">최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다 이 경우 플레이스토어에서 Intra앱을 이용하시면 정상이용이 가능합니다.</string>
|
||||||
<string name="main_dialog_export">갤러리 내보내기</string>
|
<string name="main_dialog_export">갤러리 내보내기</string>
|
||||||
<string name="main_export_complete">내보내기 완료</string>
|
<string name="main_export_complete">내보내기 완료</string>
|
||||||
<string name="main_export_open_folder">폴더 열기</string>
|
<string name="main_export_open_folder">폴더 열기</string>
|
||||||
<string name="main_export_error">내보내기 오류가 발생했습니다</string>
|
<string name="main_export_error">내보내기 오류가 발생했습니다</string>
|
||||||
<string name="settings_clear_downloads">다운로드 삭제</string>
|
<string name="settings_clear_downloads">다운로드 삭제</string>
|
||||||
<string name="settings_clear_downloads_alert_message">다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까?</string>
|
<string name="settings_clear_downloads_alert_message">다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까?</string>
|
||||||
<string name="settings_use_hiyobi_summary">속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드</string>
|
|
||||||
<string name="main_drawer_favorite">즐겨찾기</string>
|
<string name="main_drawer_favorite">즐겨찾기</string>
|
||||||
<string name="main_open_gallery_by_id">갤러리 번호로 열기</string>
|
<string name="main_open_gallery_by_id">갤러리 번호로 열기</string>
|
||||||
<string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string>
|
<string name="reader_failed_to_find_gallery">갤러리를 찾지 못했습니다</string>
|
||||||
<string name="settings_storage">저장 공간</string>
|
<string name="settings_storage">저장 공간</string>
|
||||||
<string name="main_drawer_grouop_contact_kakaotalk">카카오톡 오픈채팅방</string>
|
<string name="main_drawer_grouop_contact_discord">디스코드</string>
|
||||||
<string name="settings_app_lock">앱 잠금</string>
|
<string name="settings_app_lock">앱 잠금</string>
|
||||||
<string name="settings_app_lock_type">앱 잠금 종류</string>
|
<string name="settings_app_lock_type">앱 잠금 종류</string>
|
||||||
<string name="settings_app_version_title">앱 버전</string>
|
<string name="settings_app_version_title">앱 버전(업데이트 확인)</string>
|
||||||
<string name="settings_lock_biomatrics">생체 인식</string>
|
<string name="settings_lock_biometrics">생체 인식</string>
|
||||||
<string name="settings_lock_confirm">잠금 확인을 위해 한번 더 입력해주세요</string>
|
<string name="settings_lock_confirm">잠금 확인을 위해 한번 더 입력해주세요</string>
|
||||||
<string name="settings_lock_enabled">사용 중</string>
|
<string name="settings_lock_enabled">사용 중</string>
|
||||||
<string name="settings_lock_fingerprint">지문</string>
|
<string name="settings_lock_fingerprint">지문</string>
|
||||||
@@ -79,4 +77,49 @@
|
|||||||
<string name="settings_lock_wrong_confirm">잠금이 일치하지 않습니다. 다시 시도하세요.</string>
|
<string name="settings_lock_wrong_confirm">잠금이 일치하지 않습니다. 다시 시도하세요.</string>
|
||||||
<string name="settings_lock_none">없음</string>
|
<string name="settings_lock_none">없음</string>
|
||||||
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
|
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
|
||||||
|
<string name="reader_loading">로딩중</string>
|
||||||
|
<string name="main_menu_sort">정렬</string>
|
||||||
|
<string name="main_menu_sort_popular">인기순</string>
|
||||||
|
<string name="main_menu_sort_newest">시간순</string>
|
||||||
|
<string name="update_failed">"업데이트 에러</string>
|
||||||
|
<string name="update_failed_message">업데이트 중 에러가 발생했습니다</string>
|
||||||
|
<string name="ignore_update">무시</string>
|
||||||
|
<string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string>
|
||||||
|
<string name="update_no_permission">권한이 부여되어 있지 않아 자동 업데이트를 진행할 수 없습니다. 홈페이지에서 직접 다운로드 받으시기 바랍니다.</string>
|
||||||
|
<string name="settings_dark_mode_title">다크 모드</string>
|
||||||
|
<string name="settings_dark_mode_summary">딥 다크한 모오드</string>
|
||||||
|
<string name="gallery_details">갤러리 정보</string>
|
||||||
|
<string name="gallery_artists">작가</string>
|
||||||
|
<string name="gallery_characters">캐릭터</string>
|
||||||
|
<string name="gallery_groups">그룹</string>
|
||||||
|
<string name="gallery_language">언어</string>
|
||||||
|
<string name="gallery_series">시리즈</string>
|
||||||
|
<string name="gallery_tags">태그</string>
|
||||||
|
<string name="gallery_related">관련 갤러리</string>
|
||||||
|
<string name="gallery_thumbnails">미리보기</string>
|
||||||
|
<string name="settings_nomedia_summary">갤러리에서 이미지 검색이 되지 않도록 합니다</string>
|
||||||
|
<string name="settings_nomedia_title">이미지 숨기기</string>
|
||||||
|
<string name="reader_help">도움말</string>
|
||||||
|
<string name="main_delete">삭제</string>
|
||||||
|
<string name="main_download">다운로드</string>
|
||||||
|
<string name="settings_backup_title">즐겨찾기 백업</string>
|
||||||
|
<string name="settings_restore_title">즐겨찾기 복원</string>
|
||||||
|
<string name="settings_backup_snackbar">백업 파일을 생성하였습니다</string>
|
||||||
|
<string name="settings_backup_checkout">확인</string>
|
||||||
|
<string name="settings_restore_failed">복원에 실패했습니다</string>
|
||||||
|
<string name="settings_restore_successful">%1$d개 항목을 복원했습니다</string>
|
||||||
|
<string name="settings_dl_location">다운로드 위치</string>
|
||||||
|
<string name="settings_dl_location_internal">내부 저장공간</string>
|
||||||
|
<string name="settings_dl_location_removable">외부 SD카드</string>
|
||||||
|
<string name="settings_dl_location_available">%s 사용 가능</string>
|
||||||
|
<string name="update_download_completed">다운로드가 완료되었습니다</string>
|
||||||
|
<string name="update_download_completed_description">여기를 클릭해서 업데이트를 진행할 수 있습니다</string>
|
||||||
|
<string name="settings_beta">베타 채널에서 업데이트</string>
|
||||||
|
<string name="settings_app_version_description">v%s</string>
|
||||||
|
<string name="settings_low_quality">저해상도 이미지</string>
|
||||||
|
<string name="settings_low_quality_summary">로드 속도와 데이터 사용량을 줄이기 위해 저해상도 이미지를 로드</string>
|
||||||
|
<string name="settings_mirror_summary">미러 서버에서 이미지 로드</string>
|
||||||
|
<string name="settings_mirror_title">미러 설정</string>
|
||||||
|
<string name="settings_dl_location_custom">직접 설정</string>
|
||||||
|
<string name="settings_dl_location_not_writable">이 폴더에 접근할 수 없습니다. 다른 폴더를 선택해주세요.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -57,4 +57,9 @@
|
|||||||
<item>japanese|日本語</item>
|
<item>japanese|日本語</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="mirrors">
|
||||||
|
<item>HITOMI|hitomi.la</item>
|
||||||
|
<item>HIYOBI|hiyobi.me</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -8,4 +8,6 @@
|
|||||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
<dimen name="nav_header_vertical_spacing">8dp</dimen>
|
<dimen name="nav_header_vertical_spacing">8dp</dimen>
|
||||||
<dimen name="nav_header_height">176dp</dimen>
|
<dimen name="nav_header_height">176dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="thumbnail_margin">8dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user