Compare commits
129 Commits
5.0-beta3
...
5.1.6-hotf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f07f624fcf | ||
|
|
48ff2f328f | ||
|
|
9ae2423a40 | ||
|
|
2bc3c78c75 | ||
|
|
18e9fe75fb | ||
|
|
880a741a44 | ||
|
|
2c6ddcc64b | ||
|
|
8f2e757b77 | ||
|
|
ff177955b3 | ||
|
|
8bb8066a98 | ||
|
|
2747ddbf65 | ||
|
|
b939e9424d | ||
|
|
fb9dea5d1e | ||
|
|
da4d5d711b | ||
|
|
331cbec5f1 | ||
|
|
7f02284285 | ||
|
|
ac2c3a6d97 | ||
|
|
c3bc80fec6 | ||
|
|
09779a0710 | ||
|
|
e82c6ef866 | ||
|
|
861ae9be64 | ||
|
|
96108bc1ec | ||
|
|
016f217db0 | ||
|
|
0688294f18 | ||
|
|
9ad008255d | ||
|
|
4c5a862dd6 | ||
|
|
b165a2308f | ||
|
|
8757b08cd2 | ||
|
|
3800543fba | ||
|
|
02ef60c818 | ||
|
|
88f3b30266 | ||
|
|
9203dc0112 | ||
|
|
4c683bec68 | ||
|
|
0cfd1eb453 | ||
|
|
19744dab37 | ||
|
|
12d58e5aa7 | ||
|
|
e46dd37a26 | ||
|
|
49c3ebc36b | ||
|
|
11e9bc2235 | ||
|
|
3029b3bf0e | ||
|
|
9a6c6f67ce | ||
|
|
a6ed0baef2 | ||
|
|
d3b43d80da | ||
|
|
46d4316d49 | ||
|
|
ade2864351 | ||
|
|
365fc56e9d | ||
|
|
54a5cd21ea | ||
|
|
38c0399b09 | ||
|
|
2b67858453 | ||
|
|
87fdbdbb6e | ||
|
|
bab77a4116 | ||
|
|
d20756ab96 | ||
|
|
dc75a753c3 | ||
|
|
4712d47903 | ||
|
|
c5561801e1 | ||
|
|
5c259fa07a | ||
|
|
60e8b18702 | ||
|
|
a8317824a9 | ||
|
|
0c3c78cc72 | ||
|
|
cfd4a8faac | ||
|
|
7f3fb0db0d | ||
|
|
385d3f0d1b | ||
|
|
8fa6bd12a2 | ||
|
|
57c2004e46 | ||
|
|
c6b069bbfb | ||
|
|
c18bffd08f | ||
|
|
47ec181439 | ||
|
|
90ad40b1b7 | ||
|
|
4d3f20cf98 | ||
|
|
86df9d52bc | ||
|
|
1bd025e070 | ||
|
|
86ee239c71 | ||
|
|
27d0c01e1f | ||
|
|
7a9507be01 | ||
|
|
1490035893 | ||
|
|
a6afcb0ed0 | ||
|
|
ea7e8584cb | ||
|
|
608c6e6a1d | ||
|
|
bb2c91145f | ||
|
|
db074df0f7 | ||
|
|
f7c45df9a6 | ||
|
|
44e3d16cd6 | ||
|
|
a973cdfe0b | ||
|
|
fca42c79a8 | ||
|
|
f236775599 | ||
|
|
360decd37c | ||
|
|
998433479b | ||
|
|
c7e75aacf0 | ||
|
|
690338273a | ||
|
|
4207ea494d | ||
|
|
265473a15a | ||
|
|
b907d36770 | ||
|
|
fee280341a | ||
|
|
0f1ef70752 | ||
|
|
0f8c68b22e | ||
|
|
701017d2ca | ||
|
|
be6903ca12 | ||
|
|
1521bc1223 | ||
|
|
7ed66b827f | ||
|
|
df3a478ef3 | ||
|
|
974ddf69d5 | ||
|
|
56a91268de | ||
|
|
3dda2f9a1c | ||
|
|
ed20456f9f | ||
|
|
281d4a0023 | ||
|
|
2170403662 | ||
|
|
b1c1e96135 | ||
|
|
a8de1429c1 | ||
|
|
3ba6cb81ae | ||
|
|
acc85da80f | ||
|
|
b53de8624d | ||
|
|
6e2eeb29cc | ||
|
|
62eb28ac01 | ||
|
|
fd298529bf | ||
|
|
297ce506b1 | ||
|
|
18c6954be3 | ||
|
|
cea3fb1e65 | ||
|
|
7f274fd238 | ||
|
|
439a8e93ec | ||
|
|
83801feee9 | ||
|
|
8a6860c96e | ||
|
|
5c959f2987 | ||
|
|
4e4397287a | ||
|
|
fe02abc9e8 | ||
|
|
59347ab317 | ||
|
|
f408a91176 | ||
|
|
6f6956ce27 | ||
|
|
4ecad8eccc | ||
|
|
486fbe46a0 |
6
.idea/compiler.xml
generated
Normal file
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="1.8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -14,6 +14,7 @@
|
|||||||
</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>
|
||||||
|
|||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="AndroidLintNewerVersionAvailable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
15
.idea/jarRepositories.xml
generated
15
.idea/jarRepositories.xml
generated
@@ -56,5 +56,20 @@
|
|||||||
<option name="name" value="MavenLocal" />
|
<option name="name" value="MavenLocal" />
|
||||||
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
|
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
|
||||||
</remote-repository>
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="MavenLocal" />
|
||||||
|
<option name="name" value="MavenLocal" />
|
||||||
|
<option name="url" value="file:/$USER_HOME$/.m2/repository" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven3" />
|
||||||
|
<option name="name" value="maven3" />
|
||||||
|
<option name="url" value="https://dl.bintray.com/tom5079/maven" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven3" />
|
||||||
|
<option name="name" value="maven3" />
|
||||||
|
<option name="url" value="http://dl.bintray.com/piasy/maven" />
|
||||||
|
</remote-repository>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.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="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" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -1,18 +1,12 @@
|
|||||||
# Pupil
|
|
||||||
|
|
||||||

|

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

|
||||||
|
[](https://github.com/tom5079/Pupil/releases/download/5.1.6-hotfix7/Pupil-v5.1.6-hotfix7.apk)
|
||||||
[](https://discord.gg/Stj4b5v)
|
[](https://discord.gg/Stj4b5v)
|
||||||
|
|
||||||
# Screenshot
|
# Features
|
||||||

|

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

|
|
||||||
*Reader Screen*
|
|
||||||
|
|
||||||
Images are censored to be SFW
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
@@ -26,4 +20,7 @@ or Build app yourself
|
|||||||
|
|
||||||
# Contribution
|
# Contribution
|
||||||
|
|
||||||
Any kind of contribution is appriciated. Feel free to leave PR!
|
Any kind of contribution is appriciated. Feel free to leave PR!
|
||||||
|
|
||||||
|
## Tag Translation
|
||||||
|
Head over to [tags branch](https://github.com/tom5079/Pupil/tree/tags)
|
||||||
|
|||||||
147
app/build.gradle
147
app/build.gradle
@@ -1,26 +1,44 @@
|
|||||||
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-kapt"
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: "kotlin-android-extensions"
|
||||||
apply plugin: 'kotlinx-serialization'
|
apply plugin: "kotlinx-serialization"
|
||||||
|
apply plugin: "com.google.android.gms.oss-licenses-plugin"
|
||||||
|
|
||||||
if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
|
if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
|
||||||
logger.lifecycle("Firebase Enabled")
|
logger.lifecycle("Firebase Enabled")
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: "com.google.gms.google-services"
|
||||||
apply plugin: 'com.google.firebase.crashlytics'
|
apply plugin: "com.google.firebase.crashlytics"
|
||||||
apply plugin: 'com.google.firebase.firebase-perf'
|
apply plugin: "com.google.firebase.firebase-perf"
|
||||||
} else {
|
} else {
|
||||||
logger.lifecycle("Firebase Disabled")
|
logger.lifecycle("Firebase Disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
okhttp_version = "3.12.12"
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
all {
|
||||||
|
resolutionStrategy {
|
||||||
|
eachDependency { DependencyResolveDetails details ->
|
||||||
|
if (details.requested.group == "com.squareup.okhttp3" && details.requested.name == "okhttp") {
|
||||||
|
// OkHttp drops support before 5.0 since 3.13.0
|
||||||
|
details.useVersion okhttp_version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "xyz.quaver.pupil"
|
applicationId "xyz.quaver.pupil"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 57
|
versionCode 63
|
||||||
versionName "5.0-beta3"
|
versionName "5.1.6-hotfix7"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
@@ -33,8 +51,7 @@ android {
|
|||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
versionNameSuffix "-DEBUG"
|
versionNameSuffix "-DEBUG"
|
||||||
|
|
||||||
buildConfigField('Boolean', 'CENSOR', 'false')
|
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
|
|
||||||
ext.enableCrashlytics = false
|
ext.enableCrashlytics = false
|
||||||
ext.alwaysUpdateBuildId = false
|
ext.alwaysUpdateBuildId = false
|
||||||
@@ -43,68 +60,78 @@ android {
|
|||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
|
|
||||||
buildConfigField('Boolean', 'CENSOR', 'false')
|
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental"
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
buildToolsVersion = '29.0.3'
|
buildToolsVersion = "29.0.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0-M1"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC-HOTFIX1"
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
implementation "androidx.activity:activity-ktx:1.2.0-beta01"
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||||
|
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||||
|
implementation "androidx.constraintlayout:constraintlayout:2.0.2"
|
||||||
|
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||||
implementation "androidx.biometric:biometric:1.0.1"
|
implementation "androidx.biometric:biometric:1.0.1"
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.2.5'
|
implementation "androidx.work:work-runtime-ktx:2.4.0"
|
||||||
|
|
||||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||||
implementation 'com.google.android.material:material:1.3.0-alpha02'
|
|
||||||
implementation 'com.google.firebase:firebase-core:17.5.0'
|
implementation "com.google.android.material:material:1.3.0-alpha03"
|
||||||
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
|
||||||
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
|
implementation "com.google.firebase:firebase-core:17.5.1"
|
||||||
implementation 'com.google.firebase:firebase-perf:19.0.8'
|
implementation "com.google.firebase:firebase-analytics:17.6.0"
|
||||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
implementation "com.google.firebase:firebase-crashlytics:17.2.2"
|
||||||
implementation 'com.github.clans:fab:1.6.4'
|
implementation "com.google.firebase:firebase-perf:19.0.9"
|
||||||
|
|
||||||
|
implementation "com.google.android.gms:play-services-oss-licenses:17.0.0"
|
||||||
|
implementation "com.google.android.gms:play-services-mlkit-face-detection:16.1.1"
|
||||||
|
|
||||||
|
implementation "com.github.clans:fab:1.6.4"
|
||||||
|
|
||||||
|
//implementation "com.quiph.ui:recyclerviewfastscroller:0.2.1"
|
||||||
|
|
||||||
|
implementation 'com.github.piasy:BigImageViewer:1.6.7'
|
||||||
|
implementation 'com.github.piasy:FrescoImageLoader:1.6.7'
|
||||||
|
implementation 'com.github.piasy:FrescoImageViewFactory:1.6.7'
|
||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
|
||||||
implementation ("com.github.bumptech.glide:okhttp3-integration:4.11.0") {
|
implementation "com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2"
|
||||||
transitive = false
|
|
||||||
}
|
implementation "net.rdrei.android.dirchooser:library:3.2@aar"
|
||||||
implementation 'com.github.bumptech.glide:annotations:4.11.0'
|
implementation "com.gu:option:1.3"
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
|
||||||
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
implementation "com.andrognito.patternlockview:patternlockview:1.0.0"
|
||||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
|
//implementation "com.andrognito.pinlockview:pinlockview:2.1.0"
|
||||||
transitive = false
|
|
||||||
}
|
|
||||||
implementation 'com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2'
|
|
||||||
implementation 'net.rdrei.android.dirchooser:library:3.2@aar'
|
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
|
||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
|
||||||
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
|
||||||
implementation "ru.noties.markwon:core:3.1.0"
|
implementation "ru.noties.markwon:core:3.1.0"
|
||||||
implementation ("xyz.quaver:libpupil:1.3") {
|
|
||||||
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
|
implementation "xyz.quaver:libpupil:1.8.16"
|
||||||
}
|
implementation "xyz.quaver:documentfilex:0.4-alpha02"
|
||||||
implementation "xyz.quaver:documentfilex:0.2.14-alpha2"
|
implementation "xyz.quaver:floatingsearchview:1.0.7"
|
||||||
testImplementation 'junit:junit:4.13'
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
testImplementation "junit:junit:4.13"
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation "androidx.test:rules:1.3.0"
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
androidTestImplementation "androidx.test:runner:1.3.0"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
androidExtensions {
|
androidExtensions {
|
||||||
|
|||||||
BIN
app/libs/recyclerviewfastscroller-release.aar
Normal file
BIN
app/libs/recyclerviewfastscroller-release.aar
Normal file
Binary file not shown.
18
app/proguard-rules.pro
vendored
18
app/proguard-rules.pro
vendored
@@ -22,21 +22,6 @@
|
|||||||
|
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
|
||||||
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
|
||||||
<init>(...);
|
|
||||||
}
|
|
||||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
|
||||||
**[] $VALUES;
|
|
||||||
public *;
|
|
||||||
}
|
|
||||||
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
|
|
||||||
*** rewind();
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
|
||||||
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl
|
|
||||||
|
|
||||||
-keepattributes *Annotation*, InnerClasses
|
-keepattributes *Annotation*, InnerClasses
|
||||||
-dontnote kotlinx.serialization.SerializationKt
|
-dontnote kotlinx.serialization.SerializationKt
|
||||||
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
|
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
|
||||||
@@ -47,5 +32,4 @@
|
|||||||
kotlinx.serialization.KSerializer serializer(...);
|
kotlinx.serialization.KSerializer serializer(...);
|
||||||
}
|
}
|
||||||
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
||||||
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
||||||
-keep class xyz.quaver.pupil.util.Preferences
|
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"artifactType": {
|
"artifactType": {
|
||||||
"type": "APK",
|
"type": "APK",
|
||||||
"kind": "Directory"
|
"kind": "Directory"
|
||||||
},
|
},
|
||||||
"applicationId": "xyz.quaver.pupil",
|
"applicationId": "xyz.quaver.pupil",
|
||||||
"variantName": "release",
|
"variantName": "processReleaseResources",
|
||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"properties": [],
|
"versionCode": 63,
|
||||||
"versionCode": 57,
|
"versionName": "5.1.6-hotfix7",
|
||||||
"versionName": "5.0-beta3",
|
|
||||||
"enabled": true,
|
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -20,26 +20,10 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
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 kotlinx.coroutines.runBlocking
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
|
||||||
import xyz.quaver.hitomi.getSuggestionsForQuery
|
|
||||||
import xyz.quaver.hiyobi.cookie
|
|
||||||
import xyz.quaver.hiyobi.createImgList
|
|
||||||
import xyz.quaver.hiyobi.getReader
|
|
||||||
import xyz.quaver.hiyobi.user_agent
|
|
||||||
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 java.io.InputStreamReader
|
|
||||||
import java.net.URL
|
|
||||||
import javax.net.ssl.HttpsURLConnection
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented test, which will execute on an Android device.
|
* Instrumented test, which will execute on an Android device.
|
||||||
@@ -54,77 +38,4 @@ class ExampleInstrumentedTest {
|
|||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun checkCacheDir() {
|
|
||||||
val activityTestRule = ActivityTestRule(LockActivity::class.java)
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
|
|
||||||
Runtime.getRuntime().exec("du -hs " + getDownloadDirectory(appContext)).let {
|
|
||||||
InputStreamReader(it.inputStream).readLines().forEach { res ->
|
|
||||||
Log.i("PUPILD", res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_nozomi() {
|
|
||||||
val nozomi = getGalleryIDsFromNozomi(null, "index", "all")
|
|
||||||
|
|
||||||
Log.i("PUPILD", nozomi.size.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_doSearch() {
|
|
||||||
val reader = getReader( 1426382)
|
|
||||||
|
|
||||||
val data: ByteArray
|
|
||||||
|
|
||||||
with(URL(createImgList(1426382, reader)[0].path).openConnection() as HttpsURLConnection) {
|
|
||||||
setRequestProperty("User-Agent", user_agent)
|
|
||||||
setRequestProperty("Cookie", cookie)
|
|
||||||
|
|
||||||
data = inputStream.readBytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("Pupil", data.size.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@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.isInfinite() } == 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)?.galleryInfo?.title ?: "null")
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.title ?: "null")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_suggestion() {
|
|
||||||
getSuggestionsForQuery("female:l")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="21"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="21" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Pupil"
|
android:name=".Pupil"
|
||||||
@@ -24,6 +27,10 @@
|
|||||||
tools:replace="android:theme"
|
tools:replace="android:theme"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.mlkit.vision.DEPENDENCIES"
|
||||||
|
android:value="face" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.arlib.floatingsearchview
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.util.AttributeSet
|
|
||||||
|
|
||||||
class FloatingSearchViewDayNight @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null)
|
|
||||||
: FloatingSearchView(context, attrs) {
|
|
||||||
|
|
||||||
// hack to remove color attributes which should not be reused
|
|
||||||
override fun onSaveInstanceState(): Parcelable? {
|
|
||||||
super.onSaveInstanceState()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,19 +29,20 @@ import android.os.Build
|
|||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.github.piasy.biv.BigImageViewer
|
||||||
|
import com.github.piasy.biv.loader.fresco.FrescoImageLoader
|
||||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||||
import com.google.android.gms.security.ProviderInstaller
|
import com.google.android.gms.security.ProviderInstaller
|
||||||
import com.google.firebase.analytics.FirebaseAnalytics
|
import com.google.firebase.analytics.FirebaseAnalytics
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.setClient
|
import xyz.quaver.setClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -50,9 +51,13 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
||||||
|
|
||||||
lateinit var histories: GalleryList
|
lateinit var histories: SavedSet<Int>
|
||||||
private set
|
private set
|
||||||
lateinit var favorites: GalleryList
|
lateinit var favorites: SavedSet<Int>
|
||||||
|
private set
|
||||||
|
lateinit var favoriteTags: SavedSet<Tag>
|
||||||
|
private set
|
||||||
|
lateinit var searchHistory: SavedSet<String>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val interceptors = mutableMapOf<KClass<out Any>, PupilInterceptor>()
|
val interceptors = mutableMapOf<KClass<out Any>, PupilInterceptor>()
|
||||||
@@ -68,11 +73,9 @@ val client: OkHttpClient
|
|||||||
|
|
||||||
class Pupil : Application() {
|
class Pupil : Application() {
|
||||||
|
|
||||||
init {
|
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
val userID = Preferences["user_id", ""].let { userID ->
|
val userID = Preferences["user_id", ""].let { userID ->
|
||||||
@@ -97,7 +100,7 @@ class Pupil : Application() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Preferences.get<String>("download_folder").also {
|
Preferences.get<String>("download_folder").also {
|
||||||
if (Build.VERSION.SDK_INT > 19)
|
if (it.startsWith("content") && Build.VERSION.SDK_INT > 19)
|
||||||
contentResolver.takePersistableUriPermission(
|
contentResolver.takePersistableUriPermission(
|
||||||
Uri.parse(it),
|
Uri.parse(it),
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
@@ -105,28 +108,23 @@ class Pupil : Application() {
|
|||||||
|
|
||||||
if (!FileX(this, it).canWrite())
|
if (!FileX(this, it).canWrite())
|
||||||
throw Exception()
|
throw Exception()
|
||||||
|
|
||||||
|
DownloadManager.getInstance(this).migrate()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Preferences.remove("download_folder")
|
Preferences.remove("download_folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
histories = GalleryList(File(ContextCompat.getDataDir(this), "histories.json"))
|
if (!Preferences["reset_secure", false]) {
|
||||||
favorites = GalleryList(File(ContextCompat.getDataDir(this), "favorites.json"))
|
Preferences["security_mode"] = false
|
||||||
|
Preferences["reset_secure"] = true
|
||||||
if (Preferences["new_history"]) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
histories.reversed().let {
|
|
||||||
histories.clear()
|
|
||||||
histories.addAll(it)
|
|
||||||
}
|
|
||||||
favorites.reversed().let {
|
|
||||||
favorites.clear()
|
|
||||||
favorites.addAll(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Preferences["new_history"] = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
|
||||||
|
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
|
||||||
|
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
|
||||||
|
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
|
||||||
|
|
||||||
if (BuildConfig.DEBUG)
|
if (BuildConfig.DEBUG)
|
||||||
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
|
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
|
||||||
|
|
||||||
@@ -138,6 +136,8 @@ class Pupil : Application() {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BigImageViewer.initialize(FrescoImageLoader.with(this))
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.Registry
|
|
||||||
import com.bumptech.glide.annotation.GlideModule
|
|
||||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
|
||||||
import com.bumptech.glide.module.AppGlideModule
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
@GlideModule
|
|
||||||
class PupilGlideModule : AppGlideModule() {
|
|
||||||
|
|
||||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
|
||||||
registry.append(
|
|
||||||
GlideUrl::class.java,
|
|
||||||
InputStream::class.java,
|
|
||||||
OkHttpUrlLoader.Factory(client)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -18,52 +18,42 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.PorterDuffColorFilter
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.Log
|
||||||
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.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.Toast
|
||||||
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.RequestManager
|
|
||||||
import com.bumptech.glide.load.DataSource
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
|
||||||
import com.bumptech.glide.request.RequestListener
|
|
||||||
import com.bumptech.glide.request.target.Target
|
|
||||||
import com.daimajia.swipe.SwipeLayout
|
import com.daimajia.swipe.SwipeLayout
|
||||||
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
||||||
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
||||||
import com.google.android.material.chip.Chip
|
import com.github.piasy.biv.loader.ImageLoader
|
||||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import xyz.quaver.hitomi.getGallery
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import xyz.quaver.hitomi.getReader
|
import xyz.quaver.hitomi.getReader
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.ui.view.TagChip
|
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
import java.util.*
|
import java.io.File
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.concurrent.schedule
|
|
||||||
|
|
||||||
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
class GalleryBlockAdapter(private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||||
|
|
||||||
enum class ViewType {
|
enum class ViewType {
|
||||||
NEXT,
|
NEXT,
|
||||||
@@ -71,34 +61,50 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
PREV
|
PREV
|
||||||
}
|
}
|
||||||
|
|
||||||
val timer = Timer()
|
var updateAll = true
|
||||||
|
var thin: Boolean = Preferences["thin"]
|
||||||
var isThin = false
|
|
||||||
|
|
||||||
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
var timerTask: TimerTask? = null
|
private var galleryID: Int = 0
|
||||||
|
|
||||||
private fun updateProgress(context: Context, galleryID: Int) {
|
init {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
while (updateAll) {
|
||||||
|
updateProgress(view.context)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProgress(context: Context) {
|
||||||
val cache = Cache.getInstance(context, galleryID)
|
val cache = Cache.getInstance(context, galleryID)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (cache.metadata.reader == null || Preferences["cache_disable"]) {
|
if (cache.metadata.reader == null) {
|
||||||
view.galleryblock_progressbar.visibility = View.GONE
|
view.galleryblock_progressbar_layout.visibility = View.GONE
|
||||||
view.galleryblock_progress_complete.visibility = View.GONE
|
view.galleryblock_progress_complete.visibility = View.INVISIBLE
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
with(view.galleryblock_progressbar) {
|
with(view.galleryblock_progressbar) {
|
||||||
val imageList = cache.metadata.imageList!!
|
val imageList = cache.metadata.imageList!!
|
||||||
|
|
||||||
progress = imageList.filterNotNull().size
|
progress = imageList.count { it != null }
|
||||||
|
max = imageList.size
|
||||||
|
|
||||||
if (visibility == View.GONE) {
|
with(view.galleryblock_progressbar_layout) {
|
||||||
visibility = View.VISIBLE
|
if (visibility == View.GONE)
|
||||||
max = imageList.size
|
visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress == max) {
|
view.galleryblock_id.setOnClickListener {
|
||||||
|
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
|
||||||
|
ClipData.newPlainText("gallery_id", galleryID.toString())
|
||||||
|
)
|
||||||
|
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!imageList.contains(null)) {
|
||||||
val downloadManager = DownloadManager.getInstance(context)
|
val downloadManager = DownloadManager.getInstance(context)
|
||||||
|
|
||||||
if (completeFlag.get(galleryID, false)) {
|
if (completeFlag.get(galleryID, false)) {
|
||||||
@@ -130,9 +136,14 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun bind(galleryID: Int) {
|
fun bind(galleryID: Int) {
|
||||||
|
this.galleryID = galleryID
|
||||||
|
updateProgress(view.context)
|
||||||
|
|
||||||
val cache = Cache.getInstance(view.context, galleryID)
|
val cache = Cache.getInstance(view.context, galleryID)
|
||||||
|
|
||||||
val galleryBlock = cache.metadata.galleryBlock ?: return
|
val galleryBlock = runBlocking {
|
||||||
|
cache.getGalleryBlock()
|
||||||
|
} ?: return
|
||||||
|
|
||||||
with(view) {
|
with(view) {
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
@@ -145,60 +156,61 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
val artists = galleryBlock.artists
|
val artists = galleryBlock.artists
|
||||||
val series = galleryBlock.series
|
val series = galleryBlock.series
|
||||||
|
|
||||||
if (isThin)
|
galleryblock_thumbnail.apply {
|
||||||
galleryblock_thumbnail.layoutParams.width = context.resources.getDimensionPixelSize(
|
setOnClickListener {
|
||||||
R.dimen.galleryblock_thumbnail_thin
|
view.performClick()
|
||||||
)
|
|
||||||
|
|
||||||
galleryblock_thumbnail.setImageDrawable(CircularProgressDrawable(context).also {
|
|
||||||
it.start()
|
|
||||||
})
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val thumbnail = cache.getThumbnail()
|
|
||||||
|
|
||||||
glide
|
|
||||||
.load(thumbnail)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.error(R.drawable.image_broken_variant)
|
|
||||||
.listener(object: RequestListener<Drawable> {
|
|
||||||
override fun onLoadFailed(
|
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
Cache.getInstance(context, galleryID).findFile(".thumbnail")?.let { if (it.exists()) it.delete() }
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(
|
|
||||||
resource: Drawable?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
dataSource: DataSource?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean = false
|
|
||||||
})
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
}.let { launch(Dispatchers.Main) { it.into(galleryblock_thumbnail) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timerTask == null)
|
|
||||||
timerTask = timer.schedule(0, 1000) {
|
|
||||||
updateProgress(context, galleryID)
|
|
||||||
}
|
}
|
||||||
|
setOnLongClickListener {
|
||||||
|
view.performLongClick()
|
||||||
|
}
|
||||||
|
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||||
|
setImageLoaderCallback(object: ImageLoader.Callback {
|
||||||
|
override fun onFail(error: Exception?) {
|
||||||
|
Cache.getInstance(context, galleryID).let { cache ->
|
||||||
|
cache.cacheFolder.getChild(".thumbnail").let { if (it.exists()) it.delete() }
|
||||||
|
cache.downloadFolder?.getChild(".thumbnail")?.let { if (it.exists()) it.delete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCacheHit(imageType: Int, image: File?) {}
|
||||||
|
override fun onCacheMiss(imageType: Int, image: File?) {}
|
||||||
|
override fun onFinish() {}
|
||||||
|
override fun onProgress(progress: Int) {}
|
||||||
|
override fun onStart() {}
|
||||||
|
override fun onSuccess(image: File?) {}
|
||||||
|
})
|
||||||
|
ssiv?.recycle()
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
cache.getThumbnail().let { launch(Dispatchers.Main) {
|
||||||
|
showImage(it)
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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() }
|
||||||
visibility = when {
|
visibility = when {
|
||||||
artists.isNotEmpty() -> View.VISIBLE
|
artists.isNotEmpty() -> View.VISIBLE
|
||||||
else -> View.GONE
|
else -> View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val gallery = runCatching {
|
||||||
|
getGallery(galleryID)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (gallery?.groups?.isNotEmpty() != true)
|
||||||
|
return@launch
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
text = context.getString(
|
||||||
|
R.string.galleryblock_artist_with_group,
|
||||||
|
artists.joinToString { it.wordCapitalize() },
|
||||||
|
gallery.groups.joinToString { it.wordCapitalize() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
with(galleryblock_series) {
|
with(galleryblock_series) {
|
||||||
text =
|
text =
|
||||||
@@ -220,14 +232,37 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryblock_tag_group.removeAllViews()
|
with(galleryblock_tag_group) {
|
||||||
galleryBlock.relatedTags.forEach {
|
onClickListener = {
|
||||||
galleryblock_tag_group.addView(TagChip(context, Tag.parse(it)).apply {
|
onChipClickedHandler.forEach { callback ->
|
||||||
setOnClickListener { view ->
|
callback.invoke(it)
|
||||||
for (callback in onChipClickedHandler)
|
|
||||||
callback.invoke((view as TagChip).tag)
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
tags.clear()
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
tags.addAll(
|
||||||
|
galleryBlock.relatedTags.sortedBy {
|
||||||
|
val tag = Tag.parse(it)
|
||||||
|
|
||||||
|
if (favoriteTags.contains(tag))
|
||||||
|
-1
|
||||||
|
else
|
||||||
|
when(Tag.parse(it).area) {
|
||||||
|
"female" -> 0
|
||||||
|
"male" -> 1
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
Tag.parse(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryblock_id.text = galleryBlock.id.toString()
|
galleryblock_id.text = galleryBlock.id.toString()
|
||||||
@@ -270,9 +305,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
|
|
||||||
|
|
||||||
// Make some views invisible to make it thinner
|
// Make some views invisible to make it thinner
|
||||||
if (isThin) {
|
if (thin) {
|
||||||
galleryblock_language.visibility = View.GONE
|
|
||||||
galleryblock_type.visibility = View.GONE
|
|
||||||
galleryblock_tag_group.visibility = View.GONE
|
galleryblock_tag_group.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,15 +400,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
super.onViewDetachedFromWindow(holder)
|
|
||||||
|
|
||||||
if (holder is GalleryViewHolder) {
|
|
||||||
holder.timerTask?.cancel()
|
|
||||||
holder.timerTask = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() =
|
override fun getItemCount() =
|
||||||
galleries.size +
|
galleries.size +
|
||||||
(if (showNext) 1 else 0) +
|
(if (showNext) 1 else 0) +
|
||||||
|
|||||||
@@ -18,59 +18,93 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Animatable
|
||||||
|
import android.net.Uri
|
||||||
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.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import com.bumptech.glide.load.DataSource
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.facebook.drawee.controller.BaseControllerListener
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import com.facebook.drawee.drawable.ScalingUtils
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.facebook.drawee.interfaces.DraweeController
|
||||||
import com.bumptech.glide.load.model.LazyHeaders
|
import com.facebook.drawee.view.SimpleDraweeView
|
||||||
import com.bumptech.glide.request.RequestListener
|
import com.facebook.imagepipeline.image.ImageInfo
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.github.piasy.biv.view.BigImageView
|
||||||
|
import com.github.piasy.biv.view.ImageShownCallback
|
||||||
|
import com.github.piasy.biv.view.ImageViewFactory
|
||||||
import kotlinx.android.synthetic.main.item_reader.view.*
|
import kotlinx.android.synthetic.main.item_reader.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import xyz.quaver.Code
|
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.getReferer
|
|
||||||
import xyz.quaver.hitomi.imageUrlFromImage
|
|
||||||
import xyz.quaver.hiyobi.cookie
|
|
||||||
import xyz.quaver.hiyobi.createImgList
|
|
||||||
import xyz.quaver.hiyobi.user_agent
|
|
||||||
import xyz.quaver.io.util.readBytes
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import xyz.quaver.pupil.util.Preferences
|
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import java.util.*
|
import java.io.File
|
||||||
import kotlin.concurrent.schedule
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ReaderAdapter(private val activity: ReaderActivity,
|
class ReaderAdapter(
|
||||||
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
private val activity: ReaderActivity,
|
||||||
|
private val galleryID: Int
|
||||||
|
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||||
|
|
||||||
var reader: Reader? = null
|
var reader: Reader? = null
|
||||||
val timer = Timer()
|
|
||||||
|
|
||||||
private val glide = Glide.with(activity)
|
|
||||||
|
|
||||||
var isFullScreen = false
|
var isFullScreen = false
|
||||||
|
|
||||||
var onItemClickListener : ((Int) -> (Unit))? = null
|
var onItemClickListener : (() -> (Unit))? = null
|
||||||
|
|
||||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
fun clear() {
|
||||||
|
view.image.mainView.let {
|
||||||
|
when (it) {
|
||||||
|
is SubsamplingScaleImageView ->
|
||||||
|
it.recycle()
|
||||||
|
is SimpleDraweeView ->
|
||||||
|
it.controller = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return 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 {
|
||||||
|
with(it) {
|
||||||
|
image.setImageViewFactory(FrescoImageViewFactory().apply {
|
||||||
|
updateView = { imageInfo ->
|
||||||
|
it.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
|
dimensionRatio = "${imageInfo.width}:${imageInfo.height}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
image.setImageShownCallback(object : ImageShownCallback {
|
||||||
|
override fun onMainImageShown() {
|
||||||
|
it.image.mainView.let { v ->
|
||||||
|
when (v) {
|
||||||
|
is SubsamplingScaleImageView ->
|
||||||
|
if (!isFullScreen) it.image.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onThumbnailShown() {}
|
||||||
|
})
|
||||||
|
image.setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||||
|
image.setOnClickListener {
|
||||||
|
this.performClick()
|
||||||
|
}
|
||||||
|
setOnClickListener {
|
||||||
|
onItemClickListener?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ViewHolder(it)
|
ViewHolder(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,109 +116,134 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
|||||||
if (cache == null)
|
if (cache == null)
|
||||||
cache = Cache.getInstance(holder.view.context, galleryID)
|
cache = Cache.getInstance(holder.view.context, galleryID)
|
||||||
|
|
||||||
if (isFullScreen) {
|
if (!isFullScreen) {
|
||||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
|
holder.view.setBackgroundResource(R.drawable.reader_item_boundary)
|
||||||
holder.view.container.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
|
height = 0
|
||||||
|
dimensionRatio =
|
||||||
|
"${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
|
holder.view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
holder.view.container.layoutParams.height = 0
|
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
|
height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||||
(holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
|
dimensionRatio = null
|
||||||
.dimensionRatio = "W,${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
}
|
||||||
}
|
holder.view.background = null
|
||||||
|
|
||||||
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
|
||||||
onItemClickListener?.invoke(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.view.container.setOnClickListener {
|
|
||||||
onItemClickListener?.invoke(position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.view.reader_index.text = (position+1).toString()
|
holder.view.reader_index.text = (position+1).toString()
|
||||||
|
|
||||||
if (Preferences["cache_disable"]) {
|
val image = cache!!.getImage(position)
|
||||||
val lowQuality: Boolean = Preferences["low_quality"]
|
val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
|
||||||
|
|
||||||
val url = when (reader!!.code) {
|
if (progress?.isInfinite() == true && image != null) {
|
||||||
Code.HITOMI ->
|
holder.view.progress_group.visibility = View.INVISIBLE
|
||||||
GlideUrl(
|
holder.view.image.showImage(image.uri)
|
||||||
imageUrlFromImage(
|
|
||||||
galleryID,
|
|
||||||
reader!!.galleryInfo.files[position],
|
|
||||||
!lowQuality
|
|
||||||
)
|
|
||||||
, LazyHeaders.Builder().addHeader("Referer", getReferer(galleryID)).build())
|
|
||||||
Code.HIYOBI ->
|
|
||||||
GlideUrl(createImgList(galleryID, reader!!, lowQuality)[position].path, LazyHeaders.Builder()
|
|
||||||
.addHeader("User-Agent", user_agent)
|
|
||||||
.addHeader("Cookie", cookie)
|
|
||||||
.build())
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
holder.view.image.post {
|
|
||||||
glide
|
|
||||||
.load(url!!)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(false)
|
|
||||||
.fitCenter()
|
|
||||||
.error(R.drawable.image_broken_variant)
|
|
||||||
.into(holder.view.image)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
val image = cache!!.getImage(position)
|
holder.view.progress_group.visibility = View.VISIBLE
|
||||||
val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
|
holder.view.reader_item_progressbar.progress =
|
||||||
|
if (progress?.isInfinite() == true)
|
||||||
|
100
|
||||||
|
else
|
||||||
|
progress?.roundToInt() ?: 0
|
||||||
|
|
||||||
if (progress?.isInfinite() == true && image != null) {
|
holder.clear()
|
||||||
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
glide
|
delay(1000)
|
||||||
.load(image.readBytes())
|
notifyItemChanged(position)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.fitCenter()
|
|
||||||
.error(R.drawable.image_broken_variant)
|
|
||||||
.listener(object: RequestListener<Drawable> {
|
|
||||||
override fun onLoadFailed(
|
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
cache!!.metadata.imageList?.set(position, null)
|
|
||||||
image.delete()
|
|
||||||
DownloadService.cancel(holder.view.context, galleryID)
|
|
||||||
DownloadService.download(holder.view.context, galleryID, true)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean) =
|
|
||||||
false
|
|
||||||
}).let { launch(Dispatchers.Main) { it.into(holder.view.image) } }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
holder.view.reader_item_progressbar.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
glide.clear(holder.view.image)
|
|
||||||
|
|
||||||
holder.view.reader_item_progressbar.progress =
|
|
||||||
if (progress?.isInfinite() == true)
|
|
||||||
100
|
|
||||||
else
|
|
||||||
progress?.roundToInt() ?: 0
|
|
||||||
|
|
||||||
holder.view.image.setImageDrawable(null)
|
|
||||||
|
|
||||||
timer.schedule(1000) {
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
notifyItemChanged(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
|
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
|
holder.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class FrescoImageViewFactory : ImageViewFactory() {
|
||||||
|
var updateView: ((ImageInfo) -> Unit)? = null
|
||||||
|
|
||||||
|
override fun createAnimatedImageView(
|
||||||
|
context: Context, imageType: Int,
|
||||||
|
initScaleType: Int
|
||||||
|
): View {
|
||||||
|
val view = SimpleDraweeView(context)
|
||||||
|
view.hierarchy.actualImageScaleType = scaleType(initScaleType)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadAnimatedContent(
|
||||||
|
view: View, imageType: Int,
|
||||||
|
imageFile: File
|
||||||
|
) {
|
||||||
|
if (view is SimpleDraweeView) {
|
||||||
|
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setUri(Uri.parse("file://" + imageFile.absolutePath))
|
||||||
|
.setAutoPlayAnimations(true)
|
||||||
|
.setControllerListener(object: BaseControllerListener<ImageInfo>() {
|
||||||
|
override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
|
||||||
|
imageInfo?.let { updateView?.invoke(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
|
||||||
|
imageInfo?.let { updateView?.invoke(it) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
view.controller = controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createThumbnailView(
|
||||||
|
context: Context,
|
||||||
|
scaleType: ImageView.ScaleType, willLoadFromNetwork: Boolean
|
||||||
|
): View {
|
||||||
|
return if (willLoadFromNetwork) {
|
||||||
|
val thumbnailView = SimpleDraweeView(context)
|
||||||
|
thumbnailView.hierarchy.actualImageScaleType = scaleType(scaleType)
|
||||||
|
thumbnailView
|
||||||
|
} else {
|
||||||
|
super.createThumbnailView(context, scaleType, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadThumbnailContent(view: View, thumbnail: Uri) {
|
||||||
|
if (view is SimpleDraweeView) {
|
||||||
|
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setUri(thumbnail)
|
||||||
|
.build()
|
||||||
|
view.controller = controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scaleType(value: Int): ScalingUtils.ScaleType {
|
||||||
|
return when (value) {
|
||||||
|
BigImageView.INIT_SCALE_TYPE_CENTER -> ScalingUtils.ScaleType.CENTER
|
||||||
|
BigImageView.INIT_SCALE_TYPE_CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
|
||||||
|
BigImageView.INIT_SCALE_TYPE_CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
|
||||||
|
BigImageView.INIT_SCALE_TYPE_FIT_END -> ScalingUtils.ScaleType.FIT_END
|
||||||
|
BigImageView.INIT_SCALE_TYPE_FIT_START -> ScalingUtils.ScaleType.FIT_START
|
||||||
|
BigImageView.INIT_SCALE_TYPE_FIT_XY -> ScalingUtils.ScaleType.FIT_XY
|
||||||
|
BigImageView.INIT_SCALE_TYPE_FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
|
||||||
|
else -> ScalingUtils.ScaleType.FIT_CENTER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scaleType(scaleType: ImageView.ScaleType): ScalingUtils.ScaleType {
|
||||||
|
return when (scaleType) {
|
||||||
|
ImageView.ScaleType.CENTER -> ScalingUtils.ScaleType.CENTER
|
||||||
|
ImageView.ScaleType.CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
|
||||||
|
ImageView.ScaleType.CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
|
||||||
|
ImageView.ScaleType.FIT_END -> ScalingUtils.ScaleType.FIT_END
|
||||||
|
ImageView.ScaleType.FIT_START -> ScalingUtils.ScaleType.FIT_START
|
||||||
|
ImageView.ScaleType.FIT_XY -> ScalingUtils.ScaleType.FIT_XY
|
||||||
|
ImageView.ScaleType.FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
|
||||||
|
else -> ScalingUtils.ScaleType.FIT_CENTER
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,32 +18,35 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.RequestManager
|
import com.github.piasy.biv.view.BigImageView
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.BuildConfig
|
|
||||||
|
|
||||||
class ThumbnailAdapter(private val glide: RequestManager, var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
class ThumbnailAdapter(var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
||||||
|
|
||||||
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: BigImageView) : RecyclerView.ViewHolder(view) {
|
||||||
|
fun clear() {
|
||||||
|
view.ssiv?.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(ImageView(parent.context))
|
return ViewHolder(BigImageView(parent.context).apply {
|
||||||
|
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
glide
|
holder.view.showImage(Uri.parse(thumbnails[position]))
|
||||||
.load(thumbnails[position])
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
}
|
|
||||||
.into(holder.view)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = thumbnails.size
|
override fun getItemCount() = thumbnails.size
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
|
holder.clear()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -21,17 +21,19 @@ package xyz.quaver.pupil.adapters
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class ThumbnailPageAdapter(private val glide: RequestManager, private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailPageAdapter.ViewHolder>() {
|
class ThumbnailPageAdapter(private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailPageAdapter.ViewHolder>() {
|
||||||
|
|
||||||
class ViewHolder(val view: RecyclerView) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: RecyclerView) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(RecyclerView(parent.context).apply {
|
return ViewHolder(RecyclerView(parent.context).apply {
|
||||||
layoutManager = GridLayoutManager(parent.context, 3)
|
val layoutManager = GridLayoutManager(parent.context, 3)
|
||||||
adapter = ThumbnailAdapter(glide, listOf())
|
val adapter = ThumbnailAdapter(listOf())
|
||||||
|
|
||||||
|
this.layoutManager = layoutManager
|
||||||
|
this.adapter = adapter
|
||||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -41,7 +43,7 @@ class ThumbnailPageAdapter(private val glide: RequestManager, private val thumbn
|
|||||||
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
|
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
|
|
||||||
holder.view.layoutManager?.scrollToPosition(itemCount-1)
|
(holder.view.layoutManager as GridLayoutManager).scrollToPosition(8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import android.app.PendingIntent
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.SparseArray
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -36,18 +36,19 @@ import okhttp3.Callback
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okio.*
|
import okio.*
|
||||||
import xyz.quaver.pupil.PupilInterceptor
|
import xyz.quaver.pupil.*
|
||||||
import xyz.quaver.pupil.R
|
|
||||||
import xyz.quaver.pupil.client
|
|
||||||
import xyz.quaver.pupil.interceptors
|
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
|
import xyz.quaver.pupil.util.cleanCache
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.ellipsize
|
import xyz.quaver.pupil.util.ellipsize
|
||||||
import xyz.quaver.pupil.util.normalizeID
|
import xyz.quaver.pupil.util.normalizeID
|
||||||
import xyz.quaver.pupil.util.requestBuilders
|
import xyz.quaver.pupil.util.requestBuilders
|
||||||
import xyz.quaver.pupil.util.startForegroundServiceCompat
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.log10
|
||||||
|
|
||||||
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
||||||
class DownloadService : Service() {
|
class DownloadService : Service() {
|
||||||
@@ -66,7 +67,7 @@ class DownloadService : Service() {
|
|||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val notification = SparseArray<NotificationCompat.Builder?>()
|
private val notification = ConcurrentHashMap<Int, NotificationCompat.Builder?>()
|
||||||
|
|
||||||
private fun initNotification(galleryID: Int) {
|
private fun initNotification(galleryID: Int) {
|
||||||
val intent = Intent(this, ReaderActivity::class.java)
|
val intent = Intent(this, ReaderActivity::class.java)
|
||||||
@@ -87,7 +88,7 @@ class DownloadService : Service() {
|
|||||||
PendingIntent.FLAG_UPDATE_CURRENT),
|
PendingIntent.FLAG_UPDATE_CURRENT),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
notification.put(galleryID, NotificationCompat.Builder(this, "download").apply {
|
notification[galleryID] = NotificationCompat.Builder(this, "download").apply {
|
||||||
setContentTitle(getString(R.string.reader_loading))
|
setContentTitle(getString(R.string.reader_loading))
|
||||||
setContentText(getString(R.string.reader_notification_text))
|
setContentText(getString(R.string.reader_notification_text))
|
||||||
setSmallIcon(R.drawable.ic_notification)
|
setSmallIcon(R.drawable.ic_notification)
|
||||||
@@ -95,7 +96,7 @@ class DownloadService : Service() {
|
|||||||
addAction(action)
|
addAction(action)
|
||||||
setProgress(0, 0, true)
|
setProgress(0, 0, true)
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
})
|
}
|
||||||
|
|
||||||
notify(galleryID)
|
notify(galleryID)
|
||||||
}
|
}
|
||||||
@@ -120,7 +121,7 @@ class DownloadService : Service() {
|
|||||||
.setProgress(max, progress, false)
|
.setProgress(max, progress, false)
|
||||||
.setContentText("$progress/$max")
|
.setContentText("$progress/$max")
|
||||||
|
|
||||||
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null)
|
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null || galleryID == priority)
|
||||||
notification.let { notificationManager.notify(galleryID, it.build()) }
|
notification.let { notificationManager.notify(galleryID, it.build()) }
|
||||||
else
|
else
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID)
|
||||||
@@ -194,7 +195,8 @@ class DownloadService : Service() {
|
|||||||
* 0 <= value < 100 -> Download in progress
|
* 0 <= value < 100 -> Download in progress
|
||||||
* Float.POSITIVE_INFINITY -> Download completed
|
* Float.POSITIVE_INFINITY -> Download completed
|
||||||
*/
|
*/
|
||||||
val progress = SparseArray<MutableList<Float>?>()
|
val progress = ConcurrentHashMap<Int, MutableList<Float>>()
|
||||||
|
var priority = 0
|
||||||
|
|
||||||
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
|
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
|
||||||
|
|
||||||
@@ -215,11 +217,12 @@ class DownloadService : Service() {
|
|||||||
val ext = call.request().url().encodedPath().split('.').last()
|
val ext = call.request().url().encodedPath().split('.').last()
|
||||||
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val image = response.body()?.use { it.bytes() } ?: throw Exception()
|
val image = response.also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() } ?: throw Exception()
|
||||||
|
val padding = ceil(progress[galleryID]?.size?.let { log10(it.toFloat()) } ?: 0F).toInt()
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
Cache.getInstance(this@DownloadService, galleryID).putImage(index, "$index.$ext", image)
|
Cache.getInstance(this@DownloadService, galleryID).putImage(index, "${index.toString().padStart(padding, '0')}.$ext", image)
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
||||||
notify(galleryID)
|
notify(galleryID)
|
||||||
@@ -285,14 +288,16 @@ class DownloadService : Service() {
|
|||||||
fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
|
fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
cancel(galleryID)
|
cancel(galleryID)
|
||||||
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
||||||
Cache.delete(galleryID)
|
Cache.delete(this@DownloadService, galleryID)
|
||||||
|
|
||||||
startId?.let { stopSelf(it) }
|
startId?.let { stopSelf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
|
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (progress.indexOfKey(galleryID) >= 0)
|
if (DownloadManager.getInstance(this@DownloadService).isDownloading(galleryID))
|
||||||
cancel(galleryID)
|
return@launch
|
||||||
|
|
||||||
|
cleanCache(this@DownloadService)
|
||||||
|
|
||||||
val cache = Cache.getInstance(this@DownloadService, galleryID)
|
val cache = Cache.getInstance(this@DownloadService, galleryID)
|
||||||
|
|
||||||
@@ -303,15 +308,28 @@ class DownloadService : Service() {
|
|||||||
// Gallery doesn't exist
|
// Gallery doesn't exist
|
||||||
if (reader == null) {
|
if (reader == null) {
|
||||||
delete(galleryID)
|
delete(galleryID)
|
||||||
progress.put(galleryID, null)
|
progress[galleryID] = mutableListOf()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress.indexOfKey(galleryID) < 0)
|
histories.add(galleryID)
|
||||||
progress.put(galleryID, MutableList(reader.galleryInfo.files.size) { 0F })
|
|
||||||
|
|
||||||
cache.metadata.imageList?.forEachIndexed { index, image ->
|
progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 0F }
|
||||||
progress[galleryID]?.set(index, if (image != null) Float.POSITIVE_INFINITY else 0F)
|
|
||||||
|
cache.metadata.imageList?.let {
|
||||||
|
it.forEachIndexed { index, image ->
|
||||||
|
progress[galleryID]?.set(index, if (image != null) Float.POSITIVE_INFINITY else 0F)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCompleted(galleryID)) {
|
||||||
|
if (DownloadManager.getInstance(this@DownloadService)
|
||||||
|
.getDownloadFolder(galleryID) != null )
|
||||||
|
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
||||||
|
|
||||||
|
notificationManager.cancel(galleryID)
|
||||||
|
startId?.let { stopSelf(it) }
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30))
|
notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30))
|
||||||
@@ -328,9 +346,11 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.requestBuilders.filterIndexed { index, _ -> progress[galleryID]?.get(index)?.isInfinite() != true }.forEachIndexed { index, it ->
|
reader.requestBuilders.forEachIndexed { index, it ->
|
||||||
val request = it.tag(Tag(galleryID, index, startId)).build()
|
if (progress[galleryID]?.get(index)?.isInfinite() == false) {
|
||||||
client.newCall(request).enqueue(callback)
|
val request = it.tag(Tag(galleryID, index, startId)).build()
|
||||||
|
client.newCall(request).enqueue(callback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queued.forEach { download(it) }
|
queued.forEach { download(it) }
|
||||||
@@ -347,7 +367,7 @@ class DownloadService : Service() {
|
|||||||
const val COMMAND_DELETE = "DELETE"
|
const val COMMAND_DELETE = "DELETE"
|
||||||
|
|
||||||
private fun command(context: Context, extras: Intent.() -> Unit) {
|
private fun command(context: Context, extras: Intent.() -> Unit) {
|
||||||
context.startForegroundServiceCompat(Intent(context, DownloadService::class.java).apply(extras))
|
ContextCompat.startForegroundService(context, Intent(context, DownloadService::class.java).apply(extras))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(context: Context, galleryID: Int, priority: Boolean = false) {
|
fun download(context: Context, galleryID: Int, priority: Boolean = false) {
|
||||||
@@ -374,6 +394,8 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
||||||
|
|
||||||
when (intent?.getStringExtra(KEY_COMMAND)) {
|
when (intent?.getStringExtra(KEY_COMMAND)) {
|
||||||
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
||||||
download(it, intent.getBooleanExtra(KEY_PRIORITY, false), startId)
|
download(it, intent.getBooleanExtra(KEY_PRIORITY, false), startId)
|
||||||
|
|||||||
@@ -18,15 +18,33 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
import kotlinx.android.parcel.IgnoredOnParcel
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import xyz.quaver.hitomi.Suggestion
|
import xyz.quaver.hitomi.Suggestion
|
||||||
|
import xyz.quaver.pupil.util.translations
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class TagSuggestion(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 {
|
@IgnoredOnParcel
|
||||||
return s
|
override val body =
|
||||||
}
|
if (translations[s] != null)
|
||||||
}
|
"${translations[s]} ($s)"
|
||||||
|
else
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class Suggestion(override val body: String) : SearchSuggestion
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class NoResultSuggestion(override val body: String) : SearchSuggestion
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class LoadingSuggestion(override val body: String) : SearchSuggestion
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@Suppress("PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY")
|
||||||
|
class FavoriteHistorySwitch(override val body: String) : SearchSuggestion
|
||||||
@@ -19,13 +19,12 @@
|
|||||||
package xyz.quaver.pupil.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) {
|
data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) {
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(tag: String) : Tag {
|
fun parse(tag: String) : Tag {
|
||||||
if (tag.first() == '-') {
|
if (tag.firstOrNull() == '-') {
|
||||||
tag.substring(1).split(Regex(":"), 2).let {
|
tag.substring(1).split(Regex(":"), 2).let {
|
||||||
return when(it.size) {
|
return when(it.size) {
|
||||||
2 -> Tag(it[0], it[1], true)
|
2 -> Tag(it[0], it[1], true)
|
||||||
@@ -63,9 +62,7 @@ data class Tag(val area: String?, val tag: String, val isNegative: Boolean = fal
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode() = toString().hashCode()
|
||||||
return super.hashCode()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tags(val tags: MutableSet<Tag> = mutableSetOf()) : MutableSet<Tag> by tags {
|
class Tags(val tags: MutableSet<Tag> = mutableSetOf()) : MutableSet<Tag> by tags {
|
||||||
@@ -111,7 +108,4 @@ class Tags(val tags: MutableSet<Tag> = mutableSetOf()) : MutableSet<Tag> by tags
|
|||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return tags.joinToString(" ") { it.toString() }
|
return tags.joinToString(" ") { it.toString() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
67
app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt
Normal file
67
app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.PersistableBundle
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.util.LockManager
|
||||||
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import xyz.quaver.pupil.util.normalizeID
|
||||||
|
|
||||||
|
open class BaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private var locked: Boolean = true
|
||||||
|
|
||||||
|
private val lockLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK)
|
||||||
|
locked = false
|
||||||
|
else
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
|
||||||
|
super.onCreate(savedInstanceState, persistentState)
|
||||||
|
|
||||||
|
locked = !LockManager(this).locks.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
if (Preferences["security_mode"])
|
||||||
|
window.setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
else
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
|
||||||
|
if (locked)
|
||||||
|
lockLauncher.launch(Intent(this, LockActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ import xyz.quaver.pupil.util.Lock
|
|||||||
import xyz.quaver.pupil.util.LockManager
|
import xyz.quaver.pupil.util.LockManager
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
|
||||||
|
private var lastUnlocked = 0L
|
||||||
class LockActivity : AppCompatActivity() {
|
class LockActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var lockManager: LockManager
|
private lateinit var lockManager: LockManager
|
||||||
@@ -52,6 +53,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
val result = lockManager.check(it)
|
val result = lockManager.check(it)
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
|
lastUnlocked = System.currentTimeMillis()
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(Activity.RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
} else
|
} else
|
||||||
@@ -86,6 +88,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
val result = lockManager.check(it)
|
val result = lockManager.check(it)
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
|
lastUnlocked = System.currentTimeMillis()
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(Activity.RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
@@ -157,6 +160,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
override fun onAuthenticationSucceeded(
|
override fun onAuthenticationSucceeded(
|
||||||
result: BiometricPrompt.AuthenticationResult) {
|
result: BiometricPrompt.AuthenticationResult) {
|
||||||
super.onAuthenticationSucceeded(result)
|
super.onAuthenticationSucceeded(result)
|
||||||
|
lastUnlocked = System.currentTimeMillis()
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
@@ -185,6 +189,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mode = intent.getStringExtra("mode")
|
mode = intent.getStringExtra("mode")
|
||||||
|
val force = intent.getBooleanExtra("force", false)
|
||||||
|
|
||||||
when(mode) {
|
when(mode) {
|
||||||
null -> {
|
null -> {
|
||||||
@@ -194,6 +199,13 @@ class LockActivity : AppCompatActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() - lastUnlocked < 5*60*1000 && !force) {
|
||||||
|
lastUnlocked = System.currentTimeMillis()
|
||||||
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Preferences["lock_fingerprint"]
|
Preferences["lock_fingerprint"]
|
||||||
&& BiometricManager.from(this).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
|
&& BiometricManager.from(this).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|
|||||||
@@ -19,65 +19,59 @@
|
|||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
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.InputType
|
||||||
import android.text.style.AlignmentSpan
|
import android.text.util.Linkify
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.widget.EditText
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.widget.ImageView
|
||||||
import android.widget.*
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.content.res.ResourcesCompat
|
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
|
||||||
import com.arlib.floatingsearchview.FloatingSearchView
|
|
||||||
import com.arlib.floatingsearchview.FloatingSearchViewDayNight
|
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
|
||||||
import com.arlib.floatingsearchview.util.view.SearchInputView
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.navigation.NavigationView
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
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.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.decodeFromString
|
import xyz.quaver.floatingsearchview.FloatingSearchView
|
||||||
import kotlinx.serialization.encodeToString
|
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import kotlinx.serialization.json.Json
|
import xyz.quaver.floatingsearchview.util.view.MenuView
|
||||||
|
import xyz.quaver.floatingsearchview.util.view.SearchInputView
|
||||||
import xyz.quaver.hitomi.doSearch
|
import xyz.quaver.hitomi.doSearch
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||||
import xyz.quaver.hitomi.getSuggestionsForQuery
|
import xyz.quaver.hitomi.getSuggestionsForQuery
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.*
|
||||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||||
import xyz.quaver.pupil.favorites
|
|
||||||
import xyz.quaver.pupil.histories
|
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.types.TagSuggestion
|
import xyz.quaver.pupil.types.*
|
||||||
import xyz.quaver.pupil.types.Tags
|
|
||||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
||||||
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import xyz.quaver.pupil.util.checkUpdate
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.io.File
|
import xyz.quaver.pupil.util.restore
|
||||||
import java.util.*
|
import java.util.regex.Pattern
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity :
|
||||||
|
BaseActivity(),
|
||||||
|
NavigationView.OnNavigationItemSelectedListener
|
||||||
|
{
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
SEARCH,
|
SEARCH,
|
||||||
@@ -111,30 +105,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private var loadingJob: Job? = null
|
private var loadingJob: Job? = null
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
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()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lockManager.isNotEmpty())
|
|
||||||
startActivityForResult(Intent(this, LockActivity::class.java), R.id.request_lock.normalizeID())
|
|
||||||
|
|
||||||
if (intent.action == Intent.ACTION_VIEW) {
|
if (intent.action == Intent.ACTION_VIEW) {
|
||||||
intent.dataString?.let { url ->
|
intent.dataString?.let { url ->
|
||||||
restore(favorites, url,
|
restore(url,
|
||||||
onFailure = {
|
onFailure = {
|
||||||
Snackbar.make(this.main_recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(this.main_recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
||||||
}, onSuccess = {
|
}, onSuccess = {
|
||||||
@@ -144,8 +122,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
|
|
||||||
if (Preferences["download_folder", ""].isEmpty())
|
if (Preferences["download_folder", ""].isEmpty())
|
||||||
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
||||||
|
|
||||||
@@ -173,18 +149,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel()
|
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.updateAll = false
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
if (Preferences["security_mode"])
|
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
else
|
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
|
|
||||||
super.onResume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
@@ -224,24 +189,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
when(requestCode) {
|
|
||||||
R.id.request_settings.normalizeID() -> {
|
|
||||||
runOnUiThread {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.request_lock.normalizeID() -> {
|
|
||||||
if (resultCode != Activity.RESULT_OK)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
var prevP1 = 0
|
var prevP1 = 0
|
||||||
main_appbar_layout.addOnOffsetChangedListener(
|
main_appbar_layout.addOnOffsetChangedListener(
|
||||||
@@ -260,72 +207,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Linkify.addLinks(main_noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) })
|
||||||
|
|
||||||
//NavigationView
|
//NavigationView
|
||||||
main_nav_view.setNavigationItemSelectedListener {
|
main_nav_view.setNavigationItemSelectedListener(this)
|
||||||
runOnUiThread {
|
|
||||||
main_drawer_layout.closeDrawers()
|
|
||||||
|
|
||||||
when(it.itemId) {
|
|
||||||
R.id.main_drawer_home -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.SEARCH
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_history -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.HISTORY
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_downloads -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.DOWNLOAD
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_favorite -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.FAVORITE
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_help -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
|
||||||
}
|
|
||||||
R.id.main_drawer_github -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
|
|
||||||
}
|
|
||||||
R.id.main_drawer_homepage -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
|
|
||||||
}
|
|
||||||
R.id.main_drawer_email -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
|
||||||
}
|
|
||||||
R.id.main_drawer_kakaotalk -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
with(main_fab_cancel) {
|
with(main_fab_cancel) {
|
||||||
setImageResource(R.drawable.cancel)
|
setImageResource(R.drawable.cancel)
|
||||||
@@ -373,11 +258,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (it?.isEmpty() == false) {
|
if (it?.isEmpty() == false) {
|
||||||
val galleryID = it.random()
|
val galleryID = it.random()
|
||||||
|
|
||||||
GalleryDialog(
|
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||||
this@MainActivity,
|
|
||||||
Glide.with(this@MainActivity),
|
|
||||||
galleryID
|
|
||||||
).apply {
|
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
@@ -408,12 +289,22 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setTitle(R.string.main_open_gallery_by_id)
|
setTitle(R.string.main_open_gallery_by_id)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
|
|
||||||
putExtra("galleryID", galleryID)
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivity(intent)
|
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||||
|
onChipClickedHandler.add {
|
||||||
|
runOnUiThread {
|
||||||
|
query = it.toQuery()
|
||||||
|
currentPage = 0
|
||||||
|
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
@@ -428,7 +319,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
with(main_recyclerview) {
|
with(main_recyclerview) {
|
||||||
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply {
|
adapter = GalleryBlockAdapter(galleries).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
@@ -442,16 +333,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
onDownloadClickedHandler = { position ->
|
onDownloadClickedHandler = { position ->
|
||||||
val galleryID = galleries[position]
|
val galleryID = galleries[position]
|
||||||
if (Preferences["cache_disable"])
|
|
||||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
||||||
|
DownloadService.cancel(this@MainActivity, galleryID)
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||||
DownloadService.cancel(this@MainActivity, galleryID)
|
DownloadService.download(this@MainActivity, galleryID)
|
||||||
}
|
|
||||||
else {
|
|
||||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
|
||||||
DownloadService.download(this@MainActivity, galleryID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeAllItems()
|
closeAllItems()
|
||||||
@@ -492,13 +380,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (v !is CardView)
|
if (v !is CardView)
|
||||||
return@listener false
|
return@listener false
|
||||||
|
|
||||||
val galleryID = galleries[position]
|
val galleryID = galleries.getOrNull(position) ?: return@listener true
|
||||||
|
|
||||||
GalleryDialog(
|
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||||
this@MainActivity,
|
|
||||||
Glide.with(this@MainActivity),
|
|
||||||
galleryID
|
|
||||||
).apply {
|
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
@@ -720,37 +604,25 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isFavorite = false
|
||||||
|
private val defaultSuggestions: List<SearchSuggestion>
|
||||||
|
get() = when {
|
||||||
|
isFavorite -> {
|
||||||
|
favoriteTags.map {
|
||||||
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
|
} + FavoriteHistorySwitch(getString(R.string.search_show_histories))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
searchHistory.map {
|
||||||
|
Suggestion(it)
|
||||||
|
}.takeLast(10) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
|
||||||
|
}
|
||||||
|
}.reversed()
|
||||||
|
|
||||||
private var suggestionJob : Job? = null
|
private var suggestionJob : Job? = null
|
||||||
private fun setupSearchBar() {
|
private fun setupSearchBar() {
|
||||||
val searchInputView = findViewById<SearchInputView>(R.id.search_bar_text)
|
with(main_searchview as xyz.quaver.pupil.ui.view.FloatingSearchView) {
|
||||||
//Change upper case letters to lower case
|
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
|
||||||
searchInputView.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(Locale.getDefault()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
|
||||||
|
|
||||||
with(main_searchview as FloatingSearchViewDayNight) {
|
|
||||||
val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json")
|
|
||||||
|
|
||||||
if (!favoritesFile.exists()) {
|
|
||||||
favoritesFile.createNewFile()
|
|
||||||
favoritesFile.writeText("[]")
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
|
|
||||||
override fun onMenuOpened() {
|
override fun onMenuOpened() {
|
||||||
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
||||||
}
|
}
|
||||||
@@ -758,65 +630,45 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onMenuClosed() {
|
override fun onMenuClosed() {
|
||||||
//Do Nothing
|
//Do Nothing
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
post {
|
||||||
when(it.itemId) {
|
findViewById<MenuView>(R.id.menu_view).menuItems.firstOrNull {
|
||||||
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), R.id.request_settings.normalizeID())
|
(it as MenuItem).itemId == R.id.main_menu_thin
|
||||||
R.id.main_menu_thin -> {
|
}?.let {
|
||||||
main_recyclerview.apply {
|
(it as MenuItem).isChecked = Preferences["thin"]
|
||||||
(adapter as GalleryBlockAdapter).apply {
|
|
||||||
isThin = !isThin
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter = adapter // Force to redraw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.main_menu_sort_newest -> {
|
|
||||||
sortMode = SortMode.NEWEST
|
|
||||||
it.isChecked = true
|
|
||||||
|
|
||||||
runOnUiThread {
|
|
||||||
currentPage = 0
|
|
||||||
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.main_menu_sort_popular -> {
|
|
||||||
sortMode = SortMode.POPULAR
|
|
||||||
it.isChecked = true
|
|
||||||
|
|
||||||
runOnUiThread {
|
|
||||||
currentPage = 0
|
|
||||||
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnQueryChangeListener { _, query ->
|
onHistoryDeleteClickedListener = {
|
||||||
|
searchHistory.remove(it)
|
||||||
|
swapSuggestions(defaultSuggestions)
|
||||||
|
}
|
||||||
|
onFavoriteHistorySwitchClickListener = {
|
||||||
|
isFavorite = !isFavorite
|
||||||
|
swapSuggestions(defaultSuggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMenuItemClickListener = {
|
||||||
|
onActionMenuItemSelected(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
onQueryChangeListener = lambda@{ _, query ->
|
||||||
this@MainActivity.query = query
|
this@MainActivity.query = query
|
||||||
|
|
||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
|
|
||||||
clearSuggestions()
|
|
||||||
|
|
||||||
if (query.isEmpty() or query.endsWith(' ')) {
|
if (query.isEmpty() or query.endsWith(' ')) {
|
||||||
swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map {
|
swapSuggestions(defaultSuggestions)
|
||||||
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
|
||||||
})
|
|
||||||
|
|
||||||
return@setOnQueryChangeListener
|
return@lambda
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentQuery = query.split(" ").last().replace('_', ' ')
|
swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString())))
|
||||||
|
|
||||||
|
val currentQuery = query.split(" ").last()
|
||||||
|
.replace(Regex("^-"), "")
|
||||||
|
.replace('_', ' ')
|
||||||
|
|
||||||
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val suggestions = kotlin.runCatching {
|
val suggestions = kotlin.runCatching {
|
||||||
@@ -825,113 +677,22 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
suggestions.filter {
|
suggestions.filter {
|
||||||
val tag = "${it.n}:${it.s.replace(Regex("\\s"), "_")}"
|
val tag = "${it.n}:${it.s.replace(Regex("\\s"), "_")}"
|
||||||
Tags(Json.decodeFromString(favoritesFile.readText())).contains(tag)
|
favoriteTags.contains(Tag.parse(tag))
|
||||||
}.reversed().forEach {
|
}.reversed().forEach {
|
||||||
suggestions.remove(it)
|
suggestions.remove(it)
|
||||||
suggestions.add(0, it)
|
suggestions.add(0, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
swapSuggestions(suggestions)
|
swapSuggestions(if (suggestions.isNotEmpty()) suggestions else listOf(NoResultSuggestion(getText(R.string.main_no_result).toString())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ ->
|
onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener {
|
||||||
item as TagSuggestion
|
|
||||||
|
|
||||||
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
|
|
||||||
|
|
||||||
val color = TypedValue()
|
|
||||||
theme.resolveAttribute(R.attr.colorControlNormal, color, true)
|
|
||||||
|
|
||||||
leftIcon.setImageDrawable(
|
|
||||||
ResourcesCompat.getDrawable(
|
|
||||||
resources,
|
|
||||||
when(item.n) {
|
|
||||||
"female" -> R.drawable.gender_female
|
|
||||||
"male" -> R.drawable.gender_male
|
|
||||||
"language" -> R.drawable.translate
|
|
||||||
"group" -> R.drawable.account_group
|
|
||||||
"character" -> R.drawable.account_star
|
|
||||||
"series" -> R.drawable.book_open
|
|
||||||
"artist" -> R.drawable.brush
|
|
||||||
else -> R.drawable.tag
|
|
||||||
},
|
|
||||||
context.theme)
|
|
||||||
)
|
|
||||||
|
|
||||||
with(suggestionView.findViewById<ImageView>(R.id.right_icon)) {
|
|
||||||
|
|
||||||
if (Tags(Json.decodeFromString(favoritesFile.readText())).contains(tag))
|
|
||||||
setImageResource(R.drawable.ic_star_filled)
|
|
||||||
else
|
|
||||||
setImageResource(R.drawable.ic_star_empty)
|
|
||||||
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
rotation = 0f
|
|
||||||
isEnabled = true
|
|
||||||
|
|
||||||
isClickable = true
|
|
||||||
setOnClickListener {
|
|
||||||
val favorites = Tags(Json.decodeFromString(favoritesFile.readText()))
|
|
||||||
|
|
||||||
if (favorites.contains(tag)) {
|
|
||||||
setImageResource(R.drawable.ic_star_empty)
|
|
||||||
favorites.remove(tag)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setImageDrawable(AnimatedVectorDrawableCompat.create(context,
|
|
||||||
R.drawable.avd_star
|
|
||||||
))
|
|
||||||
(drawable as Animatable).start()
|
|
||||||
|
|
||||||
favorites.add(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
favoritesFile.writeText(Json.encodeToString(favorites.tags))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.t == -1) {
|
|
||||||
textView.text = item.s
|
|
||||||
} else {
|
|
||||||
val text = "${item.s}\n ${item.t}"
|
|
||||||
|
|
||||||
val len = text.length
|
|
||||||
val left = item.s.length
|
|
||||||
|
|
||||||
textView.text = SpannableString(text).apply {
|
|
||||||
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
|
|
||||||
setSpan(s, left, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
setSpan(SetLineOverlap(true), 1, len-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
setSpan(SetLineOverlap(false), len-1, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnSearchListener(object : FloatingSearchView.OnSearchListener {
|
|
||||||
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
|
||||||
if (searchSuggestion !is TagSuggestion)
|
|
||||||
return
|
|
||||||
|
|
||||||
with(searchInputView.text) {
|
|
||||||
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
|
||||||
append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSearchAction(currentQuery: String?) {
|
|
||||||
//Do search on onFocusCleared()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
|
||||||
override fun onFocus() {
|
override fun onFocus() {
|
||||||
if (query.isEmpty() or query.endsWith(' '))
|
if (query.isEmpty() or query.endsWith(' '))
|
||||||
swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map {
|
swapSuggestions(defaultSuggestions)
|
||||||
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFocusCleared() {
|
override fun onFocusCleared() {
|
||||||
@@ -945,12 +706,124 @@ class MainActivity : AppCompatActivity() {
|
|||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
attachNavigationDrawerToMenuButton(main_drawer_layout)
|
attachNavigationDrawerToMenuButton(main_drawer_layout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onActionMenuItemSelected(item: MenuItem?) {
|
||||||
|
when(item?.itemId) {
|
||||||
|
R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
|
||||||
|
R.id.main_menu_thin -> {
|
||||||
|
val thin = !item.isChecked
|
||||||
|
|
||||||
|
item.isChecked = thin
|
||||||
|
main_recyclerview.apply {
|
||||||
|
(adapter as GalleryBlockAdapter).apply {
|
||||||
|
this.thin = thin
|
||||||
|
|
||||||
|
Preferences["thin"] = thin
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = adapter // Force to redraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.main_menu_sort_newest -> {
|
||||||
|
sortMode = SortMode.NEWEST
|
||||||
|
item.isChecked = true
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
currentPage = 0
|
||||||
|
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.main_menu_sort_popular -> {
|
||||||
|
sortMode = SortMode.POPULAR
|
||||||
|
item.isChecked = true
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
currentPage = 0
|
||||||
|
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
||||||
|
runOnUiThread {
|
||||||
|
main_drawer_layout.closeDrawers()
|
||||||
|
|
||||||
|
when(item.itemId) {
|
||||||
|
R.id.main_drawer_home -> {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
currentPage = 0
|
||||||
|
query = ""
|
||||||
|
queryStack.clear()
|
||||||
|
mode = Mode.SEARCH
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
R.id.main_drawer_history -> {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
currentPage = 0
|
||||||
|
query = ""
|
||||||
|
queryStack.clear()
|
||||||
|
mode = Mode.HISTORY
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
R.id.main_drawer_downloads -> {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
currentPage = 0
|
||||||
|
query = ""
|
||||||
|
queryStack.clear()
|
||||||
|
mode = Mode.DOWNLOAD
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
R.id.main_drawer_favorite -> {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
currentPage = 0
|
||||||
|
query = ""
|
||||||
|
queryStack.clear()
|
||||||
|
mode = Mode.FAVORITE
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
R.id.main_drawer_help -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
||||||
|
}
|
||||||
|
R.id.main_drawer_github -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
|
||||||
|
}
|
||||||
|
R.id.main_drawer_homepage -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
|
||||||
|
}
|
||||||
|
R.id.main_drawer_email -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
||||||
|
}
|
||||||
|
R.id.main_drawer_kakaotalk -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun cancelFetch() {
|
private fun cancelFetch() {
|
||||||
galleryIDs?.cancel()
|
galleryIDs?.cancel()
|
||||||
loadingJob?.cancel()
|
loadingJob?.cancel()
|
||||||
@@ -974,6 +847,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private fun fetchGalleries(query: String, sortMode: SortMode) {
|
private fun fetchGalleries(query: String, sortMode: SortMode) {
|
||||||
val defaultQuery: String = Preferences["default_query"]
|
val defaultQuery: String = Preferences["default_query"]
|
||||||
|
|
||||||
|
if (query.isNotBlank())
|
||||||
|
searchHistory.add(query)
|
||||||
|
|
||||||
if (query != queryStack.lastOrNull()) {
|
if (query != queryStack.lastOrNull()) {
|
||||||
queryStack.remove(query)
|
queryStack.remove(query)
|
||||||
queryStack.add(query)
|
queryStack.add(query)
|
||||||
@@ -981,7 +857,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
if (query.isNotEmpty() && mode != Mode.SEARCH) {
|
if (query.isNotEmpty() && mode != Mode.SEARCH) {
|
||||||
Snackbar.make(this@MainActivity.main_recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply {
|
Snackbar.make(this@MainActivity.main_recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply {
|
||||||
setAction(android.R.string.yes) {
|
setAction(android.R.string.ok) {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
currentPage = 0
|
currentPage = 0
|
||||||
@@ -1034,12 +910,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
|
val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
query.isEmpty() -> downloads.also {
|
query.isEmpty() -> downloads.reversed().also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val result = doSearch(query).sorted()
|
val result = doSearch(query).sorted()
|
||||||
downloads.filter { result.binarySearch(it) >= 0 }.also {
|
downloads.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1052,13 +928,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val result = doSearch(query).sorted()
|
val result = doSearch(query).sorted()
|
||||||
favorites.filter { result.binarySearch(it) >= 0 }.also {
|
favorites.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,19 +18,24 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.Toast
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnticipateInterpolator
|
||||||
|
import android.view.animation.OvershootInterpolator
|
||||||
|
import android.view.animation.TranslateAnimation
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.PagerSnapHelper
|
import androidx.recyclerview.widget.PagerSnapHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -38,26 +43,29 @@ import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
|||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import com.google.mlkit.vision.face.Face
|
||||||
|
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||||
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.android.synthetic.main.reader_eye_card.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import xyz.quaver.Code
|
import xyz.quaver.Code
|
||||||
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.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.histories
|
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import xyz.quaver.pupil.util.camera
|
||||||
|
import xyz.quaver.pupil.util.closeCamera
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.util.*
|
import xyz.quaver.pupil.util.startCamera
|
||||||
import kotlin.concurrent.schedule
|
|
||||||
import kotlin.concurrent.timer
|
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : BaseActivity() {
|
||||||
|
|
||||||
private var galleryID = 0
|
private var galleryID = 0
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
@@ -68,18 +76,18 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
field = value
|
field = value
|
||||||
|
|
||||||
(reader_recyclerview.adapter as ReaderAdapter).isFullScreen = value
|
(reader_recyclerview.adapter as ReaderAdapter).isFullScreen = value
|
||||||
|
|
||||||
reader_progressbar.visibility = when {
|
|
||||||
value -> View.VISIBLE
|
|
||||||
else -> View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var cache: Cache
|
private lateinit var cache: Cache
|
||||||
var downloader: DownloadService? = null
|
var downloader: DownloadService? = null
|
||||||
private val conn = object: ServiceConnection {
|
private val conn = object: ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
downloader = (service as DownloadService.Binder).service
|
downloader = (service as DownloadService.Binder).service.also {
|
||||||
|
it.priority = 0
|
||||||
|
|
||||||
|
if (!it.progress.containsKey(galleryID))
|
||||||
|
DownloadService.download(this@ReaderActivity, galleryID, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
@@ -87,13 +95,29 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val timer = Timer()
|
|
||||||
private var autoTimer: Timer? = null
|
|
||||||
|
|
||||||
private val snapHelper = PagerSnapHelper()
|
private val snapHelper = PagerSnapHelper()
|
||||||
|
|
||||||
private var menu: Menu? = null
|
private var menu: Menu? = null
|
||||||
|
|
||||||
|
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
|
if (isGranted)
|
||||||
|
toggleCamera()
|
||||||
|
else
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.camera_denied)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Eye {
|
||||||
|
LEFT,
|
||||||
|
RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cameraEnabled = false
|
||||||
|
private var eyeType: Eye? = null
|
||||||
|
private var eyeTime: Long = 0L
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_reader)
|
setContentView(R.layout.activity_reader)
|
||||||
@@ -101,10 +125,6 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
title = getString(R.string.reader_loading)
|
title = getString(R.string.reader_loading)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
|
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
|
|
||||||
handleIntent(intent)
|
handleIntent(intent)
|
||||||
cache = Cache.getInstance(this, galleryID)
|
cache = Cache.getInstance(this, galleryID)
|
||||||
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
||||||
@@ -113,38 +133,8 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
onBackPressed()
|
onBackPressed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (Preferences["cache_disable"]) {
|
|
||||||
reader_download_progressbar.visibility = View.GONE
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val reader = cache.getReader()
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) initDownloader@{
|
|
||||||
if (reader == null) {
|
|
||||||
Snackbar
|
|
||||||
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
|
||||||
.show()
|
|
||||||
return@initDownloader
|
|
||||||
}
|
|
||||||
|
|
||||||
histories.add(galleryID)
|
|
||||||
(reader_recyclerview.adapter as ReaderAdapter).apply {
|
|
||||||
this.reader = reader
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
title = reader.galleryInfo.title ?: ""
|
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}"
|
|
||||||
|
|
||||||
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
|
|
||||||
when (reader.code) {
|
|
||||||
Code.HITOMI -> R.drawable.hitomi
|
|
||||||
Code.HIYOBI -> R.drawable.ic_hiyobi
|
|
||||||
else -> android.R.color.transparent
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
initDownloader()
|
|
||||||
|
|
||||||
|
initDownloadListener()
|
||||||
initView()
|
initView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,17 +161,6 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
if (Preferences["security_mode"])
|
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
else
|
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
|
|
||||||
super.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
menuInflater.inflate(R.menu.reader, menu)
|
menuInflater.inflate(R.menu.reader, menu)
|
||||||
|
|
||||||
@@ -196,8 +175,8 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when(item?.itemId) {
|
when(item.itemId) {
|
||||||
R.id.reader_menu_page_indicator -> {
|
R.id.reader_menu_page_indicator -> {
|
||||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
|
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
|
||||||
with(view.dialog_number_picker) {
|
with(view.dialog_number_picker) {
|
||||||
@@ -232,17 +211,32 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onResume() {
|
||||||
super.onDestroy()
|
super.onResume()
|
||||||
|
|
||||||
timer.cancel()
|
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
|
||||||
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
|
|
||||||
|
|
||||||
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
|
if (cameraEnabled)
|
||||||
DownloadService.cancel(this, galleryID)
|
startCamera(this, cameraCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
closeCamera()
|
||||||
|
|
||||||
if (downloader != null)
|
if (downloader != null)
|
||||||
unbindService(conn)
|
unbindService(conn)
|
||||||
|
|
||||||
|
downloader?.priority = galleryID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
|
||||||
|
update = false
|
||||||
|
|
||||||
|
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
|
||||||
|
DownloadService.cancel(this, galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -264,7 +258,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
//currentPage is 1-based
|
//currentPage is 1-based
|
||||||
return when(keyCode) {
|
return when(keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage-2, 0)
|
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-2, 0)
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -277,48 +271,51 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initDownloader() {
|
private var update = true
|
||||||
DownloadService.download(this, galleryID, true)
|
private fun initDownloadListener() {
|
||||||
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
while (update) {
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
timer.schedule(1000, 1000) {
|
val downloader = downloader ?: continue
|
||||||
val downloader = downloader ?: return@schedule
|
|
||||||
|
|
||||||
if (downloader.progress.indexOfKey(galleryID) < 0) //loading
|
if (!downloader.progress.containsKey(galleryID)) //loading
|
||||||
return@schedule
|
continue
|
||||||
|
|
||||||
if (downloader.progress[galleryID] == null) { //Gallery not found
|
if (downloader.progress[galleryID]?.isEmpty() == true) { //Gallery not found
|
||||||
timer.cancel()
|
update = false
|
||||||
Snackbar
|
Snackbar
|
||||||
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||||
.show()
|
.show()
|
||||||
}
|
|
||||||
|
|
||||||
histories.add(galleryID)
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
runOnUiThread {
|
|
||||||
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||||
reader_download_progressbar.progress = downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
reader_download_progressbar.progress =
|
||||||
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
||||||
|
|
||||||
if (title == getString(R.string.reader_loading)) {
|
if (title == getString(R.string.reader_loading)) {
|
||||||
val reader = cache.metadata.reader
|
val reader = cache.metadata.reader
|
||||||
|
|
||||||
if (reader != null) {
|
if (reader != null) {
|
||||||
with (reader_recyclerview.adapter as ReaderAdapter) {
|
with(reader_recyclerview.adapter as ReaderAdapter) {
|
||||||
this.reader = reader
|
this.reader = reader
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
title = reader.galleryInfo.title
|
title = reader.galleryInfo.title
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}"
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title =
|
||||||
|
"$currentPage/${reader.galleryInfo.files.size}"
|
||||||
|
|
||||||
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
|
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(
|
||||||
|
this@ReaderActivity,
|
||||||
when (reader.code) {
|
when (reader.code) {
|
||||||
Code.HITOMI -> R.drawable.hitomi
|
Code.HITOMI -> R.drawable.hitomi
|
||||||
Code.HIYOBI -> R.drawable.ic_hiyobi
|
Code.HIYOBI -> R.drawable.ic_hiyobi
|
||||||
else -> android.R.color.transparent
|
else -> android.R.color.transparent
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +339,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
scrollMode(false)
|
scrollMode(false)
|
||||||
fullscreen(true)
|
fullscreen(true)
|
||||||
} else {
|
} else {
|
||||||
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage) //Moves to next page because currentPage is 1-based indexing
|
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage, 0) //Moves to next page because currentPage is 1-based indexing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,7 +359,6 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
return
|
return
|
||||||
currentPage = layoutManager.findFirstVisibleItemPosition()+1
|
currentPage = layoutManager.findFirstVisibleItemPosition()+1
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}"
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}"
|
||||||
this@ReaderActivity.reader_progressbar.progress = currentPage
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -371,18 +367,15 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
|
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
|
||||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
|
||||||
else {
|
|
||||||
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
|
|
||||||
|
|
||||||
if (downloadManager.isDownloading(galleryID)) {
|
if (downloadManager.isDownloading(galleryID)) {
|
||||||
downloadManager.deleteDownloadFolder(galleryID)
|
downloadManager.deleteDownloadFolder(galleryID)
|
||||||
animateDownloadFAB(false)
|
animateDownloadFAB(false)
|
||||||
} else {
|
} else {
|
||||||
downloadManager.addDownloadFolder(galleryID)
|
downloadManager.addDownloadFolder(galleryID)
|
||||||
animateDownloadFAB(true)
|
DownloadService.download(context, galleryID, true)
|
||||||
}
|
animateDownloadFAB(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,31 +383,26 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
with(reader_fab_retry) {
|
with(reader_fab_retry) {
|
||||||
setImageResource(R.drawable.refresh)
|
setImageResource(R.drawable.refresh)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
downloader?.cancel(galleryID)
|
DownloadService.download(context, galleryID)
|
||||||
downloader?.download(galleryID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
with(reader_fab_auto) {
|
with(reader_fab_auto) {
|
||||||
setImageResource(R.drawable.clock_start)
|
setImageResource(R.drawable.eye_white)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
if (autoTimer == null) {
|
when {
|
||||||
autoTimer = timer(initialDelay = 10000L, period = 10000L) {
|
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED -> {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
toggleCamera()
|
||||||
with(this@ReaderActivity.reader_recyclerview) {
|
|
||||||
val lastItem =
|
|
||||||
(layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
|
|
||||||
|
|
||||||
if (lastItem < adapter!!.itemCount - 1)
|
|
||||||
(layoutManager as LinearLayoutManager).scrollToPosition(lastItem + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setImageResource(R.drawable.clock_end)
|
Build.VERSION.SDK_INT >= 23 && shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
|
||||||
} else {
|
AlertDialog.Builder(this@ReaderActivity)
|
||||||
autoTimer?.cancel()
|
.setTitle(R.string.warning)
|
||||||
autoTimer = null
|
.setMessage(R.string.camera_denied)
|
||||||
setImageResource(R.drawable.clock_start)
|
.setPositiveButton(android.R.string.ok) { _, _ ->}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
else ->
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,10 +424,22 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
flags = flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
|
flags = flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||||
supportActionBar?.hide()
|
supportActionBar?.hide()
|
||||||
this@ReaderActivity.reader_fab.visibility = View.INVISIBLE
|
this@ReaderActivity.reader_fab.visibility = View.INVISIBLE
|
||||||
|
this@ReaderActivity.scroller.let {
|
||||||
|
it.handleWidth = resources.getDimensionPixelSize(R.dimen.thumb_height)
|
||||||
|
it.handleHeight = resources.getDimensionPixelSize(R.dimen.thumb_width)
|
||||||
|
it.handleDrawable = ContextCompat.getDrawable(this@ReaderActivity, R.drawable.thumb_horizontal)
|
||||||
|
it.fastScrollDirection = RecyclerViewFastScroller.FastScrollDirection.HORIZONTAL
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
flags = flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
|
flags = flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
|
||||||
supportActionBar?.show()
|
supportActionBar?.show()
|
||||||
this@ReaderActivity.reader_fab.visibility = View.VISIBLE
|
this@ReaderActivity.reader_fab.visibility = View.VISIBLE
|
||||||
|
this@ReaderActivity.scroller.let {
|
||||||
|
it.handleWidth = resources.getDimensionPixelSize(R.dimen.thumb_width)
|
||||||
|
it.handleHeight = resources.getDimensionPixelSize(R.dimen.thumb_height)
|
||||||
|
it.handleDrawable = ContextCompat.getDrawable(this@ReaderActivity, R.drawable.thumb)
|
||||||
|
it.fastScrollDirection = RecyclerViewFastScroller.FastScrollDirection.VERTICAL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.attributes = this
|
window.attributes = this
|
||||||
@@ -454,7 +454,11 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
reader_recyclerview.layoutManager = LinearLayoutManager(this)
|
reader_recyclerview.layoutManager = LinearLayoutManager(this)
|
||||||
} else {
|
} else {
|
||||||
snapHelper.attachToRecyclerView(reader_recyclerview)
|
snapHelper.attachToRecyclerView(reader_recyclerview)
|
||||||
reader_recyclerview.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, Preferences["rtl", false])
|
reader_recyclerview.layoutManager = object: LinearLayoutManager(this, HORIZONTAL, Preferences["rtl", false]) {
|
||||||
|
override fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {
|
||||||
|
extraLayoutSpace.fill(600)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
|
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
|
||||||
@@ -488,4 +492,120 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val cameraCallback: (List<Face>) -> Unit = callback@{ faces ->
|
||||||
|
eye_card.dot.let {
|
||||||
|
it.visibility = View.VISIBLE
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
delay(50)
|
||||||
|
it.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faces.size != 1)
|
||||||
|
ContextCompat.getDrawable(this, R.drawable.eye_off).let {
|
||||||
|
with(eye_card) {
|
||||||
|
left_eye.setImageDrawable(it)
|
||||||
|
right_eye.setImageDrawable(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@callback
|
||||||
|
}
|
||||||
|
|
||||||
|
val (left, right) = Pair(
|
||||||
|
faces[0].rightEyeOpenProbability?.let { it > 0.4 } == true,
|
||||||
|
faces[0].leftEyeOpenProbability?.let { it > 0.4 } == true
|
||||||
|
)
|
||||||
|
|
||||||
|
with(eye_card) {
|
||||||
|
left_eye.setImageDrawable(
|
||||||
|
ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
if (left) R.drawable.eye else R.drawable.eye_closed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
right_eye.setImageDrawable(
|
||||||
|
ContextCompat.getDrawable(
|
||||||
|
context,
|
||||||
|
if (right) R.drawable.eye else R.drawable.eye_closed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
// Both closed / opened
|
||||||
|
!left.xor(right) -> {
|
||||||
|
eyeType = null
|
||||||
|
eyeTime = 0L
|
||||||
|
}
|
||||||
|
!left -> {
|
||||||
|
if (eyeType != Eye.LEFT) {
|
||||||
|
eyeType = Eye.LEFT
|
||||||
|
eyeTime = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
!right -> {
|
||||||
|
if (eyeType != Eye.RIGHT) {
|
||||||
|
eyeType = Eye.RIGHT
|
||||||
|
eyeTime = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eyeType != null && System.currentTimeMillis() - eyeTime > 100) {
|
||||||
|
(this@ReaderActivity.reader_recyclerview.layoutManager as LinearLayoutManager).let {
|
||||||
|
it.scrollToPositionWithOffset(when(eyeType!!) {
|
||||||
|
Eye.RIGHT -> {
|
||||||
|
if (it.reverseLayout) currentPage - 2 else currentPage
|
||||||
|
}
|
||||||
|
Eye.LEFT -> {
|
||||||
|
if (it.reverseLayout) currentPage else currentPage - 2
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
eyeTime = System.currentTimeMillis() + 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleCamera() {
|
||||||
|
val eyes = this@ReaderActivity.eye_card
|
||||||
|
when (camera) {
|
||||||
|
null -> {
|
||||||
|
reader_fab_auto.labelText = getString(R.string.reader_fab_auto_cancel)
|
||||||
|
reader_fab_auto.setImageResource(R.drawable.eye_off_white)
|
||||||
|
eyes.apply {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
TranslateAnimation(0F, 0F, -100F, 0F).apply {
|
||||||
|
duration = 500
|
||||||
|
fillAfter = false
|
||||||
|
interpolator = OvershootInterpolator()
|
||||||
|
}.let { startAnimation(it) }
|
||||||
|
}
|
||||||
|
startCamera(this, cameraCallback)
|
||||||
|
cameraEnabled = true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
reader_fab_auto.labelText = getString(R.string.reader_fab_auto)
|
||||||
|
reader_fab_auto.setImageResource(R.drawable.eye_white)
|
||||||
|
eyes.apply {
|
||||||
|
TranslateAnimation(0F, 0F, 0F, -100F).apply {
|
||||||
|
duration = 500
|
||||||
|
fillAfter = false
|
||||||
|
interpolator = AnticipateInterpolator()
|
||||||
|
setAnimationListener(object: Animation.AnimationListener {
|
||||||
|
override fun onAnimationStart(p0: Animation?) {}
|
||||||
|
override fun onAnimationRepeat(p0: Animation?) {}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(p0: Animation?) {
|
||||||
|
eyes.visibility = View.GONE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}.let { startAnimation(it) }
|
||||||
|
}
|
||||||
|
closeCamera()
|
||||||
|
cameraEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,38 +18,15 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import kotlinx.android.synthetic.main.settings_activity.*
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
|
||||||
import xyz.quaver.io.FileX
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.favorites
|
|
||||||
import xyz.quaver.pupil.ui.fragment.LockSettingsFragment
|
|
||||||
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
||||||
import xyz.quaver.pupil.util.*
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
|
|
||||||
setContentView(R.layout.settings_activity)
|
setContentView(R.layout.settings_activity)
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
@@ -58,78 +35,11 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
if (Preferences["security_mode"])
|
when (item.itemId) {
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
else
|
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
super.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
|
||||||
when (item?.itemId) {
|
|
||||||
android.R.id.home -> onBackPressed()
|
android.R.id.home -> onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
when(requestCode) {
|
|
||||||
R.id.request_lock.normalizeID() -> {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
supportFragmentManager
|
|
||||||
.beginTransaction()
|
|
||||||
.replace(R.id.settings, LockSettingsFragment())
|
|
||||||
.addToBackStack("Lock")
|
|
||||||
.commitAllowingStateLoss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.request_restore.normalizeID() -> {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
val uri = data?.data ?: return
|
|
||||||
|
|
||||||
try {
|
|
||||||
val str = contentResolver.openInputStream(uri).use { inputStream ->
|
|
||||||
inputStream!!
|
|
||||||
|
|
||||||
inputStream.readBytes().toString(Charset.defaultCharset())
|
|
||||||
}
|
|
||||||
|
|
||||||
favorites.addAll(Json.decodeFromString<List<Int>>(str).also {
|
|
||||||
Snackbar.make(
|
|
||||||
window.decorView,
|
|
||||||
getString(R.string.settings_restore_success, it.size),
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
})
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Snackbar.make(
|
|
||||||
window.decorView,
|
|
||||||
R.string.settings_restore_failed,
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
|
||||||
when (requestCode) {
|
|
||||||
R.id.request_write_permission_and_saf.normalizeID() -> {
|
|
||||||
if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
|
||||||
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivityForResult(intent, R.id.request_download_folder.normalizeID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ class DownloadFolderNameDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
private fun build(): View {
|
private fun build(): View {
|
||||||
val galleryID = Cache.instances.let { if (it.size() == 0) 1199708 else it.keyAt((0 until it.size()).random()) }
|
val galleryID = Cache.instances.let { if (it.size == 0) 1199708 else it.keys.elementAt((0 until it.size).random()) }
|
||||||
val galleryBlock = runBlocking {
|
val galleryBlock = runBlocking {
|
||||||
Cache.getInstance(requireContext(), galleryID).getGalleryBlock()
|
Cache.getInstance(requireContext(), galleryID).getGalleryBlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,26 +26,87 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.android.synthetic.main.dialog_download_folder_name.view.*
|
||||||
import kotlinx.android.synthetic.main.item_download_folder.view.*
|
import kotlinx.android.synthetic.main.item_download_folder.view.*
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
|
import xyz.quaver.io.util.toFile
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.byteToString
|
import xyz.quaver.pupil.util.byteToString
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.migrate
|
import xyz.quaver.pupil.util.migrate
|
||||||
import xyz.quaver.pupil.util.normalizeID
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class DownloadLocationDialogFragment : DialogFragment() {
|
class DownloadLocationDialogFragment : DialogFragment() {
|
||||||
private val entries = mutableMapOf<File?, View>()
|
private val entries = mutableMapOf<File?, View>()
|
||||||
|
|
||||||
|
private val requestDownloadFolderLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
|
val activity = activity ?: return@registerForActivityResult
|
||||||
|
val context = context ?: return@registerForActivityResult
|
||||||
|
val dialog = dialog ?: return@registerForActivityResult
|
||||||
|
|
||||||
|
it.data?.data?.also { uri ->
|
||||||
|
val takeFlags: Int =
|
||||||
|
activity.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)
|
||||||
|
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
|
|
||||||
|
if (kotlin.runCatching { FileX(context, uri).canWrite() }.getOrDefault(false)) {
|
||||||
|
entries[null]?.location_available?.text = uri.toFile(context)?.canonicalPath
|
||||||
|
Preferences["download_folder"] = uri.toString()
|
||||||
|
} else {
|
||||||
|
Snackbar.make(
|
||||||
|
dialog.window!!.decorView.rootView,
|
||||||
|
R.string.settings_download_folder_not_writable,
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
|
||||||
|
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
|
||||||
|
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
|
||||||
|
entries[key]!!.button.isChecked = true
|
||||||
|
if (key == null) entries[key]!!.location_available.text = downloadFolder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requestDownloadFolderOldLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
val context = context ?: return@registerForActivityResult
|
||||||
|
val dialog = dialog ?: return@registerForActivityResult
|
||||||
|
|
||||||
|
if (it.resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
|
||||||
|
val directory = it.data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
|
||||||
|
|
||||||
|
if (!File(directory).canWrite()) {
|
||||||
|
Snackbar.make(
|
||||||
|
dialog.window!!.decorView.rootView,
|
||||||
|
R.string.settings_download_folder_not_writable,
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
|
||||||
|
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
|
||||||
|
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
|
||||||
|
entries[key]!!.button.isChecked = true
|
||||||
|
if (key == null) entries[key]!!.location_available.text = downloadFolder
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entries[null]?.location_available?.text = directory
|
||||||
|
Preferences["download_folder"] = File(directory).toURI().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
private fun build() : View? {
|
private fun build() : View? {
|
||||||
val context = context ?: return null
|
val context = context ?: return null
|
||||||
@@ -90,7 +151,7 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
startActivityForResult(intent, R.id.request_download_folder.normalizeID())
|
requestDownloadFolderLauncher.launch(intent)
|
||||||
} else { // Can't use SAF on old Androids!
|
} else { // Can't use SAF on old Androids!
|
||||||
val config = DirectoryChooserConfig.builder()
|
val config = DirectoryChooserConfig.builder()
|
||||||
.newDirectoryName("Pupil")
|
.newDirectoryName("Pupil")
|
||||||
@@ -101,7 +162,7 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
|
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
startActivityForResult(intent, R.id.request_download_folder_old.normalizeID())
|
requestDownloadFolderOldLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entries[null] = this
|
entries[null] = this
|
||||||
@@ -122,6 +183,9 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
.setTitle(R.string.settings_download_folder)
|
.setTitle(R.string.settings_download_folder)
|
||||||
.setView(build())
|
.setView(build())
|
||||||
.setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ ->
|
.setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ ->
|
||||||
|
if (Preferences["download_folder", ""].isEmpty())
|
||||||
|
Preferences["download_folder"] = context?.getExternalFilesDir(null)?.toUri()?.toString() ?: ""
|
||||||
|
|
||||||
DownloadManager.getInstance(requireContext()).migrate()
|
DownloadManager.getInstance(requireContext()).migrate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,65 +193,4 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
when (requestCode) {
|
|
||||||
R.id.request_download_folder.normalizeID() -> {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
val activity = activity ?: return
|
|
||||||
val context = context ?: return
|
|
||||||
val dialog = dialog ?: return
|
|
||||||
|
|
||||||
data?.data?.also { uri ->
|
|
||||||
val takeFlags: Int =
|
|
||||||
activity.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)
|
|
||||||
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
|
||||||
|
|
||||||
if (kotlin.runCatching { FileX(context, uri).canWrite() }.getOrDefault(false))
|
|
||||||
Preferences["download_folder"] = uri.toString()
|
|
||||||
else {
|
|
||||||
Snackbar.make(
|
|
||||||
dialog.window!!.decorView.rootView,
|
|
||||||
R.string.settings_download_folder_not_writable,
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
|
|
||||||
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
|
|
||||||
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
|
|
||||||
entries[key]!!.button.isChecked = true
|
|
||||||
if (key == null) entries[key]!!.location_available.text = downloadFolder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.request_download_folder_old.normalizeID() -> {
|
|
||||||
val context = context ?: return
|
|
||||||
val dialog = dialog ?: return
|
|
||||||
|
|
||||||
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
|
|
||||||
val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
|
|
||||||
|
|
||||||
if (!File(directory).canWrite()) {
|
|
||||||
Snackbar.make(
|
|
||||||
dialog.window!!.decorView.rootView,
|
|
||||||
R.string.settings_download_folder_not_writable,
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
|
|
||||||
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
|
|
||||||
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
|
|
||||||
entries[key]!!.button.isChecked = true
|
|
||||||
if (key == null) entries[key]!!.location_available.text = downloadFolder
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Preferences["download_folder"] = File(directory).canonicalPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -18,18 +18,19 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.dialog
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout.LayoutParams
|
import android.widget.LinearLayout.LayoutParams
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.android.synthetic.main.dialog_gallery.*
|
import kotlinx.android.synthetic.main.dialog_gallery.*
|
||||||
import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
|
import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
|
||||||
@@ -41,10 +42,10 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import xyz.quaver.hitomi.Gallery
|
import xyz.quaver.hitomi.Gallery
|
||||||
import xyz.quaver.hitomi.getGallery
|
import xyz.quaver.hitomi.getGallery
|
||||||
import xyz.quaver.pupil.BuildConfig
|
|
||||||
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.adapters.ThumbnailPageAdapter
|
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
import xyz.quaver.pupil.histories
|
import xyz.quaver.pupil.histories
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
@@ -52,8 +53,10 @@ import xyz.quaver.pupil.ui.view.TagChip
|
|||||||
import xyz.quaver.pupil.util.ItemClickSupport
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) {
|
class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(context) {
|
||||||
|
|
||||||
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
||||||
|
|
||||||
@@ -74,7 +77,6 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||||
putExtra("galleryID", galleryID)
|
putExtra("galleryID", galleryID)
|
||||||
})
|
})
|
||||||
histories.add(galleryID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,19 +106,19 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glide
|
gallery_cover.showImage(Uri.parse(gallery.cover))
|
||||||
.load(gallery.cover)
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
}.into(gallery_cover)
|
|
||||||
|
|
||||||
addDetails(gallery)
|
addDetails(gallery)
|
||||||
addThumbnails(gallery)
|
addThumbnails(gallery)
|
||||||
addRelated(gallery)
|
addRelated(gallery)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show()
|
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).apply {
|
||||||
|
if (Locale.getDefault().language == "ko")
|
||||||
|
setAction(context.getText(R.string.https_text)) {
|
||||||
|
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.https))))
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +143,18 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
listOf(gallery.language).map { Tag("language", it) },
|
listOf(gallery.language).map { Tag("language", it) },
|
||||||
gallery.series.map { Tag("series", it) },
|
gallery.series.map { Tag("series", it) },
|
||||||
gallery.characters.map { Tag("character", it) },
|
gallery.characters.map { Tag("character", it) },
|
||||||
gallery.tags.map {
|
gallery.tags.sortedBy {
|
||||||
|
val tag = Tag.parse(it)
|
||||||
|
|
||||||
|
if (favoriteTags.contains(tag))
|
||||||
|
-1
|
||||||
|
else
|
||||||
|
when(Tag.parse(it).area) {
|
||||||
|
"female" -> 0
|
||||||
|
"male" -> 1
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
Tag.parse(it).let { tag ->
|
Tag.parse(it).let { tag ->
|
||||||
when {
|
when {
|
||||||
tag.area != null -> tag
|
tag.area != null -> tag
|
||||||
@@ -183,7 +196,8 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
gallery_details.setText(R.string.gallery_thumbnails)
|
gallery_details.setText(R.string.gallery_thumbnails)
|
||||||
|
|
||||||
val pager = ViewPager2(context).apply {
|
val pager = ViewPager2(context).apply {
|
||||||
adapter = ThumbnailPageAdapter(glide, gallery.thumbnails)
|
adapter = ThumbnailPageAdapter(gallery.thumbnails)
|
||||||
|
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
gallery_details_contents.addView(
|
gallery_details_contents.addView(
|
||||||
@@ -203,7 +217,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
val galleries = ArrayList<Int>()
|
val galleries = ArrayList<Int>()
|
||||||
|
|
||||||
val adapter = GalleryBlockAdapter(glide, galleries).apply {
|
val adapter = GalleryBlockAdapter(galleries).apply {
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||||
handler.invoke(tag)
|
handler.invoke(tag)
|
||||||
@@ -223,14 +237,9 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||||
putExtra("galleryID", galleries[position])
|
putExtra("galleryID", galleries[position])
|
||||||
})
|
})
|
||||||
histories.add(galleries[position])
|
|
||||||
}
|
}
|
||||||
onItemLongClickListener = { _, position, _ ->
|
onItemLongClickListener = { _, position, _ ->
|
||||||
GalleryDialog(
|
GalleryDialog(context, galleries[position]).apply {
|
||||||
context,
|
|
||||||
glide,
|
|
||||||
galleries[position]
|
|
||||||
).apply {
|
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class MirrorDialog(context: Context) : AlertDialog(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onItemMoved = {
|
onItemMoved = {
|
||||||
Preferences["mirrors", it.joinToString(">")]
|
Preferences["mirrors"] = it.joinToString(">")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import kotlinx.android.synthetic.main.dialog_proxy.view.*
|
import kotlinx.android.synthetic.main.dialog_proxy.view.*
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -40,11 +41,10 @@ import xyz.quaver.pupil.util.getProxyInfo
|
|||||||
import xyz.quaver.pupil.util.proxyInfo
|
import xyz.quaver.pupil.util.proxyInfo
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
|
||||||
class ProxyDialog(context: Context) : Dialog(context) {
|
class ProxyDialog(context: Context) : AlertDialog(context) {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setContentView(build())
|
setView(build())
|
||||||
window?.attributes?.width = ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,11 +74,11 @@ class LockSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_lock_remove_message)
|
setMessage(R.string.settings_lock_remove_message)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
lockManager.remove(Lock.Type.PATTERN)
|
lockManager.remove(Lock.Type.PATTERN)
|
||||||
onResume()
|
onResume()
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
} else {
|
} else {
|
||||||
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
@@ -107,11 +107,11 @@ class LockSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_lock_remove_message)
|
setMessage(R.string.settings_lock_remove_message)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
lockManager.remove(Lock.Type.PIN)
|
lockManager.remove(Lock.Type.PIN)
|
||||||
onResume()
|
onResume()
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
} else {
|
} else {
|
||||||
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (response.code() != 200) {
|
||||||
|
response.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Intent(Intent.ACTION_SEND).apply {
|
Intent(Intent.ACTION_SEND).apply {
|
||||||
type = "text/plain"
|
type = "text/plain"
|
||||||
putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", ""))
|
putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", ""))
|
||||||
@@ -82,7 +87,7 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
|||||||
.setTitle(R.string.settings_restore_title)
|
.setTitle(R.string.settings_restore_title)
|
||||||
.setView(editText)
|
.setView(editText)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
restore(favorites, editText.text.toString(),
|
restore(editText.text.toString(),
|
||||||
onFailure = onFailure@{
|
onFailure = onFailure@{
|
||||||
val view = view ?: return@onFailure
|
val view = view ?: return@onFailure
|
||||||
Snackbar.make(view, R.string.settings_restore_failed, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(view, R.string.settings_restore_failed, Snackbar.LENGTH_LONG).show()
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import xyz.quaver.io.util.deleteRecursively
|
|||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.histories
|
import xyz.quaver.pupil.histories
|
||||||
import xyz.quaver.pupil.util.byteToString
|
import xyz.quaver.pupil.util.byteToString
|
||||||
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -57,10 +58,12 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_cache_alert_message)
|
setMessage(R.string.settings_clear_cache_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
if (dir.exists())
|
if (dir.exists())
|
||||||
dir.deleteRecursively()
|
dir.deleteRecursively()
|
||||||
|
|
||||||
|
Cache.instances.clear()
|
||||||
|
|
||||||
summary = context.getString(R.string.settings_storage_usage, byteToString(0))
|
summary = context.getString(R.string.settings_storage_usage, byteToString(0))
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
var size = 0L
|
var size = 0L
|
||||||
@@ -74,7 +77,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"delete_downloads" -> {
|
"delete_downloads" -> {
|
||||||
@@ -83,7 +86,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_downloads_alert_message)
|
setMessage(R.string.settings_clear_downloads_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
@@ -109,18 +112,18 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"clear_history" -> {
|
"clear_history" -> {
|
||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_history_alert_message)
|
setMessage(R.string.settings_clear_history_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
histories.clear()
|
histories.clear()
|
||||||
summary = context.getString(R.string.settings_clear_history_summary, histories.size)
|
summary = context.getString(R.string.settings_clear_history_summary, histories.size)
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
else -> return false
|
else -> return false
|
||||||
|
|||||||
@@ -18,14 +18,17 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.fragment
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.preference.Preference
|
import androidx.preference.*
|
||||||
import androidx.preference.PreferenceCategory
|
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
@@ -34,6 +37,7 @@ import xyz.quaver.pupil.ui.SettingsActivity
|
|||||||
import xyz.quaver.pupil.ui.dialog.*
|
import xyz.quaver.pupil.ui.dialog.*
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class SettingsFragment :
|
class SettingsFragment :
|
||||||
PreferenceFragmentCompat(),
|
PreferenceFragmentCompat(),
|
||||||
@@ -41,6 +45,16 @@ class SettingsFragment :
|
|||||||
Preference.OnPreferenceChangeListener,
|
Preference.OnPreferenceChangeListener,
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private val lockLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
|
parentFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.settings, LockSettingsFragment())
|
||||||
|
.addToBackStack("Lock")
|
||||||
|
.commitAllowingStateLoss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
@@ -68,7 +82,7 @@ class SettingsFragment :
|
|||||||
checkUpdate(activity as SettingsActivity, true)
|
checkUpdate(activity as SettingsActivity, true)
|
||||||
}
|
}
|
||||||
"download_folder" -> {
|
"download_folder" -> {
|
||||||
DownloadLocationDialogFragment().show(requireActivity().supportFragmentManager, "Download Location Dialog")
|
DownloadLocationDialogFragment().show(parentFragmentManager, "Download Location Dialog")
|
||||||
}
|
}
|
||||||
"default_query" -> {
|
"default_query" -> {
|
||||||
DefaultQueryDialog(requireContext()).apply {
|
DefaultQueryDialog(requireContext()).apply {
|
||||||
@@ -79,8 +93,10 @@ class SettingsFragment :
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"app_lock" -> {
|
"app_lock" -> {
|
||||||
val intent = Intent(requireContext(), LockActivity::class.java)
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
activity?.startActivityForResult(intent, R.id.request_lock.normalizeID())
|
putExtra("force", true)
|
||||||
|
}
|
||||||
|
lockLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
"mirrors" -> {
|
"mirrors" -> {
|
||||||
MirrorDialog(requireContext())
|
MirrorDialog(requireContext())
|
||||||
@@ -94,7 +110,7 @@ class SettingsFragment :
|
|||||||
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
|
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
|
||||||
ClipData.newPlainText("user_id", Preferences.get<String>("user_id"))
|
ClipData.newPlainText("user_id", Preferences.get<String>("user_id"))
|
||||||
)
|
)
|
||||||
Toast.makeText(context, R.string.settings_user_id_toast, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
@@ -108,6 +124,9 @@ class SettingsFragment :
|
|||||||
this ?: return false
|
this ?: return false
|
||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
|
"tag_translation" -> {
|
||||||
|
updateTranslations()
|
||||||
|
}
|
||||||
"nomedia" -> {
|
"nomedia" -> {
|
||||||
val create = (newValue as? Boolean) ?: return false
|
val create = (newValue as? Boolean) ?: return false
|
||||||
|
|
||||||
@@ -228,6 +247,27 @@ class SettingsFragment :
|
|||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
|
"tag_translation" -> {
|
||||||
|
this as ListPreference
|
||||||
|
|
||||||
|
isEnabled = false
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
kotlin.runCatching {
|
||||||
|
val languages = getAvailableLanguages().distinct().toTypedArray()
|
||||||
|
|
||||||
|
entries = languages.map { Locale(it).let { loc -> loc.getDisplayLanguage(loc) } }.toTypedArray()
|
||||||
|
entryValues = languages
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPreferenceChangeListener = this@SettingsFragment
|
||||||
|
|
||||||
|
}
|
||||||
"mirrors" -> {
|
"mirrors" -> {
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
@@ -246,6 +286,12 @@ class SettingsFragment :
|
|||||||
summary = Preferences.get<String>("user_id")
|
summary = Preferences.get<String>("user_id")
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
|
"oss" -> {
|
||||||
|
setOnPreferenceClickListener {
|
||||||
|
context?.startActivity(Intent(context, OssLicensesMenuActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
218
app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt
Normal file
218
app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* 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.view
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffColorFilter
|
||||||
|
import android.graphics.drawable.Animatable
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
|
import xyz.quaver.floatingsearchview.FloatingSearchView
|
||||||
|
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
|
import xyz.quaver.floatingsearchview.util.MenuPopupHelper
|
||||||
|
import xyz.quaver.floatingsearchview.util.view.MenuView
|
||||||
|
import xyz.quaver.floatingsearchview.util.view.SearchInputView
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
|
import xyz.quaver.pupil.types.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
|
FloatingSearchView(context, attrs),
|
||||||
|
FloatingSearchView.OnSearchListener,
|
||||||
|
TextWatcher
|
||||||
|
{
|
||||||
|
private val searchInputView = findViewById<SearchInputView>(R.id.search_bar_text)
|
||||||
|
|
||||||
|
var onHistoryDeleteClickedListener: ((String) -> Unit)? = null
|
||||||
|
var onFavoriteHistorySwitchClickListener: (() -> Unit)? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI or searchInputView.imeOptions
|
||||||
|
|
||||||
|
searchInputView.addTextChangedListener(this)
|
||||||
|
onSearchListener = this
|
||||||
|
onBindSuggestionCallback = { a, b, c, d, e ->
|
||||||
|
onBindSuggestion(a, b, c, d, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(Locale.getDefault()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
||||||
|
when (searchSuggestion) {
|
||||||
|
is TagSuggestion -> {
|
||||||
|
val tag = "${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")}"
|
||||||
|
with(searchInputView.text!!) {
|
||||||
|
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ') + 1, length)
|
||||||
|
|
||||||
|
if (!this.contains(tag))
|
||||||
|
append("$tag ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Suggestion -> {
|
||||||
|
with(searchInputView.text!!) {
|
||||||
|
clear()
|
||||||
|
append(searchSuggestion.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FavoriteHistorySwitch -> onFavoriteHistorySwitchClickListener?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSearchAction(currentQuery: String?) {}
|
||||||
|
|
||||||
|
fun onBindSuggestion(
|
||||||
|
suggestionView: View?,
|
||||||
|
leftIcon: ImageView?,
|
||||||
|
textView: TextView?,
|
||||||
|
item: SearchSuggestion?,
|
||||||
|
itemPosition: Int
|
||||||
|
) {
|
||||||
|
when(item) {
|
||||||
|
is TagSuggestion -> {
|
||||||
|
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
|
||||||
|
|
||||||
|
leftIcon?.setImageDrawable(
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
resources,
|
||||||
|
when(item.n) {
|
||||||
|
"female" -> R.drawable.gender_female
|
||||||
|
"male" -> R.drawable.gender_male
|
||||||
|
"language" -> R.drawable.translate
|
||||||
|
"group" -> R.drawable.account_group
|
||||||
|
"character" -> R.drawable.account_star
|
||||||
|
"series" -> R.drawable.book_open
|
||||||
|
"artist" -> R.drawable.brush
|
||||||
|
else -> R.drawable.tag
|
||||||
|
},
|
||||||
|
context.theme)
|
||||||
|
)
|
||||||
|
|
||||||
|
with(suggestionView?.findViewById<ImageView>(R.id.right_icon)) {
|
||||||
|
this ?: return@with
|
||||||
|
|
||||||
|
if (favoriteTags.contains(Tag.parse(tag)))
|
||||||
|
setImageResource(R.drawable.ic_star_filled)
|
||||||
|
else
|
||||||
|
setImageResource(R.drawable.ic_star_empty)
|
||||||
|
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
rotation = 0f
|
||||||
|
|
||||||
|
isEnabled = true
|
||||||
|
isClickable = true
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
val tag = Tag.parse(tag)
|
||||||
|
|
||||||
|
if (favoriteTags.contains(tag)) {
|
||||||
|
setImageResource(R.drawable.ic_star_empty)
|
||||||
|
favoriteTags.remove(tag)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setImageDrawable(
|
||||||
|
AnimatedVectorDrawableCompat.create(context,
|
||||||
|
R.drawable.avd_star
|
||||||
|
))
|
||||||
|
(drawable as Animatable).start()
|
||||||
|
|
||||||
|
favoriteTags.add(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.t > 0) {
|
||||||
|
(suggestionView as? LinearLayout)?.let {
|
||||||
|
val count = it.findViewById<TextView>(R.id.count)
|
||||||
|
if (count == null)
|
||||||
|
it.addView(
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.suggestion_count, suggestionView, false)
|
||||||
|
.apply {
|
||||||
|
this as TextView
|
||||||
|
|
||||||
|
text = item.t.toString()
|
||||||
|
}, 2
|
||||||
|
)
|
||||||
|
else
|
||||||
|
count.text = item.t.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FavoriteHistorySwitch -> {
|
||||||
|
leftIcon?.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.swap_horizontal, context.theme))
|
||||||
|
}
|
||||||
|
is Suggestion -> {
|
||||||
|
leftIcon?.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.history, context.theme))
|
||||||
|
|
||||||
|
with(suggestionView?.findViewById<ImageView>(R.id.right_icon)) {
|
||||||
|
this ?: return@with
|
||||||
|
|
||||||
|
setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.delete, context.theme))
|
||||||
|
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
rotation = 0f
|
||||||
|
|
||||||
|
isEnabled = true
|
||||||
|
isClickable = true
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
onHistoryDeleteClickedListener?.invoke(item.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is LoadingSuggestion -> {
|
||||||
|
leftIcon?.setImageDrawable(CircularProgressDrawable(context).also {
|
||||||
|
it.setStyle(CircularProgressDrawable.DEFAULT)
|
||||||
|
it.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(context, R.color.colorAccent), PorterDuff.Mode.SRC_IN)
|
||||||
|
it.start()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
is NoResultSuggestion -> {
|
||||||
|
leftIcon?.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.close, context.theme))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,14 +18,26 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.view
|
package xyz.quaver.pupil.ui.view
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
|
import xyz.quaver.pupil.util.translations
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
|
||||||
class TagChip(context: Context, val tag: Tag) : Chip(context) {
|
@SuppressLint("ViewConstructor")
|
||||||
|
class TagChip(context: Context, _tag: Tag) : Chip(context) {
|
||||||
|
|
||||||
|
val tag: Tag =
|
||||||
|
_tag.let {
|
||||||
|
when {
|
||||||
|
it.area != null -> it
|
||||||
|
else -> Tag("tag", _tag.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val languages = context.resources.getStringArray(R.array.languages).map {
|
private val languages = context.resources.getStringArray(R.array.languages).map {
|
||||||
it.split("|").let { split ->
|
it.split("|").let { split ->
|
||||||
@@ -34,30 +46,52 @@ class TagChip(context: Context, val tag: Tag) : Chip(context) {
|
|||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val tag = tag.let {
|
when(tag.area) {
|
||||||
when {
|
|
||||||
it.area != null -> it
|
|
||||||
else -> Tag("tag", tag.tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chipIcon = when(tag.area) {
|
|
||||||
"male" -> {
|
"male" -> {
|
||||||
setChipBackgroundColorResource(R.color.material_blue_700)
|
setChipBackgroundColorResource(R.color.material_blue_700)
|
||||||
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||||
ContextCompat.getDrawable(context, R.drawable.gender_male_white)
|
setCloseIconTintResource(android.R.color.white)
|
||||||
|
chipIcon = ContextCompat.getDrawable(context, R.drawable.gender_male_white)
|
||||||
}
|
}
|
||||||
"female" -> {
|
"female" -> {
|
||||||
setChipBackgroundColorResource(R.color.material_pink_600)
|
setChipBackgroundColorResource(R.color.material_pink_600)
|
||||||
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||||
ContextCompat.getDrawable(context, R.drawable.gender_female_white)
|
setCloseIconTintResource(android.R.color.white)
|
||||||
|
chipIcon = ContextCompat.getDrawable(context, R.drawable.gender_female_white)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (favoriteTags.contains(tag))
|
||||||
|
setChipBackgroundColorResource(R.color.material_orange_500)
|
||||||
|
|
||||||
|
isCloseIconVisible = true
|
||||||
|
closeIcon = ContextCompat.getDrawable(context,
|
||||||
|
if (favoriteTags.contains(tag))
|
||||||
|
R.drawable.ic_star_filled
|
||||||
|
else
|
||||||
|
R.drawable.ic_star_empty
|
||||||
|
)
|
||||||
|
|
||||||
|
setOnCloseIconClickListener {
|
||||||
|
if (favoriteTags.contains(tag)) {
|
||||||
|
favoriteTags.remove(tag)
|
||||||
|
closeIcon = ContextCompat.getDrawable(context, R.drawable.ic_star_empty)
|
||||||
|
|
||||||
|
when(tag.area) {
|
||||||
|
"male" -> setChipBackgroundColorResource(R.color.material_blue_700)
|
||||||
|
"female" -> setChipBackgroundColorResource(R.color.material_pink_600)
|
||||||
|
else -> chipBackgroundColor = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
favoriteTags.add(tag)
|
||||||
|
closeIcon = ContextCompat.getDrawable(context, R.drawable.ic_star_filled)
|
||||||
|
setChipBackgroundColorResource(R.color.material_orange_500)
|
||||||
}
|
}
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text = when (tag.area) {
|
text = when (tag.area) {
|
||||||
"language" -> languages[tag.tag]
|
"language" -> languages[tag.tag]
|
||||||
else -> tag.tag.wordCapitalize()
|
else -> (translations[tag.tag] ?: tag.tag).wordCapitalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnsureMinTouchTargetSize(false)
|
setEnsureMinTouchTargetSize(false)
|
||||||
|
|||||||
100
app/src/main/java/xyz/quaver/pupil/ui/view/TagChipGroup.kt
Normal file
100
app/src/main/java/xyz/quaver/pupil/ui/view/TagChipGroup.kt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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.view
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.TypedArray
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.types.Tag
|
||||||
|
import xyz.quaver.pupil.types.Tags
|
||||||
|
|
||||||
|
class TagChipGroup @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, attrStyle: Int = R.attr.chipGroupStyle, val tags: Tags = Tags()) : ChipGroup(context, attr, attrStyle), MutableSet<Tag> by tags {
|
||||||
|
|
||||||
|
object Defaults {
|
||||||
|
val maxChipSize = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxChipSize: Int = Defaults.maxChipSize
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val moreView = Chip(context).apply {
|
||||||
|
text = "…"
|
||||||
|
|
||||||
|
setEnsureMinTouchTargetSize(false)
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
removeView(this)
|
||||||
|
|
||||||
|
for (i in maxChipSize until tags.size) {
|
||||||
|
val tag = tags.elementAt(i)
|
||||||
|
|
||||||
|
addView(TagChip(context, tag).apply {
|
||||||
|
setOnClickListener {
|
||||||
|
onClickListener?.invoke(tag)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var onClickListener: ((Tag) -> Unit)? = null
|
||||||
|
|
||||||
|
private fun applyAttributes(attr: TypedArray) {
|
||||||
|
maxChipSize = attr.getInt(R.styleable.TagChipGroup_maxTag, Defaults.maxChipSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var refreshJob: Job? = null
|
||||||
|
fun refresh() {
|
||||||
|
refreshJob?.cancel()
|
||||||
|
this.removeAllViews()
|
||||||
|
|
||||||
|
refreshJob = CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
tags.take(maxChipSize).map {
|
||||||
|
CoroutineScope(Dispatchers.Default).async {
|
||||||
|
TagChip(context, it).apply {
|
||||||
|
setOnClickListener {
|
||||||
|
onClickListener?.invoke(this.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.forEach {
|
||||||
|
addView(it.await())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxChipSize > 0 && tags.size > maxChipSize && parent == null)
|
||||||
|
addView(moreView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
applyAttributes(context.obtainStyledAttributes(attr, R.styleable.TagChipGroup))
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class GalleryList(private val file: File, private val list: MutableSet<Int> = mutableSetOf()) : MutableSet<Int> by list {
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (!file.exists()) {
|
|
||||||
file.parentFile?.mkdirs()
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
load()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun load() {
|
|
||||||
synchronized(this) {
|
|
||||||
list.clear()
|
|
||||||
list.addAll(
|
|
||||||
Json.decodeFromString<List<Int>>(file.bufferedReader().use { it.readText() })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun save() {
|
|
||||||
synchronized(this) {
|
|
||||||
file.writeText(Json.encodeToString(list))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun add(element: Int): Boolean {
|
|
||||||
load()
|
|
||||||
|
|
||||||
return list.add(element).also {
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addAll(elements: Collection<Int>): Boolean {
|
|
||||||
load()
|
|
||||||
|
|
||||||
return list.addAll(elements).also {
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun remove(element: Int): Boolean {
|
|
||||||
load()
|
|
||||||
|
|
||||||
return list.remove(element).also {
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun clear() {
|
|
||||||
list.clear()
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
95
app/src/main/java/xyz/quaver/pupil/util/SavedSet.kt
Normal file
95
app/src/main/java/xyz/quaver/pupil/util/SavedSet.kt
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class SavedSet <T: Any> (private val file: File, private val any: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> by set {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
val serializer: KSerializer<List<T>>
|
||||||
|
get() = ListSerializer(serializer(any::class.java) as KSerializer<T>)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.parentFile?.mkdirs()
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun load() {
|
||||||
|
set.clear()
|
||||||
|
kotlin.runCatching {
|
||||||
|
Json.decodeFromString(serializer, file.readText())
|
||||||
|
}.onSuccess {
|
||||||
|
set.addAll(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
fun save() {
|
||||||
|
file.writeText(Json.encodeToString(serializer, set.toList()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun add(element: T): Boolean {
|
||||||
|
load()
|
||||||
|
|
||||||
|
set.remove(element)
|
||||||
|
|
||||||
|
return set.add(element).also {
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun addAll(elements: Collection<T>): Boolean {
|
||||||
|
load()
|
||||||
|
|
||||||
|
set.removeAll(elements)
|
||||||
|
|
||||||
|
return set.addAll(elements).also {
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun remove(element: T): Boolean {
|
||||||
|
load()
|
||||||
|
|
||||||
|
return set.remove(element).also {
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun clear() {
|
||||||
|
set.clear()
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package xyz.quaver.pupil.util
|
|
||||||
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.text.style.LineHeightSpan
|
|
||||||
|
|
||||||
class SetLineOverlap(private val overlap: Boolean) : LineHeightSpan {
|
|
||||||
companion object {
|
|
||||||
private var originalBottom = 15
|
|
||||||
private var originalDescent = 13
|
|
||||||
private var overlapSaved = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chooseHeight(
|
|
||||||
text: CharSequence?,
|
|
||||||
start: Int,
|
|
||||||
end: Int,
|
|
||||||
spanstartv: Int,
|
|
||||||
lineHeight: Int,
|
|
||||||
fm: Paint.FontMetricsInt?
|
|
||||||
) {
|
|
||||||
fm ?: return
|
|
||||||
|
|
||||||
if (overlap) {
|
|
||||||
if (overlapSaved) {
|
|
||||||
originalBottom = fm.bottom
|
|
||||||
originalDescent = fm.descent
|
|
||||||
overlapSaved = true
|
|
||||||
}
|
|
||||||
fm.bottom += fm.top
|
|
||||||
fm.descent += fm.top
|
|
||||||
} else {
|
|
||||||
fm.bottom = originalBottom
|
|
||||||
fm.descent = originalDescent
|
|
||||||
overlapSaved = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
119
app/src/main/java/xyz/quaver/pupil/util/camera.kt
Normal file
119
app/src/main/java/xyz/quaver/pupil/util/camera.kt
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("DEPRECATION", "Recycle")
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.ImageFormat
|
||||||
|
import android.graphics.SurfaceTexture
|
||||||
|
import android.hardware.Camera
|
||||||
|
import android.view.Surface
|
||||||
|
import android.view.WindowManager
|
||||||
|
import com.google.android.gms.tasks.Task
|
||||||
|
import com.google.mlkit.vision.common.InputImage
|
||||||
|
import com.google.mlkit.vision.face.Face
|
||||||
|
import com.google.mlkit.vision.face.FaceDetection
|
||||||
|
import com.google.mlkit.vision.face.FaceDetectorOptions
|
||||||
|
|
||||||
|
/** Check if this device has a camera */
|
||||||
|
private fun Context.checkCameraHardware() =
|
||||||
|
this.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||||
|
|
||||||
|
private fun openFrontCamera() : Pair<Camera?, Int> {
|
||||||
|
var camera: Camera? = null
|
||||||
|
var cameraID: Int = -1
|
||||||
|
|
||||||
|
val cameraInfo = Camera.CameraInfo()
|
||||||
|
|
||||||
|
for (i in 0 until Camera.getNumberOfCameras()) {
|
||||||
|
Camera.getCameraInfo(i, cameraInfo)
|
||||||
|
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
|
||||||
|
runCatching { Camera.open(i) }.getOrNull()?.let { camera = it; cameraID = i }
|
||||||
|
|
||||||
|
if (camera != null) break
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(camera, cameraID)
|
||||||
|
}
|
||||||
|
|
||||||
|
val orientations = mapOf(
|
||||||
|
Surface.ROTATION_0 to 0,
|
||||||
|
Surface.ROTATION_90 to 90,
|
||||||
|
Surface.ROTATION_180 to 180,
|
||||||
|
Surface.ROTATION_270 to 270,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getRotation(context: Context, cameraID: Int): Int {
|
||||||
|
val cameraRotation = Camera.CameraInfo().also { Camera.getCameraInfo(cameraID, it) }.orientation
|
||||||
|
val rotation = orientations[(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.rotation] ?: error("")
|
||||||
|
|
||||||
|
return (cameraRotation + rotation) % 360
|
||||||
|
}
|
||||||
|
|
||||||
|
var camera: Camera? = null
|
||||||
|
var surfaceTexture: SurfaceTexture? = null
|
||||||
|
private val detector = FaceDetection.getClient(
|
||||||
|
FaceDetectorOptions.Builder()
|
||||||
|
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
private var process: Task<List<Face>>? = null
|
||||||
|
|
||||||
|
fun startCamera(context: Context, callback: (List<Face>) -> Unit) {
|
||||||
|
if (camera != null) closeCamera()
|
||||||
|
|
||||||
|
val cameraID = openFrontCamera().let { (cam, cameraID) ->
|
||||||
|
cam ?: return
|
||||||
|
camera = cam
|
||||||
|
cameraID
|
||||||
|
}
|
||||||
|
|
||||||
|
with (camera!!) {
|
||||||
|
parameters = parameters.apply {
|
||||||
|
setPreviewSize(640, 480)
|
||||||
|
previewFormat = ImageFormat.NV21
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreviewTexture(surfaceTexture ?: SurfaceTexture(0).also {
|
||||||
|
surfaceTexture = it
|
||||||
|
})
|
||||||
|
startPreview()
|
||||||
|
setPreviewCallback { bytes, _ ->
|
||||||
|
if (process?.isComplete == false)
|
||||||
|
return@setPreviewCallback
|
||||||
|
|
||||||
|
val rotation = getRotation(context, cameraID)
|
||||||
|
|
||||||
|
val image = InputImage.fromByteArray(bytes, 640, 480, rotation, InputImage.IMAGE_FORMAT_NV21)
|
||||||
|
process = detector.process(image)
|
||||||
|
.addOnSuccessListener(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeCamera() {
|
||||||
|
camera?.setPreviewCallback(null)
|
||||||
|
camera?.stopPreview()
|
||||||
|
surfaceTexture?.release()
|
||||||
|
surfaceTexture = null
|
||||||
|
camera?.release()
|
||||||
|
camera = null
|
||||||
|
}
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 android.util.SparseArray
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import xyz.quaver.Code
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
|
||||||
import xyz.quaver.hitomi.Reader
|
|
||||||
import xyz.quaver.pupil.util.getCachedGallery
|
|
||||||
import xyz.quaver.pupil.util.getDownloadDirectory
|
|
||||||
import xyz.quaver.pupil.util.isParentOf
|
|
||||||
import xyz.quaver.readBytes
|
|
||||||
import java.io.BufferedInputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
@Deprecated("Use downloader.Cache instead")
|
|
||||||
class Cache(context: Context) : ContextWrapper(context) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val moving = mutableListOf<Int>()
|
|
||||||
private val readers = SparseArray<Reader?>()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
|
|
||||||
// Search in this order
|
|
||||||
// Download -> Cache
|
|
||||||
fun getCachedGallery(galleryID: Int) = getCachedGallery(this, galleryID).also {
|
|
||||||
if (!it.exists())
|
|
||||||
it.mkdirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCachedMetadata(galleryID: Int) : Metadata? {
|
|
||||||
val file = File(getCachedGallery(galleryID), ".metadata")
|
|
||||||
|
|
||||||
if (!file.exists())
|
|
||||||
return null
|
|
||||||
|
|
||||||
return try {
|
|
||||||
Json.decodeFromString(file.readText())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
//File corrupted
|
|
||||||
file.delete()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCachedMetadata(galleryID: Int, metadata: Metadata) {
|
|
||||||
if (preference.getBoolean("cache_disable", false))
|
|
||||||
return
|
|
||||||
|
|
||||||
val file = File(getCachedGallery(galleryID), ".metadata").also {
|
|
||||||
if (!it.exists())
|
|
||||||
it.createNewFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
file.writeText(Json.encodeToString(metadata))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getThumbnail(galleryID: Int): String? {
|
|
||||||
val metadata = Cache(this).getCachedMetadata(galleryID)
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
val thumbnail = if (metadata?.thumbnail == null)
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val thumbnail = getGalleryBlock(galleryID)?.thumbnails?.firstOrNull() ?: return@withContext null
|
|
||||||
try {
|
|
||||||
val data = URL(thumbnail).readBytes().apply {
|
|
||||||
if (isEmpty()) return@withContext null
|
|
||||||
}
|
|
||||||
Base64.encodeToString(data, 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 sources = listOf(
|
|
||||||
{ xyz.quaver.hitomi.getGalleryBlock(galleryID) },
|
|
||||||
{ xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
|
|
||||||
)
|
|
||||||
|
|
||||||
val galleryBlock = if (metadata?.galleryBlock == null) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
var galleryBlock: GalleryBlock? = null
|
|
||||||
|
|
||||||
for (source in sources) {
|
|
||||||
galleryBlock = try {
|
|
||||||
source.invoke()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (galleryBlock != null)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
galleryBlock
|
|
||||||
} ?: return null
|
|
||||||
}
|
|
||||||
else
|
|
||||||
metadata.galleryBlock
|
|
||||||
|
|
||||||
setCachedMetadata(
|
|
||||||
galleryID,
|
|
||||||
Metadata(Cache(this).getCachedMetadata(galleryID), galleryBlock = galleryBlock)
|
|
||||||
)
|
|
||||||
|
|
||||||
return galleryBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getReaderOrNull(galleryID: Int): Reader? {
|
|
||||||
return readers[galleryID] ?: 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{ o1, o2 ->
|
|
||||||
mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
it
|
|
||||||
}
|
|
||||||
|
|
||||||
val reader =
|
|
||||||
if (readers[galleryID] != null)
|
|
||||||
return readers[galleryID]
|
|
||||||
else if (metadata?.reader == null) {
|
|
||||||
var retval: Reader? = null
|
|
||||||
|
|
||||||
for (source in sources) {
|
|
||||||
retval = try {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
source.value.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (retval != null)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
retval
|
|
||||||
} else
|
|
||||||
metadata.reader
|
|
||||||
|
|
||||||
readers.put(galleryID, reader)
|
|
||||||
|
|
||||||
setCachedMetadata(
|
|
||||||
galleryID,
|
|
||||||
Metadata(Cache(this).getCachedMetadata(galleryID), readers = reader)
|
|
||||||
)
|
|
||||||
|
|
||||||
return reader
|
|
||||||
}
|
|
||||||
|
|
||||||
val imageNameRegex = Regex("""^\d+\..+$""")
|
|
||||||
fun getImages(galleryID: Int): List<File?>? {
|
|
||||||
val gallery = getCachedGallery(galleryID)
|
|
||||||
|
|
||||||
return gallery.list { _, name ->
|
|
||||||
imageNameRegex.matches(name)
|
|
||||||
}?.map {
|
|
||||||
File(gallery, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val imageExtensions = listOf(
|
|
||||||
"png",
|
|
||||||
"jpg",
|
|
||||||
"webp",
|
|
||||||
"gif"
|
|
||||||
)
|
|
||||||
fun getImage(galleryID: Int, index: Int): File? {
|
|
||||||
val gallery = getCachedGallery(galleryID)
|
|
||||||
|
|
||||||
for (ext in imageExtensions) {
|
|
||||||
File(gallery, "%05d.$ext".format(index)).let {
|
|
||||||
if (it.exists())
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun putImage(galleryID: Int, index: Int, ext: String, data: InputStream) {
|
|
||||||
if (preference.getBoolean("cache_disable", false))
|
|
||||||
return
|
|
||||||
|
|
||||||
val cache = File(getCachedGallery(galleryID), "%05d.$ext".format(index)).also {
|
|
||||||
if (!it.exists())
|
|
||||||
it.createNewFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
BufferedInputStream(data).use { inputStream ->
|
|
||||||
FileOutputStream(cache).use { outputStream ->
|
|
||||||
inputStream.copyTo(outputStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
cache.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun moveToDownload(galleryID: Int) {
|
|
||||||
if (preference.getBoolean("cache_disable", false))
|
|
||||||
return
|
|
||||||
|
|
||||||
if (moving.contains(galleryID))
|
|
||||||
return
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val cache = getCachedGallery(galleryID).also {
|
|
||||||
if (!it.exists())
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
val download = File(getDownloadDirectory(this@Cache), galleryID.toString())
|
|
||||||
|
|
||||||
if (download.isParentOf(cache))
|
|
||||||
return@launch
|
|
||||||
|
|
||||||
FirebaseCrashlytics.getInstance().log("MOVING ${cache.canonicalPath} --> ${download.canonicalPath}")
|
|
||||||
|
|
||||||
cache.copyRecursively(download, true) { file, err ->
|
|
||||||
FirebaseCrashlytics.getInstance().log("MOVING ERROR ${file.canonicalPath} ${err.message}")
|
|
||||||
OnErrorAction.SKIP
|
|
||||||
}
|
|
||||||
FirebaseCrashlytics.getInstance().log("MOVED ${cache.canonicalPath}")
|
|
||||||
|
|
||||||
FirebaseCrashlytics.getInstance().log("DELETING ${cache.canonicalPath}")
|
|
||||||
cache.deleteRecursively()
|
|
||||||
FirebaseCrashlytics.getInstance().log("DELETED ${cache.canonicalPath}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true
|
|
||||||
|
|
||||||
fun setDownloading(galleryID: Int, isDownloading: Boolean) {
|
|
||||||
setCachedMetadata(galleryID, Metadata(getCachedMetadata(galleryID), isDownloading = isDownloading))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,386 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.Log
|
|
||||||
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.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
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.imageUrlFromImage
|
|
||||||
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.client
|
|
||||||
import xyz.quaver.pupil.interceptors
|
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
|
||||||
|
|
||||||
@Deprecated("Use DownloadService instead")
|
|
||||||
@OptIn(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()
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
interceptors[Pair::class] = { chain ->
|
|
||||||
val request = chain.request()
|
|
||||||
var response = chain.proceed(request)
|
|
||||||
|
|
||||||
var retry = 5
|
|
||||||
while (!response.isSuccessful && retry > 0) {
|
|
||||||
response = chain.proceed(request)
|
|
||||||
retry--
|
|
||||||
}
|
|
||||||
|
|
||||||
response.newBuilder()
|
|
||||||
.body(response.body()?.let {
|
|
||||||
ProgressResponseBody(request.tag(), it, progressListener)
|
|
||||||
}).build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//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
|
|
||||||
*/
|
|
||||||
val progress = SparseArray<MutableList<Float>?>()
|
|
||||||
val notification = SparseArray<NotificationCompat.Builder?>()
|
|
||||||
|
|
||||||
private val loop = loop()
|
|
||||||
private val worker = SparseArray<Job?>()
|
|
||||||
|
|
||||||
fun stop() {
|
|
||||||
queue.clear()
|
|
||||||
|
|
||||||
loop.cancel()
|
|
||||||
for (i in 0 until worker.size()) {
|
|
||||||
val galleryID = worker.keyAt(i)
|
|
||||||
|
|
||||||
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
|
||||||
worker[galleryID]?.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
client.dispatcher().queuedCalls().filter {
|
|
||||||
it.request().tag() is Pair<*, *>
|
|
||||||
}.forEach {
|
|
||||||
it.cancel()
|
|
||||||
}
|
|
||||||
client.dispatcher().runningCalls().filter {
|
|
||||||
it.request().tag() is Pair<*, *>
|
|
||||||
}.forEach {
|
|
||||||
it.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.clear()
|
|
||||||
notification.clear()
|
|
||||||
notificationManager.cancelAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel(galleryID: Int) {
|
|
||||||
queue.remove(galleryID)
|
|
||||||
worker[galleryID]?.cancel()
|
|
||||||
|
|
||||||
client.dispatcher().queuedCalls().filter {
|
|
||||||
((it.request().tag() as Pair<*, *>).first as Int) == galleryID
|
|
||||||
}.forEach {
|
|
||||||
it.cancel()
|
|
||||||
}
|
|
||||||
client.dispatcher().runningCalls().filter {
|
|
||||||
((it.request().tag() as Pair<*, *>).first as Int) == galleryID
|
|
||||||
}.forEach {
|
|
||||||
it.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.remove(galleryID)
|
|
||||||
notification.remove(galleryID)
|
|
||||||
notificationManager.cancel(galleryID)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isCompleted(galleryID: Int) = progress[galleryID]?.all { it.isInfinite() } == true
|
|
||||||
|
|
||||||
private fun queueDownload(galleryID: Int, reader: Reader, index: Int, callback: Callback) {
|
|
||||||
val lowQuality = preferences.getBoolean("low_quality", false)
|
|
||||||
|
|
||||||
val request = Request.Builder().apply {
|
|
||||||
when (reader.code) {
|
|
||||||
Code.HITOMI -> {
|
|
||||||
url(
|
|
||||||
imageUrlFromImage(
|
|
||||||
galleryID,
|
|
||||||
reader.galleryInfo.files[index],
|
|
||||||
!lowQuality
|
|
||||||
)
|
|
||||||
)
|
|
||||||
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)
|
|
||||||
|
|
||||||
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val cache = Cache(this@DownloadWorker).getImages(galleryID)
|
|
||||||
|
|
||||||
progress.put(galleryID, reader.galleryInfo.files.indices.map { index ->
|
|
||||||
if (cache?.firstOrNull { it?.nameWithoutExtension?.toIntOrNull() == index } != null)
|
|
||||||
Float.POSITIVE_INFINITY
|
|
||||||
else
|
|
||||||
0F
|
|
||||||
}.toMutableList())
|
|
||||||
|
|
||||||
if (notification[galleryID] == null)
|
|
||||||
initNotification(galleryID)
|
|
||||||
|
|
||||||
notification[galleryID]?.setContentTitle(reader.galleryInfo.title)
|
|
||||||
notify(galleryID)
|
|
||||||
|
|
||||||
if (isCompleted(galleryID)) {
|
|
||||||
with(Cache(this@DownloadWorker)) {
|
|
||||||
if (isDownloading(galleryID)) {
|
|
||||||
moveToDownload(galleryID)
|
|
||||||
setDownloading(galleryID, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in reader.galleryInfo.files.indices) {
|
|
||||||
val callback = object : Callback {
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
if (e.message?.contains("cancel", true) != false)
|
|
||||||
return
|
|
||||||
|
|
||||||
cancel(galleryID)
|
|
||||||
queue.add(galleryID)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
val ext = call.request().url().encodedPath().split('.').last()
|
|
||||||
|
|
||||||
try {
|
|
||||||
response.body()!!.use {
|
|
||||||
Cache(this@DownloadWorker).putImage(galleryID, i, ext, it.byteStream())
|
|
||||||
}
|
|
||||||
progress[galleryID]?.set(i, Float.POSITIVE_INFINITY)
|
|
||||||
|
|
||||||
notify(galleryID)
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
if (isCompleted(galleryID)) {
|
|
||||||
with(Cache(this@DownloadWorker)) {
|
|
||||||
if (isDownloading(galleryID)) {
|
|
||||||
moveToDownload(galleryID)
|
|
||||||
setDownloading(galleryID, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
FirebaseCrashlytics.getInstance().apply {
|
|
||||||
log("FAIL ON OK ${call.request().tag()} (${e.message})")
|
|
||||||
setCustomKey("POS", "FAIL ON OK")
|
|
||||||
recordException(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
File(Cache(this@DownloadWorker).getCachedGallery(galleryID), "%05d.$ext".format(i)).delete()
|
|
||||||
|
|
||||||
cancel(galleryID)
|
|
||||||
queue.add(galleryID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress[galleryID]?.get(i)?.isFinite() == true)
|
|
||||||
queueDownload(galleryID, reader, i, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun notify(galleryID: Int) {
|
|
||||||
val max = progress[galleryID]?.size ?: 0
|
|
||||||
val progress = progress[galleryID]?.count { it.isInfinite() } ?: 0
|
|
||||||
|
|
||||||
if (isCompleted(galleryID)) {
|
|
||||||
notification[galleryID]
|
|
||||||
?.setContentText(getString(R.string.reader_notification_complete))
|
|
||||||
?.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
|
||||||
?.setProgress(0, 0, false)
|
|
||||||
?.setOngoing(false)
|
|
||||||
|
|
||||||
notificationManager.cancel(galleryID)
|
|
||||||
} else
|
|
||||||
notification[galleryID]
|
|
||||||
?.setProgress(max, progress, false)
|
|
||||||
?.setContentText("$progress/$max")
|
|
||||||
|
|
||||||
if (Cache(this).isDownloading(galleryID) && notification[galleryID] != null)
|
|
||||||
notification[galleryID]?.let { notificationManager.notify(galleryID, it.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(galleryID, 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)
|
|
||||||
setOngoing(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loop() = CoroutineScope(Dispatchers.Default).launch {
|
|
||||||
while (true) {
|
|
||||||
if (queue.isEmpty())
|
|
||||||
continue
|
|
||||||
|
|
||||||
val galleryID = queue.peek() ?: continue
|
|
||||||
|
|
||||||
if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading!
|
|
||||||
cancel(galleryID)
|
|
||||||
|
|
||||||
if (notification[galleryID] == null)
|
|
||||||
initNotification(galleryID)
|
|
||||||
|
|
||||||
if (Cache(this@DownloadWorker).isDownloading(galleryID))
|
|
||||||
notification[galleryID]?.let { notificationManager.notify(galleryID, it.build()) }
|
|
||||||
|
|
||||||
worker.put(galleryID, download(galleryID))
|
|
||||||
queue.poll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
@Deprecated("Use downloader.Cache.Metadata instead")
|
|
||||||
@Serializable
|
|
||||||
data class Metadata(
|
|
||||||
var thumbnail: String? = null,
|
|
||||||
var galleryBlock: GalleryBlock? = null,
|
|
||||||
var reader: Reader? = null,
|
|
||||||
var 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -20,10 +20,12 @@ package xyz.quaver.pupil.util.downloader
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.util.SparseArray
|
import android.net.Uri
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
@@ -34,12 +36,12 @@ import xyz.quaver.Code
|
|||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.*
|
||||||
import xyz.quaver.io.util.readBytes
|
|
||||||
import xyz.quaver.io.util.readText
|
|
||||||
import xyz.quaver.io.util.writeBytes
|
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
@@ -53,16 +55,17 @@ data class Metadata(
|
|||||||
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val instances = SparseArray<Cache>()
|
val instances = ConcurrentHashMap<Int, Cache>()
|
||||||
|
|
||||||
fun getInstance(context: Context, galleryID: Int) =
|
fun getInstance(context: Context, galleryID: Int) =
|
||||||
instances[galleryID] ?: synchronized(this) {
|
instances[galleryID] ?: synchronized(this) {
|
||||||
instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(galleryID: Int) {
|
@Synchronized
|
||||||
instances[galleryID]?.cacheFolder?.deleteRecursively()
|
fun delete(context: Context, galleryID: Int) {
|
||||||
instances.delete(galleryID)
|
File(context.cacheDir, "imageCache/$galleryID").deleteRecursively()
|
||||||
|
instances.remove(galleryID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,11 +89,11 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findFile(fileName: String): FileX? =
|
fun findFile(fileName: String): FileX? =
|
||||||
cacheFolder.getChild(fileName).let {
|
downloadFolder?.let { downloadFolder -> downloadFolder.getChild(fileName).let {
|
||||||
if (it.exists()) it else null
|
if (it.exists()) it else null
|
||||||
} ?: downloadFolder?.let { downloadFolder -> downloadFolder.getChild(fileName).let {
|
} } ?: cacheFolder.getChild(fileName).let {
|
||||||
if (it.exists()) it else null
|
if (it.exists()) it else null
|
||||||
} }
|
}
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun setMetadata(change: (Metadata) -> Unit) {
|
fun setMetadata(change: (Metadata) -> Unit) {
|
||||||
@@ -132,19 +135,24 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
suspend fun getThumbnail(): ByteArray? =
|
suspend fun getThumbnail(): Uri =
|
||||||
findFile(".thumbnail")?.readBytes()
|
findFile(".thumbnail")?.uri
|
||||||
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
|
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(it)
|
.url(it)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
client.newCall(request).execute().body()?.use { it.bytes() }
|
client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() }
|
||||||
}.getOrNull()?.also { kotlin.run {
|
}.getOrNull()?.let { thumbnail -> kotlin.runCatching {
|
||||||
cacheFolder.getChild(".thumbnail").writeBytes(it)
|
cacheFolder.getChild(".thumbnail").also {
|
||||||
} }
|
if (!it.exists())
|
||||||
} }
|
it.createNewFile()
|
||||||
|
|
||||||
|
it.writeBytes(thumbnail)
|
||||||
|
}
|
||||||
|
}.getOrNull()?.uri }
|
||||||
|
} } ?: Uri.EMPTY
|
||||||
|
|
||||||
suspend fun getReader(): Reader? {
|
suspend fun getReader(): Reader? {
|
||||||
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
||||||
@@ -186,46 +194,76 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getImage(index: Int): FileX? =
|
fun getImage(index: Int): FileX? =
|
||||||
metadata.imageList?.get(index)?.let { findFile(it) }
|
metadata.imageList?.getOrNull(index)?.let { findFile(it) }
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun putImage(index: Int, fileName: String, data: ByteArray) {
|
fun putImage(index: Int, fileName: String, data: ByteArray) {
|
||||||
val file = cacheFolder.getChild(fileName)
|
val file = cacheFolder.getChild(fileName)
|
||||||
|
|
||||||
file.createNewFile()
|
if (!file.exists())
|
||||||
|
file.createNewFile()
|
||||||
file.writeBytes(data)
|
file.writeBytes(data)
|
||||||
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val lock = ConcurrentHashMap<Int, Mutex>()
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val downloadFolder = downloadFolder ?: return@launch
|
val downloadFolder = downloadFolder ?: return@launch
|
||||||
|
|
||||||
metadata.imageList?.forEach { imageName ->
|
if (lock[galleryID]?.isLocked == true)
|
||||||
imageName ?: return@forEach
|
return@launch
|
||||||
val target = downloadFolder.getChild(imageName)
|
|
||||||
val source = cacheFolder.getChild(imageName)
|
|
||||||
|
|
||||||
if (!source.exists())
|
(lock[galleryID] ?: Mutex().also { lock[galleryID] = it }).withLock {
|
||||||
return@forEach
|
val cacheMetadata = cacheFolder.getChild(".metadata")
|
||||||
|
val downloadMetadata = downloadFolder.getChild(".metadata")
|
||||||
|
|
||||||
kotlin.runCatching {
|
if (!cacheMetadata.exists())
|
||||||
target.createNewFile()
|
return@launch
|
||||||
source.readBytes()?.let { target.writeBytes(it) }
|
|
||||||
|
if (cacheMetadata.exists()) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
if (!downloadMetadata.exists())
|
||||||
|
downloadMetadata.createNewFile()
|
||||||
|
|
||||||
|
downloadMetadata.writeText(Json.encodeToString(metadata))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val cacheMetadata = cacheFolder.getChild(".metadata")
|
val cacheThumbnail = cacheFolder.getChild(".thumbnail")
|
||||||
val downloadMetadata = downloadFolder.getChild(".metadata")
|
val downloadThumbnail = downloadFolder.getChild(".thumbnail")
|
||||||
|
|
||||||
if (cacheMetadata.exists()) {
|
if (cacheThumbnail.exists()) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
downloadMetadata.createNewFile()
|
if (!downloadThumbnail.exists())
|
||||||
downloadMetadata.writeText(Json.encodeToString(metadata))
|
downloadThumbnail.createNewFile()
|
||||||
cacheMetadata.delete()
|
|
||||||
|
downloadThumbnail.outputStream()?.use { target -> target.channel.truncate(0L); cacheThumbnail.inputStream()?.use { source ->
|
||||||
|
source.copyTo(target)
|
||||||
|
} }
|
||||||
|
cacheThumbnail.delete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cacheFolder.delete()
|
metadata.imageList?.forEach { imageName ->
|
||||||
|
imageName ?: return@forEach
|
||||||
|
val target = downloadFolder.getChild(imageName)
|
||||||
|
val source = cacheFolder.getChild(imageName)
|
||||||
|
|
||||||
|
if (!source.exists())
|
||||||
|
return@forEach
|
||||||
|
|
||||||
|
kotlin.runCatching {
|
||||||
|
if (!target.exists())
|
||||||
|
target.createNewFile()
|
||||||
|
|
||||||
|
target.outputStream()?.use { target -> target.channel.truncate(0L); source.inputStream()?.use { source ->
|
||||||
|
source.copyTo(target)
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheFolder.deleteRecursively()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,13 +75,12 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
data ?: {
|
data ?: {
|
||||||
file.createNewFile()
|
file.createNewFile()
|
||||||
file.writeText("{}")
|
|
||||||
mutableMapOf<Int, String>()
|
mutableMapOf<Int, String>()
|
||||||
}.invoke()
|
}.invoke()
|
||||||
}.invoke()
|
}.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
return downloadFolderMapInstance!!
|
return downloadFolderMapInstance ?: mutableMapOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -99,17 +98,16 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun addDownloadFolder(galleryID: Int) {
|
fun addDownloadFolder(galleryID: Int) {
|
||||||
if (downloadFolderMap.containsKey(galleryID))
|
|
||||||
return
|
|
||||||
|
|
||||||
val name = runBlocking {
|
val name = runBlocking {
|
||||||
Cache.getInstance(this@DownloadManager, galleryID).getGalleryBlock()
|
Cache.getInstance(this@DownloadManager, galleryID).getGalleryBlock()
|
||||||
}?.formatDownloadFolder() ?: return
|
}?.formatDownloadFolder() ?: return
|
||||||
|
|
||||||
val folder = downloadFolder.getChild(name)
|
val folder = downloadFolder.getChild(name)
|
||||||
|
|
||||||
if (!folder.exists())
|
if (folder.exists())
|
||||||
folder.mkdir()
|
return
|
||||||
|
|
||||||
|
folder.mkdir()
|
||||||
|
|
||||||
downloadFolderMap[galleryID] = folder.name
|
downloadFolderMap[galleryID] = folder.name
|
||||||
|
|
||||||
@@ -119,12 +117,9 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun deleteDownloadFolder(galleryID: Int) {
|
fun deleteDownloadFolder(galleryID: Int) {
|
||||||
if (!downloadFolderMap.containsKey(galleryID))
|
|
||||||
return
|
|
||||||
|
|
||||||
downloadFolderMap[galleryID]?.let {
|
downloadFolderMap[galleryID]?.let {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
downloadFolder.getChild(it).delete()
|
downloadFolder.getChild(it).deleteRecursively()
|
||||||
downloadFolderMap.remove(galleryID)
|
downloadFolderMap.remove(galleryID)
|
||||||
|
|
||||||
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile() }
|
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile() }
|
||||||
|
|||||||
@@ -19,32 +19,49 @@
|
|||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.storage.StorageManager
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import androidx.core.content.ContextCompat
|
import kotlinx.coroutines.Dispatchers
|
||||||
import androidx.core.net.toUri
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import xyz.quaver.pupil.histories
|
||||||
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.lang.reflect.Array
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
@Deprecated("Use downloader.Cache instead")
|
val mutex = Mutex()
|
||||||
fun getCachedGallery(context: Context, galleryID: Int) =
|
fun cleanCache(context: Context) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
File(getDownloadDirectory(context), galleryID.toString()).let {
|
if (mutex.isLocked) return@launch
|
||||||
if (it.exists())
|
|
||||||
it
|
mutex.withLock {
|
||||||
else
|
val cacheFolder = File(context.cacheDir, "imageCache")
|
||||||
File(context.cacheDir, "imageCache/$galleryID")
|
val downloadManager = DownloadManager.getInstance(context)
|
||||||
|
|
||||||
|
val limit = (Preferences.get<String>("cache_limit").toLongOrNull() ?: 0L)*1024*1024*1024
|
||||||
|
|
||||||
|
if (limit == 0L) return@withLock
|
||||||
|
|
||||||
|
val cacheSize = {
|
||||||
|
var size = 0L
|
||||||
|
|
||||||
|
cacheFolder.walk().forEach {
|
||||||
|
size += it.length()
|
||||||
|
}
|
||||||
|
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cacheSize.invoke() > limit)
|
||||||
|
while (cacheSize.invoke() > limit/2) {
|
||||||
|
val caches = cacheFolder.list() ?: return@withLock
|
||||||
|
|
||||||
|
synchronized(histories) {
|
||||||
|
(histories.firstOrNull {
|
||||||
|
caches.contains(it.toString()) && !downloadManager.isDownloading(it)
|
||||||
|
} ?: return@withLock).let {
|
||||||
|
Cache.delete(context, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Deprecated("Use downloader.Cache instead")
|
|
||||||
fun getDownloadDirectory(context: Context) =
|
|
||||||
Preferences.get<String>("dl_location").let {
|
|
||||||
if (it.isNotEmpty() && !it.startsWith("content"))
|
|
||||||
File(it)
|
|
||||||
else
|
|
||||||
context.getExternalFilesDir(null)!!
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Use FileX instead")
|
|
||||||
fun File.isParentOf(another: File) =
|
|
||||||
another.absolutePath.startsWith(this.absolutePath)
|
|
||||||
@@ -22,11 +22,10 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.serialization.json.jsonArray
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import xyz.quaver.Code
|
import xyz.quaver.Code
|
||||||
@@ -34,11 +33,7 @@ import xyz.quaver.hitomi.GalleryBlock
|
|||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.getReferer
|
import xyz.quaver.hitomi.getReferer
|
||||||
import xyz.quaver.hitomi.imageUrlFromImage
|
import xyz.quaver.hitomi.imageUrlFromImage
|
||||||
import xyz.quaver.hiyobi.cookie
|
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.user_agent
|
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
|
||||||
import xyz.quaver.pupil.util.downloader.Metadata
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@@ -101,21 +96,14 @@ fun GalleryBlock.formatDownloadFolder(): String =
|
|||||||
formatMap.entries.fold(it) { str, (k, v) ->
|
formatMap.entries.fold(it) { str, (k, v) ->
|
||||||
str.replace(k, v.invoke(this), true)
|
str.replace(k, v.invoke(this), true)
|
||||||
}
|
}
|
||||||
}
|
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||||
|
|
||||||
fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
||||||
format.let {
|
format.let {
|
||||||
formatMap.entries.fold(it) { str, (k, v) ->
|
formatMap.entries.fold(it) { str, (k, v) ->
|
||||||
str.replace(k, v.invoke(this), true)
|
str.replace(k, v.invoke(this), true)
|
||||||
}
|
}
|
||||||
}
|
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||||
|
|
||||||
fun Context.startForegroundServiceCompat(service: Intent) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 26)
|
|
||||||
startForegroundService(service)
|
|
||||||
else
|
|
||||||
startService(service)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Reader.requestBuilders: List<Request.Builder>
|
val Reader.requestBuilders: List<Request.Builder>
|
||||||
get() {
|
get() {
|
||||||
@@ -134,8 +122,6 @@ val Reader.requestBuilders: List<Request.Builder>
|
|||||||
createImgList(galleryID, this, lowQuality).map {
|
createImgList(galleryID, this, lowQuality).map {
|
||||||
Request.Builder()
|
Request.Builder()
|
||||||
.url(it.path)
|
.url(it.path)
|
||||||
.header("User-Agent", user_agent)
|
|
||||||
.header("Cookie", cookie)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,4 +131,10 @@ fun String.ellipsize(n: Int): String =
|
|||||||
if (this.length > n)
|
if (this.length > n)
|
||||||
this.slice(0 until n) + "…"
|
this.slice(0 until n) + "…"
|
||||||
else
|
else
|
||||||
this
|
this
|
||||||
|
|
||||||
|
operator fun JsonElement.get(index: Int) =
|
||||||
|
this.jsonArray[index]
|
||||||
|
|
||||||
|
operator fun JsonElement.get(tag: String) =
|
||||||
|
this.jsonObject[tag]
|
||||||
68
app/src/main/java/xyz/quaver/pupil/util/translation.kt
Normal file
68
app/src/main/java/xyz/quaver/pupil/util/translation.kt
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import okhttp3.Request
|
||||||
|
import xyz.quaver.pupil.client
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
private val filesURL = "https://api.github.com/repos/tom5079/Pupil/git/trees/tags"
|
||||||
|
private val contentURL = "https://raw.githubusercontent.com/tom5079/Pupil/tags/"
|
||||||
|
|
||||||
|
var translations: Map<String, String> = run {
|
||||||
|
updateTranslations()
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
fun updateTranslations() = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
translations = emptyMap()
|
||||||
|
kotlin.runCatching {
|
||||||
|
translations = Json.decodeFromString<Map<String, String>>(client.newCall(
|
||||||
|
Request.Builder()
|
||||||
|
.url(contentURL + "${Preferences["tag_translation", ""].let { if (it.isEmpty()) Locale.getDefault().language else it }}.json")
|
||||||
|
.build()
|
||||||
|
).execute().also { if (it.code() != 200) return@launch }.body()?.use { it.string() } ?: return@launch).filterValues { it.isNotEmpty() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAvailableLanguages(): List<String> {
|
||||||
|
val languages = Locale.getISOLanguages()
|
||||||
|
|
||||||
|
val json = Json.parseToJsonElement(client.newCall(
|
||||||
|
Request.Builder()
|
||||||
|
.url(filesURL)
|
||||||
|
.build()
|
||||||
|
).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.string() } ?: return emptyList())
|
||||||
|
|
||||||
|
return listOf("en") + (json["tree"]?.jsonArray?.mapNotNull {
|
||||||
|
val name = it["path"]?.jsonPrimitive?.content?.takeWhile { c -> c != '.' }
|
||||||
|
|
||||||
|
languages.firstOrNull { code -> code.equals(name, ignoreCase = true) }
|
||||||
|
} ?: emptyList())
|
||||||
|
}
|
||||||
@@ -27,13 +27,11 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -52,10 +50,13 @@ import xyz.quaver.hitomi.getGalleryBlock
|
|||||||
import xyz.quaver.hitomi.getReader
|
import xyz.quaver.hitomi.getReader
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.io.util.*
|
import xyz.quaver.io.util.readText
|
||||||
|
import xyz.quaver.io.util.writeBytes
|
||||||
|
import xyz.quaver.io.util.writeText
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.Metadata
|
import xyz.quaver.pupil.util.downloader.Metadata
|
||||||
@@ -158,8 +159,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
setTitle(R.string.update_title)
|
setTitle(R.string.update_title)
|
||||||
val msg = extractReleaseNote(update, Locale.getDefault())
|
val msg = extractReleaseNote(update, Locale.getDefault())
|
||||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
|
||||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
|
||||||
//Cancel any download queued before
|
//Cancel any download queued before
|
||||||
@@ -181,7 +181,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
Preferences["update_download_id"] = it
|
Preferences["update_download_id"] = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNegativeButton(if (force) android.R.string.no else R.string.ignore_update) { _, _ ->
|
setNegativeButton(if (force) android.R.string.cancel else R.string.ignore_update) { _, _ ->
|
||||||
if (!force)
|
if (!force)
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
|
.putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
|
||||||
@@ -195,7 +195,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restore(favorites: GalleryList, url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List<Int>) -> Unit)? = null) {
|
fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List<Int>) -> Unit)? = null) {
|
||||||
if (!URLUtil.isValidUrl(url)) {
|
if (!URLUtil.isValidUrl(url)) {
|
||||||
onFailure?.invoke(IllegalArgumentException())
|
onFailure?.invoke(IllegalArgumentException())
|
||||||
return
|
return
|
||||||
@@ -213,7 +213,7 @@ fun restore(favorites: GalleryList, url: String, onFailure: ((Throwable) -> Unit
|
|||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
Json.decodeFromString<List<Int>>(response.body().use { it?.string() } ?: "[]").let {
|
Json.decodeFromString<List<Int>>(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]").let {
|
||||||
favorites.addAll(it)
|
favorites.addAll(it)
|
||||||
onSuccess?.invoke(it)
|
onSuccess?.invoke(it)
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,12 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
|||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = CoroutineScope(Dispatchers.IO).launch {
|
job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val downloadFolders = downloadFolder.listFiles { folder ->
|
val downloadFolders = downloadFolder.listFiles { folder ->
|
||||||
(folder as? FileX)?.isDirectory == true && !downloadFolderMap.values.contains(folder.name)
|
folder.isDirectory && !downloadFolderMap.values.contains(folder.name)
|
||||||
|
}?.map {
|
||||||
|
if (it !is FileX)
|
||||||
|
FileX(this@migrate, it)
|
||||||
|
else
|
||||||
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadFolders.isNullOrEmpty()) return@launch
|
if (downloadFolders.isNullOrEmpty()) return@launch
|
||||||
@@ -269,8 +274,6 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
|||||||
notificationManager.notify(R.id.notification_id_import, notification.build())
|
notificationManager.notify(R.id.notification_id_import, notification.build())
|
||||||
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
if (folder !is FileX) return@runCatching
|
|
||||||
|
|
||||||
val metadata = kotlin.runCatching {
|
val metadata = kotlin.runCatching {
|
||||||
folder.getChild(".metadata").readText()?.let { Json.parseToJsonElement(it).jsonObject }
|
folder.getChild(".metadata").readText()?.let { Json.parseToJsonElement(it).jsonObject }
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
@@ -310,15 +313,14 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
synchronized(Cache) {
|
synchronized(Cache) {
|
||||||
Cache.instances.delete(galleryID)
|
Cache.delete(this@migrate, galleryID)
|
||||||
}
|
}
|
||||||
downloadFolderMap[galleryID] = folder.name
|
downloadFolderMap[galleryID] = folder.name
|
||||||
|
|
||||||
|
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile(); it.writeText(Json.encodeToString(downloadFolderMap)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile() }
|
|
||||||
downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
|
|
||||||
|
|
||||||
notification
|
notification
|
||||||
.setContentText(getText(R.string.import_old_galleries_notification_done))
|
.setContentText(getText(R.string.import_old_galleries_notification_done))
|
||||||
.setProgress(0, 0, false)
|
.setProgress(0, 0, false)
|
||||||
@@ -326,6 +328,8 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
|||||||
.mActions.clear()
|
.mActions.clear()
|
||||||
notificationManager.notify(R.id.notification_id_import, notification.build())
|
notificationManager.notify(R.id.notification_id_import, notification.build())
|
||||||
|
|
||||||
unregisterReceiver(receiver)
|
kotlin.runCatching {
|
||||||
|
unregisterReceiver(receiver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 585 B |
Binary file not shown.
|
Before Width: | Height: | Size: 366 B |
Binary file not shown.
|
Before Width: | Height: | Size: 700 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/account_group.xml -->
|
<!--drawable/account_group.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M12 5.5A3.5 3.5 0 0 1 15.5 9a3.5 3.5 0 0 1-3.5 3.5A3.5 3.5 0 0 1 8.5 9 3.5 3.5 0 0 1 12 5.5M5 8c0.56 0 1.08 0.15 1.53 0.42-0.15 1.43 0.27 2.85 1.13 3.96C7.16 13.34 6.16 14 5 14a3 3 0 0 1-3-3 3 3 0 0 1 3-3m14 0a3 3 0 0 1 3 3 3 3 0 0 1-3 3c-1.16 0-2.16-0.66-2.66-1.62 0.86-1.11 1.28-2.53 1.13-3.96C17.92 8.15 18.44 8 19 8M5.5 18.25c0-2.07 2.91-3.75 6.5-3.75s6.5 1.68 6.5 3.75V20h-13v-1.75M0 20v-1.5c0-1.39 1.89-2.56 4.45-2.9-0.59 0.68-0.95 1.62-0.95 2.65V20H0m24 0h-3.5v-1.75c0-1.03-0.36-1.97-0.95-2.65 2.56 0.34 4.45 1.51 4.45 2.9V20z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="?attr/colorControlNormal" android:pathData="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/account_star.xml -->
|
<!--drawable/account_star.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M15 14c-2.67 0-8 1.33-8 4v2h16v-2c0-2.67-5.33-4-8-4m0-2a4 4 0 0 0 4-4 4 4 0 0 0-4-4 4 4 0 0 0-4 4 4 4 0 0 0 4 4M5 13.28l2.45 1.49-0.65-2.81L9 10.08 6.11 9.83 5 7.19 3.87 9.83 1 10.08l2.18 1.88-0.68 2.81L5 13.28z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="?attr/colorControlNormal" android:pathData="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12M5,13.28L7.45,14.77L6.8,11.96L9,10.08L6.11,9.83L5,7.19L3.87,9.83L1,10.08L3.18,11.96L2.5,14.77L5,13.28Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/arrow_right.xml -->
|
<!--drawable/arrow_right.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#fff" android:pathData="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11H4z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
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>
|
|
||||||
|
|||||||
@@ -1,37 +1,14 @@
|
|||||||
<animated-vector
|
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" xmlns:tools="http://schemas.android.com/tools" tools:ignore="newApi">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
tools:ignore="newApi">
|
|
||||||
<aapt:attr name="android:drawable">
|
<aapt:attr name="android:drawable">
|
||||||
<vector
|
<vector android:name="vector" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:name="vector"
|
<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:fillColor="@color/material_orange_500"/>
|
||||||
android:width="24dp"
|
<clip-path android:name="clip" android:pathData="M 2 21 L 2 21 L 22 21 L 22 21 Z"/>
|
||||||
android:height="24dp"
|
<path 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:fillColor="@color/material_orange_500"/>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<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:fillColor="@color/material_orange_500"/>
|
|
||||||
<clip-path
|
|
||||||
android:name="clip"
|
|
||||||
android:pathData="M 2 21 L 2 21 L 22 21 L 22 21 Z"/>
|
|
||||||
<path
|
|
||||||
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:fillColor="@color/material_orange_500"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
<target android:name="clip">
|
<target android:name="clip">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator android:propertyName="pathData" android:duration="500" android:valueFrom="M 2 21 L 2 21 L 22 21 L 22 21 Z" android:valueTo="M 2 2 L 2 21 L 22 21 L 22 2 Z" android:valueType="pathType" android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||||
android:propertyName="pathData"
|
|
||||||
android:duration="500"
|
|
||||||
android:valueFrom="M 2 21 L 2 21 L 22 21 L 22 21 Z"
|
|
||||||
android:valueTo="M 2 2 L 2 21 L 22 21 L 22 2 Z"
|
|
||||||
android:valueType="pathType"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
</animated-vector>
|
</animated-vector>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/backspace_outline.xml -->
|
<!--drawable/backspace_outline.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#000" android:pathData="M19 15.59L17.59 17 14 13.41 10.41 17 9 15.59 12.59 12 9 8.41 10.41 7 14 10.59 17.59 7 19 8.41 15.41 12 19 15.59M22 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H7c-0.69 0-1.23-0.36-1.59-0.89L0 12l5.41-8.12C5.77 3.35 6.31 3 7 3h15m0 2H7l-4.72 7L7 19h15V5z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#000" android:pathData="M19,15.59L17.59,17L14,13.41L10.41,17L9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59M22,3A2,2 0 0,1 24,5V19A2,2 0 0,1 22,21H7C6.31,21 5.77,20.64 5.41,20.11L0,12L5.41,3.88C5.77,3.35 6.31,3 7,3H22M22,5H7L2.28,12L7,19H22V5Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/book_open.xml -->
|
<!--drawable/book_open.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M13 12h7v1.5h-7m0-4h7V11h-7m0 3.5h7V16h-7m8-12H3a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2m0 15h-9V6h9"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="?attr/colorControlNormal" android:pathData="M13,12H20V13.5H13M13,9.5H20V11H13M13,14.5H20V16H13M21,4H3A2,2 0 0,0 1,6V19A2,2 0 0,0 3,21H21A2,2 0 0,0 23,19V6A2,2 0 0,0 21,4M21,19H12V6H21" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/brush.xml -->
|
<!--drawable/brush.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M20.71 4.63l-1.34-1.34c-0.37-0.39-1.02-0.39-1.41 0L9 12.25 11.75 15l8.96-8.96c0.39-0.39 0.39-1.04 0-1.41M7 14a3 3 0 0 0-3 3c0 1.31-1.16 2-2 2 0.92 1.22 2.5 2 4 2a4 4 0 0 0 4-4 3 3 0 0 0-3-3z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="?attr/colorControlNormal" android:pathData="M20.71,4.63L19.37,3.29C19,2.9 18.35,2.9 17.96,3.29L9,12.25L11.75,15L20.71,6.04C21.1,5.65 21.1,5 20.71,4.63M7,14A3,3 0 0,0 4,17C4,18.31 2.84,19 2,19C2.92,20.22 4.5,21 6,21A4,4 0 0,0 10,17A3,3 0 0,0 7,14Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/cancel.xml -->
|
<!--drawable/cancel.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#fff" android:pathData="M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8c0 1.85 0.63 3.55 1.68 4.91L16.91 5.68C15.55 4.63 13.85 4 12 4m0 16a8 8 0 0 0 8-8c0-1.85-0.63-3.55-1.68-4.91L7.09 18.32C8.45 19.37 10.15 20 12 20z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,13.85 4.63,15.55 5.68,16.91L16.91,5.68C15.55,4.63 13.85,4 12,4M12,20A8,8 0 0,0 20,12C20,10.15 19.37,8.45 18.32,7.09L7.09,18.32C8.45,19.37 10.15,20 12,20Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/clock_end.xml -->
|
<!--drawable/clock_end.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#fff" android:pathData="M12 1C8.14 1 5 4.14 5 8a7 7 0 0 0 7 7c3.86 0 7-3.13 7-7 0-3.86-3.14-7-7-7m0 2.15c2.67 0 4.85 2.17 4.85 4.85 0 2.68-2.18 4.85-4.85 4.85A4.85 4.85 0 0 1 7.15 8 4.85 4.85 0 0 1 12 3.15M11 5v3.69l3.19 1.84 0.75-1.3-2.44-1.41V5M15 16v3H3v2h12v3l4-4m0 0v4h2v-8h-2"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M12,1C8.14,1 5,4.14 5,8A7,7 0 0,0 12,15C15.86,15 19,11.87 19,8C19,4.14 15.86,1 12,1M12,3.15C14.67,3.15 16.85,5.32 16.85,8C16.85,10.68 14.67,12.85 12,12.85A4.85,4.85 0 0,1 7.15,8A4.85,4.85 0 0,1 12,3.15M11,5V8.69L14.19,10.53L14.94,9.23L12.5,7.82V5M15,16V19H3V21H15V24L19,20M19,20V24H21V16H19" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/clock_start.xml -->
|
<!--drawable/clock_start.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#fff" android:pathData="M12 1C8.14 1 5 4.14 5 8a7 7 0 0 0 7 7c3.86 0 7-3.13 7-7 0-3.86-3.14-7-7-7m0 2.15c2.67 0 4.85 2.17 4.85 4.85 0 2.68-2.18 4.85-4.85 4.85A4.85 4.85 0 0 1 7.15 8 4.85 4.85 0 0 1 12 3.15M11 5v3.69l3.19 1.84 0.75-1.3-2.44-1.41V5M4 16v8h2v-3h12v3l4-4-4-4v3H6v-3"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M12,1C8.14,1 5,4.14 5,8A7,7 0 0,0 12,15C15.86,15 19,11.87 19,8C19,4.14 15.86,1 12,1M12,3.15C14.67,3.15 16.85,5.32 16.85,8C16.85,10.68 14.67,12.85 12,12.85A4.85,4.85 0 0,1 7.15,8A4.85,4.85 0 0,1 12,3.15M11,5V8.69L14.19,10.53L14.94,9.23L12.5,7.82V5M4,16V24H6V21H18V24L22,20L18,16V19H6V16" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
4
app/src/main/res/drawable/close.xml
Normal file
4
app/src/main/res/drawable/close.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!--drawable/close.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="?attr/colorControlNormal" android:pathData="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/>
|
||||||
|
</vector>
|
||||||
4
app/src/main/res/drawable/delete.xml
Normal file
4
app/src/main/res/drawable/delete.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!--drawable/delete.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="?attr/colorControlNormal" android:pathData="M19 4h-3.5l-1-1h-5l-1 1H5v2h14M6 19a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V7H6v12z"/>
|
||||||
|
</vector>
|
||||||
30
app/src/main/res/drawable/dot.xml
Normal file
30
app/src/main/res/drawable/dot.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
|
||||||
|
<solid
|
||||||
|
android:color="@color/colorAccent"/>
|
||||||
|
|
||||||
|
<size
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"/>
|
||||||
|
</shape>
|
||||||
8
app/src/main/res/drawable/eye.xml
Normal file
8
app/src/main/res/drawable/eye.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/eye.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="?attr/colorControlNormal" android:pathData="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" />
|
||||||
|
</vector>
|
||||||
44
app/src/main/res/drawable/eye_closed.xml
Normal file
44
app/src/main/res/drawable/eye_closed.xml
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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="22dp"
|
||||||
|
android:height="15dp"
|
||||||
|
android:viewportWidth="22"
|
||||||
|
android:viewportHeight="15">
|
||||||
|
<path
|
||||||
|
android:pathData="M21.61,5.4C14.21,13.39 7.16,13.37 0.43,5.32"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="?attr/colorControlNormal"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M1.32,9.8L3.03,7.8"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="?attr/colorControlNormal"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M5.14,12.37L6.16,10.37"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="?attr/colorControlNormal"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M16.27,12.37L15.25,10.37"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="?attr/colorControlNormal"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M18.78,7.8L20.49,9.8"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="?attr/colorControlNormal"/>
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/eye_off.xml
Normal file
8
app/src/main/res/drawable/eye_off.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/eye_off.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="?attr/colorControlNormal" android:pathData="M11.83,9L15,12.16C15,12.11 15,12.05 15,12A3,3 0 0,0 12,9C11.94,9 11.89,9 11.83,9M7.53,9.8L9.08,11.35C9.03,11.56 9,11.77 9,12A3,3 0 0,0 12,15C12.22,15 12.44,14.97 12.65,14.92L14.2,16.47C13.53,16.8 12.79,17 12,17A5,5 0 0,1 7,12C7,11.21 7.2,10.47 7.53,9.8M2,4.27L4.28,6.55L4.73,7C3.08,8.3 1.78,10 1,12C2.73,16.39 7,19.5 12,19.5C13.55,19.5 15.03,19.2 16.38,18.66L16.81,19.08L19.73,22L21,20.73L3.27,3M12,7A5,5 0 0,1 17,12C17,12.64 16.87,13.26 16.64,13.82L19.57,16.75C21.07,15.5 22.27,13.86 23,12C21.27,7.61 17,4.5 12,4.5C10.6,4.5 9.26,4.75 8,5.2L10.17,7.35C10.74,7.13 11.35,7 12,7Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/eye_off_white.xml
Normal file
8
app/src/main/res/drawable/eye_off_white.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/eye_off.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="M11.83,9L15,12.16C15,12.11 15,12.05 15,12A3,3 0 0,0 12,9C11.94,9 11.89,9 11.83,9M7.53,9.8L9.08,11.35C9.03,11.56 9,11.77 9,12A3,3 0 0,0 12,15C12.22,15 12.44,14.97 12.65,14.92L14.2,16.47C13.53,16.8 12.79,17 12,17A5,5 0 0,1 7,12C7,11.21 7.2,10.47 7.53,9.8M2,4.27L4.28,6.55L4.73,7C3.08,8.3 1.78,10 1,12C2.73,16.39 7,19.5 12,19.5C13.55,19.5 15.03,19.2 16.38,18.66L16.81,19.08L19.73,22L21,20.73L3.27,3M12,7A5,5 0 0,1 17,12C17,12.64 16.87,13.26 16.64,13.82L19.57,16.75C21.07,15.5 22.27,13.86 23,12C21.27,7.61 17,4.5 12,4.5C10.6,4.5 9.26,4.75 8,5.2L10.17,7.35C10.74,7.13 11.35,7 12,7Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/eye_white.xml
Normal file
8
app/src/main/res/drawable/eye_white.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/eye.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="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" />
|
||||||
|
</vector>
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
<!-- drawable/fingerprint.xml -->
|
<!--drawable/fingerprint.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
<path android:fillColor="#fff" android:pathData="M17.81 4.47c-0.08 0-0.16-0.02-0.23-0.06C15.66 3.42 14 3 12 3c-1.97 0-3.85 0.47-5.56 1.41C6.2 4.54 5.9 4.45 5.76 4.21c-0.13-0.24-0.04-0.55 0.2-0.68C7.82 2.5 9.86 2 12 2s4 0.47 6.04 1.5c0.25 0.15 0.34 0.45 0.21 0.69-0.09 0.18-0.25 0.28-0.44 0.28M3.5 9.72c-0.1 0-0.2-0.03-0.29-0.09C3 9.47 2.93 9.16 3.09 8.93c0.99-1.4 2.25-2.5 3.75-3.27C10 4.04 14 4.03 17.15 5.65c1.5 0.77 2.76 1.85 3.75 3.25 0.16 0.22 0.1 0.54-0.12 0.7-0.23 0.16-0.54 0.11-0.7-0.1-0.9-1.28-2.04-2.27-3.39-2.96-2.87-1.47-6.54-1.47-9.4 0.01-1.36 0.7-2.5 1.7-3.4 2.95C3.81 9.65 3.66 9.72 3.5 9.72m6.25 12.07c-0.13 0-0.25-0.05-0.35-0.15-0.87-0.87-1.34-1.43-2.01-2.64-0.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39a0.5 0.5 0 0 1-0.5 0.5 0.5 0.5 0 0 1-0.5-0.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44 0.32 2.77 0.93 3.84 0.64 1.16 1.08 1.65 1.85 2.43 0.19 0.2 0.19 0.51 0 0.71-0.12 0.1-0.24 0.15-0.37 0.15m7.17-1.85c-1.19 0-2.24-0.3-3.1-0.89-1.49-1.01-2.38-2.65-2.38-4.39a0.5 0.5 0 0 1 0.5-0.5 0.5 0.5 0 0 1 0.5 0.5c0 1.41 0.72 2.74 1.94 3.56 0.71 0.48 1.54 0.71 2.54 0.71 0.24 0 0.64-0.03 1.04-0.1 0.27-0.05 0.54 0.13 0.58 0.41 0.05 0.26-0.13 0.53-0.41 0.58-0.57 0.11-1.07 0.12-1.21 0.12M14.91 22h-0.13c-1.59-0.46-2.63-1.05-3.72-2.12-1.4-1.38-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07 0.95 1.94 2.08 1.94 1.15 0 2.08-0.87 2.08-1.94 0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.46 1.58-6.61 4.03-0.39 0.81-0.59 1.76-0.59 2.8 0 0.78 0.07 2.01 0.67 3.61 0.1 0.26-0.03 0.55-0.29 0.64C4.88 19 4.59 18.87 4.5 18.62 4 17.31 3.77 16 3.77 14.66c0-1.2 0.23-2.29 0.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.54 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94-1.7 0-3.08-1.32-3.08-2.94 0-1.07-0.93-1.94-2.08-1.94s-2.08 0.87-2.08 1.94c0 1.71 0.66 3.31 1.87 4.51 0.95 0.94 1.86 1.46 3.27 1.84 0.27 0.08 0.42 0.36 0.35 0.62-0.05 0.23-0.26 0.38-0.47 0.38z" tools:ignore="VectorPath"/>
|
||||||
android:height="24dp"
|
</vector>
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M17.81,4.47C17.73,4.47 17.65,4.45 17.58,4.41C15.66,3.42 14,3 12,3C10.03,3 8.15,3.47 6.44,4.41C6.2,4.54 5.9,4.45 5.76,4.21C5.63,3.97 5.72,3.66 5.96,3.53C7.82,2.5 9.86,2 12,2C14.14,2 16,2.47 18.04,3.5C18.29,3.65 18.38,3.95 18.25,4.19C18.16,4.37 18,4.47 17.81,4.47M3.5,9.72C3.4,9.72 3.3,9.69 3.21,9.63C3,9.47 2.93,9.16 3.09,8.93C4.08,7.53 5.34,6.43 6.84,5.66C10,4.04 14,4.03 17.15,5.65C18.65,6.42 19.91,7.5 20.9,8.9C21.06,9.12 21,9.44 20.78,9.6C20.55,9.76 20.24,9.71 20.08,9.5C19.18,8.22 18.04,7.23 16.69,6.54C13.82,5.07 10.15,5.07 7.29,6.55C5.93,7.25 4.79,8.25 3.89,9.5C3.81,9.65 3.66,9.72 3.5,9.72M9.75,21.79C9.62,21.79 9.5,21.74 9.4,21.64C8.53,20.77 8.06,20.21 7.39,19C6.7,17.77 6.34,16.27 6.34,14.66C6.34,11.69 8.88,9.27 12,9.27C15.12,9.27 17.66,11.69 17.66,14.66A0.5,0.5 0 0,1 17.16,15.16A0.5,0.5 0 0,1 16.66,14.66C16.66,12.24 14.57,10.27 12,10.27C9.43,10.27 7.34,12.24 7.34,14.66C7.34,16.1 7.66,17.43 8.27,18.5C8.91,19.66 9.35,20.15 10.12,20.93C10.31,21.13 10.31,21.44 10.12,21.64C10,21.74 9.88,21.79 9.75,21.79M16.92,19.94C15.73,19.94 14.68,19.64 13.82,19.05C12.33,18.04 11.44,16.4 11.44,14.66A0.5,0.5 0 0,1 11.94,14.16A0.5,0.5 0 0,1 12.44,14.66C12.44,16.07 13.16,17.4 14.38,18.22C15.09,18.7 15.92,18.93 16.92,18.93C17.16,18.93 17.56,18.9 17.96,18.83C18.23,18.78 18.5,18.96 18.54,19.24C18.59,19.5 18.41,19.77 18.13,19.82C17.56,19.93 17.06,19.94 16.92,19.94M14.91,22C14.87,22 14.82,22 14.78,22C13.19,21.54 12.15,20.95 11.06,19.88C9.66,18.5 8.89,16.64 8.89,14.66C8.89,13.04 10.27,11.72 11.97,11.72C13.67,11.72 15.05,13.04 15.05,14.66C15.05,15.73 16,16.6 17.13,16.6C18.28,16.6 19.21,15.73 19.21,14.66C19.21,10.89 15.96,7.83 11.96,7.83C9.12,7.83 6.5,9.41 5.35,11.86C4.96,12.67 4.76,13.62 4.76,14.66C4.76,15.44 4.83,16.67 5.43,18.27C5.53,18.53 5.4,18.82 5.14,18.91C4.88,19 4.59,18.87 4.5,18.62C4,17.31 3.77,16 3.77,14.66C3.77,13.46 4,12.37 4.45,11.42C5.78,8.63 8.73,6.82 11.96,6.82C16.5,6.82 20.21,10.33 20.21,14.65C20.21,16.27 18.83,17.59 17.13,17.59C15.43,17.59 14.05,16.27 14.05,14.65C14.05,13.58 13.12,12.71 11.97,12.71C10.82,12.71 9.89,13.58 9.89,14.65C9.89,16.36 10.55,17.96 11.76,19.16C12.71,20.1 13.62,20.62 15.03,21C15.3,21.08 15.45,21.36 15.38,21.62C15.33,21.85 15.12,22 14.91,22Z"
|
|
||||||
tools:ignore="VectorPath" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/gender_female.xml -->
|
<!--drawable/gender_female.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M12 4a6 6 0 0 1 6 6c0 2.97-2.16 5.44-5 5.92V18h2v2h-2v2h-2v-2H9v-2h2v-2.08C8.16 15.44 6 12.97 6 10a6 6 0 0 1 6-6m0 2a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="?attr/colorControlNormal" android:pathData="M12,4A6,6 0 0,1 18,10C18,12.97 15.84,15.44 13,15.92V18H15V20H13V22H11V20H9V18H11V15.92C8.16,15.44 6,12.97 6,10A6,6 0 0,1 12,4M12,6A4,4 0 0,0 8,10A4,4 0 0,0 12,14A4,4 0 0,0 16,10A4,4 0 0,0 12,6Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/gender_female.xml -->
|
<!--drawable/gender_female.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#FFF" android:pathData="M12 4a6 6 0 0 1 6 6c0 2.97-2.16 5.44-5 5.92V18h2v2h-2v2h-2v-2H9v-2h2v-2.08C8.16 15.44 6 12.97 6 10a6 6 0 0 1 6-6m0 2a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M12,4A6,6 0 0,1 18,10C18,12.97 15.84,15.44 13,15.92V18H15V20H13V22H11V20H9V18H11V15.92C8.16,15.44 6,12.97 6,10A6,6 0 0,1 12,4M12,6A4,4 0 0,0 8,10A4,4 0 0,0 12,14A4,4 0 0,0 16,10A4,4 0 0,0 12,6Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/gender_male.xml -->
|
<!--drawable/gender_male.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M9 9c1.29 0 2.5 0.41 3.47 1.11L17.58 5H13V3h8v8h-2V6.41l-5.11 5.09c0.7 1 1.11 2.2 1.11 3.5a6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6m0 2a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="?attr/colorControlNormal" android:pathData="M9,9C10.29,9 11.5,9.41 12.47,10.11L17.58,5H13V3H21V11H19V6.41L13.89,11.5C14.59,12.5 15,13.7 15,15A6,6 0 0,1 9,21A6,6 0 0,1 3,15A6,6 0 0,1 9,9M9,11A4,4 0 0,0 5,15A4,4 0 0,0 9,19A4,4 0 0,0 13,15A4,4 0 0,0 9,11Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/gender_male.xml -->
|
<!--drawable/gender_male.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#FFF" android:pathData="M9 9c1.29 0 2.5 0.41 3.47 1.11L17.58 5H13V3h8v8h-2V6.41l-5.11 5.09c0.7 1 1.11 2.2 1.11 3.5a6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6m0 2a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M9,9C10.29,9 11.5,9.41 12.47,10.11L17.58,5H13V3H21V11H19V6.41L13.89,11.5C14.59,12.5 15,13.7 15,15A6,6 0 0,1 9,21A6,6 0 0,1 3,15A6,6 0 0,1 9,9M9,11A4,4 0 0,0 5,15A4,4 0 0,0 9,19A4,4 0 0,0 13,15A4,4 0 0,0 9,11Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/github-circle.xml -->
|
<!--drawable/github-circle.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#000" android:pathData="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5 0.5 0.08 0.66-0.23 0.66-0.5v-1.69c-2.77 0.6-3.36-1.34-3.36-1.34-0.46-1.16-1.11-1.47-1.11-1.47-0.91-0.62 0.07-0.6 0.07-0.6 1 0.07 1.53 1.03 1.53 1.03 0.87 1.52 2.34 1.07 2.91 0.83 0.09-0.65 0.35-1.09 0.63-1.34-2.22-0.25-4.55-1.11-4.55-4.92 0-1.11 0.38-2 1.03-2.71-0.1-0.25-0.45-1.29 0.1-2.64 0 0 0.84-0.27 2.75 1.02 0.79-0.22 1.65-0.33 2.5-0.33 0.85 0 1.71 0.11 2.5 0.33 1.91-1.29 2.75-1.02 2.75-1.02 0.55 1.35 0.2 2.39 0.1 2.64 0.65 0.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91 0.36 0.31 0.69 0.92 0.69 1.85V21c0 0.27 0.16 0.59 0.67 0.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#000" android:pathData="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
4
app/src/main/res/drawable/history.xml
Normal file
4
app/src/main/res/drawable/history.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!--drawable/history.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="?attr/colorControlNormal" android:pathData="M13.5 8H12v5l4.28 2.54 0.72-1.21-3.5-2.08V8M13 3a9 9 0 0 0-9 9H1l3.96 4.03L9 12H6a7 7 0 0 1 7-7 7 7 0 0 1 7 7 7 7 0 0 1-7 7c-1.93 0-3.68-0.79-4.94-2.06l-1.42 1.42C8.27 20 10.5 21 13 21a9 9 0 0 0 9-9 9 9 0 0 0-9-9"/>
|
||||||
|
</vector>
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
<vector android:height="24dp"
|
<vector android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
<path android:fillColor="#fff" android:pathData="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,74 +1,19 @@
|
|||||||
<?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/>.-->
|
||||||
~ Pupil, Hitomi.la viewer for Android
|
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:aapt="http://schemas.android.com/aapt" tools:ignore="NewApi">
|
||||||
~ 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
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
tools:ignore="NewApi">
|
|
||||||
<aapt:attr name="android:drawable">
|
<aapt:attr name="android:drawable">
|
||||||
<vector
|
<vector android:name="vector" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:name="vector"
|
<path android:name="path" android:pathData="M 19 9 L 15 9 L 15 3 L 9 3 L 9 9 L 5 9 L 12 16 L 19 9 Z" android:fillColor="#fff" android:strokeWidth="1"/>
|
||||||
android:width="24dp"
|
<path android:name="path_2" android:pathData="M 5 19 L 19 19" android:fillColor="#fff" android:strokeColor="#fff" android:strokeWidth="2"/>
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:name="path"
|
|
||||||
android:pathData="M 19 9 L 15 9 L 15 3 L 9 3 L 9 9 L 5 9 L 12 16 L 19 9 Z"
|
|
||||||
android:fillColor="#fff"
|
|
||||||
android:strokeWidth="1"/>
|
|
||||||
<path
|
|
||||||
android:name="path_2"
|
|
||||||
android:pathData="M 5 19 L 19 19"
|
|
||||||
android:fillColor="#fff"
|
|
||||||
android:strokeColor="#fff"
|
|
||||||
android:strokeWidth="2"
|
|
||||||
android:strokeLineCap="butt"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
<target android:name="path_2">
|
<target android:name="path_2">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<set>
|
<set>
|
||||||
<objectAnimator
|
<objectAnimator android:propertyName="trimPathEnd" android:duration="500" android:valueFrom="0" android:valueTo="0.8" android:valueType="floatType" android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||||
android:propertyName="trimPathEnd"
|
<objectAnimator android:propertyName="trimPathStart" android:startOffset="500" android:duration="500" android:valueFrom="0" android:valueTo="0.8" android:valueType="floatType" android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||||
android:duration="500"
|
<objectAnimator android:propertyName="trimPathOffset" android:duration="1000" android:valueFrom="0" android:valueTo="0.2" android:valueType="floatType" android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="0.8"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="trimPathStart"
|
|
||||||
android:startOffset="500"
|
|
||||||
android:duration="500"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="0.8"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
<objectAnimator
|
|
||||||
android:propertyName="trimPathOffset"
|
|
||||||
android:duration="1000"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="0.2"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</set>
|
</set>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
</animated-vector>
|
</animated-vector>
|
||||||
|
|||||||
@@ -1,14 +1,4 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0">
|
||||||
android:width="24dp"
|
<path android:fillColor="#fff" android:pathData="M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73 0.72 5.12 1.88L13 16h9V7l-3.6 3.6z"/>
|
||||||
android:height="24dp"
|
<path android:fillColor="#fff" android:pathData="M8.5 15a1.5 1.5 0 1 1 1.5 1.5A1.5 1.5 0 0 1 8.5 15z"/>
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#fff"
|
|
||||||
android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
|
|
||||||
<path
|
|
||||||
android:fillColor="#fff"
|
|
||||||
android:pathData="M8.5,15
|
|
||||||
a1.5,1.5 0 1,1 1.5,1.5
|
|
||||||
a1.5,1.5 0 0,1 -1.5,-1.5 Z"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/message.xml -->
|
<!--drawable/message.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#000" android:pathData="M20 2H4a2 2 0 0 0-2 2v18l4-4h14a2 2 0 0 0 2-2V4c0-1.11-0.9-2-2-2z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#000" android:pathData="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0">
|
||||||
android:width="24dp"
|
<path android:fillColor="#FF000000" android:pathData="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF000000"
|
|
||||||
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -1,22 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:name="vector" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tintMode="multiply">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
<path android:name="path_1" android:pathData="M 0 12 L 24 12" android:fillColor="#000" android:strokeColor="#b9f6ca" android:strokeWidth="24"/>
|
||||||
android:name="vector"
|
<path android:name="path" android:pathData="M 0 12 L 24 12" android:fillColor="#000" android:strokeColor="#00c853" android:strokeWidth="24"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tintMode="multiply">
|
|
||||||
<path
|
|
||||||
android:name="path_1"
|
|
||||||
android:pathData="M 0 12 L 24 12"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:strokeColor="#b9f6ca"
|
|
||||||
android:strokeWidth="24"/>
|
|
||||||
<path
|
|
||||||
android:name="path"
|
|
||||||
android:pathData="M 0 12 L 24 12"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:strokeColor="#00c853"
|
|
||||||
android:strokeWidth="24"/>
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,22 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:name="vector" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tintMode="multiply">
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
<path android:name="path_1" android:pathData="M 0 12 L 24 12" android:fillColor="#000" android:strokeColor="#80d8ff" android:strokeWidth="24"/>
|
||||||
android:name="vector"
|
<path android:name="path" android:pathData="M 0 12 L 24 12" android:fillColor="#000" android:strokeColor="#0091ea" android:strokeWidth="24"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tintMode="multiply">
|
|
||||||
<path
|
|
||||||
android:name="path_1"
|
|
||||||
android:pathData="M 0 12 L 24 12"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:strokeColor="#80d8ff"
|
|
||||||
android:strokeWidth="24"/>
|
|
||||||
<path
|
|
||||||
android:name="path"
|
|
||||||
android:pathData="M 0 12 L 24 12"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:strokeColor="#0091ea"
|
|
||||||
android:strokeWidth="24"/>
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,38 +1,13 @@
|
|||||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:aapt="http://schemas.android.com/aapt" tools:ignore="NewApi">
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
tools:ignore="NewApi">
|
|
||||||
<aapt:attr name="android:drawable">
|
<aapt:attr name="android:drawable">
|
||||||
<vector
|
<vector android:name="vector" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tintMode="multiply">
|
||||||
android:name="vector"
|
<path android:name="path_1" android:pathData="M 0 12 L 24 12" android:fillColor="#000" android:strokeColor="#b9f6ca" android:strokeWidth="24"/>
|
||||||
android:width="24dp"
|
<path android:name="path" android:pathData="M 0 12 L 24 12" android:fillColor="#000" android:strokeColor="#00c853" android:strokeWidth="24"/>
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tintMode="multiply">
|
|
||||||
<path
|
|
||||||
android:name="path_1"
|
|
||||||
android:pathData="M 0 12 L 24 12"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:strokeColor="#b9f6ca"
|
|
||||||
android:strokeWidth="24"/>
|
|
||||||
<path
|
|
||||||
android:name="path"
|
|
||||||
android:pathData="M 0 12 L 24 12"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:strokeColor="#00c853"
|
|
||||||
android:strokeWidth="24"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
<target android:name="path">
|
<target android:name="path">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator android:propertyName="trimPathEnd" android:duration="1000" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||||
android:propertyName="trimPathEnd"
|
|
||||||
android:duration="1000"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="1"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
</animated-vector>
|
</animated-vector>
|
||||||
|
|||||||
@@ -1,38 +1,13 @@
|
|||||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:aapt="http://schemas.android.com/aapt" tools:ignore="NewApi">
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
tools:ignore="NewApi">
|
|
||||||
<aapt:attr name="android:drawable">
|
<aapt:attr name="android:drawable">
|
||||||
<vector
|
<vector android:name="vector" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tintMode="multiply">
|
||||||
android:name="vector"
|
<path android:name="path_1" android:pathData="M 0 12 L 24 12" android:fillColor="#000" android:strokeColor="#80d8ff" android:strokeWidth="24"/>
|
||||||
android:width="24dp"
|
<path android:name="path" android:pathData="M 0 12 L 24 12" android:fillColor="#000" android:strokeColor="#0091ea" android:strokeWidth="24"/>
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tintMode="multiply">
|
|
||||||
<path
|
|
||||||
android:name="path_1"
|
|
||||||
android:pathData="M 0 12 L 24 12"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:strokeColor="#80d8ff"
|
|
||||||
android:strokeWidth="24"/>
|
|
||||||
<path
|
|
||||||
android:name="path"
|
|
||||||
android:pathData="M 0 12 L 24 12"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:strokeColor="#0091ea"
|
|
||||||
android:strokeWidth="24"/>
|
|
||||||
</vector>
|
</vector>
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
<target android:name="path">
|
<target android:name="path">
|
||||||
<aapt:attr name="android:animation">
|
<aapt:attr name="android:animation">
|
||||||
<objectAnimator
|
<objectAnimator android:propertyName="trimPathEnd" android:duration="1000" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||||
android:propertyName="trimPathEnd"
|
|
||||||
android:duration="1000"
|
|
||||||
android:valueFrom="0"
|
|
||||||
android:valueTo="1"
|
|
||||||
android:valueType="floatType"
|
|
||||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
|
||||||
</aapt:attr>
|
</aapt:attr>
|
||||||
</target>
|
</target>
|
||||||
</animated-vector>
|
</animated-vector>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
<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="@color/material_orange_500" android:pathData="M12 15.39l-3.76 2.27 0.99-4.28-3.32-2.88 4.38-0.37L12 6.09l1.71 4.04 4.38 0.37-3.32 2.88 0.99 4.28M22 9.24l-7.19-0.61L12 2 9.19 8.63 2 9.24l5.45 4.73L5.82 21 12 17.27 18.18 21l-1.64-7.03L22 9.24z"/>
|
||||||
<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,3 @@
|
|||||||
<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="@color/material_orange_500" android:pathData="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-0.62L12 2 9.19 8.62 2 9.24l5.45 4.73L5.82 21 12 17.27z"/>
|
||||||
<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>
|
|
||||||
|
|||||||
30
app/src/main/res/drawable/icon.xml
Normal file
30
app/src/main/res/drawable/icon.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="640"
|
||||||
|
android:viewportHeight="640">
|
||||||
|
<path
|
||||||
|
android:pathData="M640,320C640,496.61 496.61,640 320,640C143.39,640 0,496.61 0,320C0,143.38 143.39,0 320,0C496.61,0 640,143.38 640,320Z"
|
||||||
|
android:fillColor="#4ec1f5"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M420,320C420,375.19 375.19,420 320,420C264.81,420 220,375.19 220,320C220,264.81 264.81,220 320,220C375.19,220 420,264.81 420,320Z"
|
||||||
|
android:fillColor="#1d1d1d"/>
|
||||||
|
</vector>
|
||||||
30
app/src/main/res/drawable/icon_red.xml
Normal file
30
app/src/main/res/drawable/icon_red.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="640"
|
||||||
|
android:viewportHeight="640">
|
||||||
|
<path
|
||||||
|
android:pathData="M640,320C640,496.61 496.61,640 320,640C143.39,640 0,496.61 0,320C0,143.38 143.39,0 320,0C496.61,0 640,143.38 640,320Z"
|
||||||
|
android:fillColor="@color/colorAccent"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M420,320C420,375.19 375.19,420 320,420C264.81,420 220,375.19 220,320C220,264.81 264.81,220 320,220C375.19,220 420,264.81 420,320Z"
|
||||||
|
android:fillColor="#1d1d1d"/>
|
||||||
|
</vector>
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/image_broken_variant.xml -->
|
<!--drawable/image_broken_variant.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#000" android:pathData="M21 5v6.59l-3-3.01-4 4.01-4-4-4 4-3-3.01V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2m-3 6.42l3 3.01V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-6.58l3 2.99 4-4 4 4"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
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>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/lastpass.xml -->
|
<!--drawable/lastpass.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#fff" android:pathData="M14 12a2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1-2 2 2 2 0 0 1-2-2m-6 0a2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1-2 2 2 2 0 0 1-2-2m-6 0a2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1-2 2 2 2 0 0 1-2-2m20-7h-2v14h2V5z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M14,12A2,2 0 0,1 16,10A2,2 0 0,1 18,12A2,2 0 0,1 16,14A2,2 0 0,1 14,12M8,12A2,2 0 0,1 10,10A2,2 0 0,1 12,12A2,2 0 0,1 10,14A2,2 0 0,1 8,12M2,12A2,2 0 0,1 4,10A2,2 0 0,1 6,12A2,2 0 0,1 4,14A2,2 0 0,1 2,12M22,5H20V19H22V5Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/lock_pattern.xml -->
|
<!--drawable/lock_pattern.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#fff" android:pathData="M7 3a4 4 0 0 1 4 4c0 1.86-1.27 3.43-3 3.87v2.26c0.37 0.09 0.72 0.24 1.04 0.43l4.52-4.52C13.2 8.44 13 7.75 13 7a4 4 0 0 1 4-4 4 4 0 0 1 4 4 4 4 0 0 1-4 4c-0.74 0-1.43-0.2-2-0.55L10.45 15C10.8 15.57 11 16.26 11 17a4 4 0 0 1-4 4 4 4 0 0 1-4-4c0-1.86 1.27-3.43 3-3.87v-2.26C4.27 10.43 3 8.86 3 7a4 4 0 0 1 4-4m10 10a4 4 0 0 1 4 4 4 4 0 0 1-4 4 4 4 0 0 1-4-4 4 4 0 0 1 4-4m0 2a2 2 0 0 0-2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M7,3A4,4 0 0,1 11,7C11,8.86 9.73,10.43 8,10.87V13.13C8.37,13.22 8.72,13.37 9.04,13.56L13.56,9.04C13.2,8.44 13,7.75 13,7A4,4 0 0,1 17,3A4,4 0 0,1 21,7A4,4 0 0,1 17,11C16.26,11 15.57,10.8 15,10.45L10.45,15C10.8,15.57 11,16.26 11,17A4,4 0 0,1 7,21A4,4 0 0,1 3,17C3,15.14 4.27,13.57 6,13.13V10.87C4.27,10.43 3,8.86 3,7A4,4 0 0,1 7,3M17,13A4,4 0 0,1 21,17A4,4 0 0,1 17,21A4,4 0 0,1 13,17A4,4 0 0,1 17,13M17,15A2,2 0 0,0 15,17A2,2 0 0,0 17,19A2,2 0 0,0 19,17A2,2 0 0,0 17,15Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/menu.xml -->
|
<!--drawable/menu.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />
|
|
||||||
</vector>
|
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<!-- drawable/numeric.xml -->
|
<!--drawable/numeric.xml-->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
android:height="24dp"
|
<path android:fillColor="#fff" android:pathData="M4 17V9H2V7h4v10H4m18-2c0 1.11-0.9 2-2 2h-4v-2h4v-2h-2v-2h2V9h-4V7h4a2 2 0 0 1 2 2v1.5a1.5 1.5 0 0 1-1.5 1.5 1.5 1.5 0 0 1 1.5 1.5V15m-8 0v2H8v-4c0-1.11 0.9-2 2-2h2V9H8V7h4a2 2 0 0 1 2 2v2c0 1.11-0.9 2-2 2h-2v2h4z"/>
|
||||||
android:width="24dp"
|
</vector>
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<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>
|
|
||||||
|
|||||||
@@ -1,23 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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/>.-->
|
||||||
~ Pupil, Hitomi.la viewer for Android
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||||
~ Copyright (C) 2020 tom5079
|
<solid android:color="@color/colorAccent"/>
|
||||||
~
|
</shape>
|
||||||
~ 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/>.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="oval">
|
|
||||||
<solid android:color="@color/colorAccent" />
|
|
||||||
</shape>
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user