Compare commits
105 Commits
5.0-hotfix
...
5.1.6-hotf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
<option name="useQualifiedModuleNames" value="true" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</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>
|
||||
10
.idea/jarRepositories.xml
generated
10
.idea/jarRepositories.xml
generated
@@ -61,5 +61,15 @@
|
||||
<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>
|
||||
</project>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@@ -2,6 +2,5 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/gh-pages" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
17
README.md
17
README.md
@@ -1,18 +1,12 @@
|
||||
# Pupil
|
||||
|
||||

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

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

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

|
||||
*Reader Screen*
|
||||
|
||||
Images are censored to be SFW
|
||||
# Features
|
||||

|
||||
|
||||
# Installation
|
||||
|
||||
@@ -27,3 +21,6 @@ or Build app yourself
|
||||
# Contribution
|
||||
|
||||
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)
|
||||
|
||||
146
app/build.gradle
146
app/build.gradle
@@ -1,27 +1,44 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "kotlin-android"
|
||||
apply plugin: "kotlin-kapt"
|
||||
apply plugin: "kotlin-android-extensions"
|
||||
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()) {
|
||||
logger.lifecycle("Firebase Enabled")
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'com.google.firebase.firebase-perf'
|
||||
apply plugin: "com.google.gms.google-services"
|
||||
apply plugin: "com.google.firebase.crashlytics"
|
||||
apply plugin: "com.google.firebase.firebase-perf"
|
||||
} else {
|
||||
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 {
|
||||
compileSdkVersion 30
|
||||
defaultConfig {
|
||||
applicationId "xyz.quaver.pupil"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 58
|
||||
versionName "5.0-hotfix2"
|
||||
versionCode 63
|
||||
versionName "5.1.6-hotfix1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
@@ -34,8 +51,7 @@ android {
|
||||
applicationIdSuffix ".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.alwaysUpdateBuildId = false
|
||||
@@ -44,70 +60,78 @@ android {
|
||||
minifyEnabled 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 {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
||||
freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental"
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
buildToolsVersion = '29.0.3'
|
||||
buildToolsVersion = "29.0.3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
||||
//implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0-M1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.activity:activity-ktx:1.2.0-beta01"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
|
||||
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.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.google.android.material:material:1.3.0-alpha02'
|
||||
implementation 'com.google.firebase:firebase-core:17.5.0'
|
||||
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
||||
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
|
||||
implementation 'com.google.firebase:firebase-perf:19.0.8'
|
||||
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
||||
implementation 'com.github.clans:fab:1.6.4'
|
||||
//implementation 'com.quiph.ui:recyclerviewfastscroller:0.2.1'
|
||||
|
||||
implementation "com.google.android.material:material:1.3.0-alpha03"
|
||||
|
||||
implementation "com.google.firebase:firebase-core:17.5.1"
|
||||
implementation "com.google.firebase:firebase-analytics:17.6.0"
|
||||
implementation "com.google.firebase:firebase-crashlytics:17.2.2"
|
||||
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
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||
implementation ("com.github.bumptech.glide:okhttp3-integration:4.11.0") {
|
||||
transitive = false
|
||||
}
|
||||
implementation 'com.github.bumptech.glide:annotations:4.11.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
||||
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.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 "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
|
||||
implementation "com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2"
|
||||
|
||||
implementation "net.rdrei.android.dirchooser:library:3.2@aar"
|
||||
implementation "com.gu:option:1.3"
|
||||
|
||||
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 ("xyz.quaver:libpupil:1.5") {
|
||||
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
|
||||
}
|
||||
implementation "xyz.quaver:documentfilex:0.2.15"
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
|
||||
implementation "xyz.quaver:libpupil:1.8.4"
|
||||
implementation "xyz.quaver:documentfilex:0.4-alpha02"
|
||||
implementation "xyz.quaver:floatingsearchview:1.0.7"
|
||||
|
||||
testImplementation "junit:junit:4.13"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
||||
androidTestImplementation "androidx.test:rules:1.3.0"
|
||||
androidTestImplementation "androidx.test:runner:1.3.0"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
16
app/proguard-rules.pro
vendored
16
app/proguard-rules.pro
vendored
@@ -22,21 +22,6 @@
|
||||
|
||||
-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
|
||||
-dontnote kotlinx.serialization.SerializationKt
|
||||
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
|
||||
@@ -48,4 +33,3 @@
|
||||
}
|
||||
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
||||
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
||||
-keep class xyz.quaver.pupil.util.Preferences
|
||||
@@ -1,19 +1,17 @@
|
||||
{
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "xyz.quaver.pupil",
|
||||
"variantName": "release",
|
||||
"variantName": "processReleaseResources",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"properties": [],
|
||||
"versionCode": 58,
|
||||
"versionName": "5.0-hotfix2",
|
||||
"enabled": true,
|
||||
"versionCode": 63,
|
||||
"versionName": "5.1.6-hotfix1",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="21"/>
|
||||
<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.CAMERA" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
|
||||
<application
|
||||
android:name=".Pupil"
|
||||
@@ -24,6 +27,10 @@
|
||||
tools:replace="android:theme"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.mlkit.vision.DEPENDENCIES"
|
||||
android:value="face" />
|
||||
|
||||
<provider
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.app.*
|
||||
import android.app.Application
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
@@ -26,14 +29,13 @@ import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
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.GooglePlayServicesRepairableException
|
||||
import com.google.android.gms.security.ProviderInstaller
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
@@ -71,11 +73,9 @@ val client: OkHttpClient
|
||||
|
||||
class Pupil : Application() {
|
||||
|
||||
init {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
val userID = Preferences["user_id", ""].let { userID ->
|
||||
@@ -115,25 +115,16 @@ class Pupil : Application() {
|
||||
Preferences.remove("download_folder")
|
||||
}
|
||||
|
||||
if (!Preferences["reset_secure", false]) {
|
||||
Preferences["security_mode"] = false
|
||||
Preferences["reset_secure"] = 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 (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
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG)
|
||||
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
|
||||
|
||||
@@ -145,6 +136,8 @@ class Pupil : Application() {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
BigImageViewer.initialize(FrescoImageLoader.with(this))
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
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,48 +18,42 @@
|
||||
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Log
|
||||
import android.util.SparseBooleanArray
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||
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.adapters.RecyclerSwipeAdapter
|
||||
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
||||
import com.github.piasy.biv.loader.ImageLoader
|
||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.getGallery
|
||||
import xyz.quaver.hitomi.getReader
|
||||
import xyz.quaver.io.util.getChild
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.favoriteTags
|
||||
import xyz.quaver.pupil.favorites
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.ui.view.TagChip
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import xyz.quaver.pupil.util.wordCapitalize
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.concurrent.schedule
|
||||
import java.io.File
|
||||
|
||||
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 {
|
||||
NEXT,
|
||||
@@ -67,33 +61,50 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
PREV
|
||||
}
|
||||
|
||||
val timer = Timer()
|
||||
|
||||
var isThin = false
|
||||
var updateAll = true
|
||||
var thin: Boolean = Preferences["thin"]
|
||||
|
||||
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)
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
if (cache.metadata.reader == null || Preferences["cache_disable"]) {
|
||||
view.galleryblock_progressbar.visibility = View.GONE
|
||||
view.galleryblock_progress_complete.visibility = View.GONE
|
||||
if (cache.metadata.reader == null) {
|
||||
view.galleryblock_progressbar_layout.visibility = View.GONE
|
||||
view.galleryblock_progress_complete.visibility = View.INVISIBLE
|
||||
return@launch
|
||||
}
|
||||
|
||||
with(view.galleryblock_progressbar) {
|
||||
val imageList = cache.metadata.imageList!!
|
||||
|
||||
progress = imageList.filterNotNull().size
|
||||
progress = imageList.count { it != null }
|
||||
max = imageList.size
|
||||
|
||||
with(view.galleryblock_progressbar_layout) {
|
||||
if (visibility == View.GONE)
|
||||
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)
|
||||
|
||||
if (completeFlag.get(galleryID, false)) {
|
||||
@@ -125,9 +136,14 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
}
|
||||
|
||||
fun bind(galleryID: Int) {
|
||||
this.galleryID = galleryID
|
||||
updateProgress(view.context)
|
||||
|
||||
val cache = Cache.getInstance(view.context, galleryID)
|
||||
|
||||
val galleryBlock = cache.metadata.galleryBlock ?: return
|
||||
val galleryBlock = runBlocking {
|
||||
cache.getGalleryBlock()
|
||||
} ?: return
|
||||
|
||||
with(view) {
|
||||
val resources = context.resources
|
||||
@@ -140,63 +156,61 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
val artists = galleryBlock.artists
|
||||
val series = galleryBlock.series
|
||||
|
||||
if (isThin)
|
||||
galleryblock_thumbnail.layoutParams.width = context.resources.getDimensionPixelSize(
|
||||
R.dimen.galleryblock_thumbnail_thin
|
||||
)
|
||||
galleryblock_thumbnail.apply {
|
||||
setOnClickListener {
|
||||
view.performClick()
|
||||
}
|
||||
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() }
|
||||
}
|
||||
}
|
||||
|
||||
galleryblock_thumbnail.setImageDrawable(CircularProgressDrawable(context).also {
|
||||
it.start()
|
||||
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 {
|
||||
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).let {
|
||||
it.cacheFolder.getChild(".thumbnail").let { if (it.exists()) it.delete() }
|
||||
it.downloadFolder?.getChild(".thumbnail")?.let { if (it.exists()) it.delete() }
|
||||
cache.getThumbnail().let { launch(Dispatchers.Main) {
|
||||
showImage(it)
|
||||
} }
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
galleryblock_title.text = galleryBlock.title
|
||||
with(galleryblock_artist) {
|
||||
text = artists.joinToString(", ") { it.wordCapitalize() }
|
||||
text = artists.joinToString { it.wordCapitalize() }
|
||||
visibility = when {
|
||||
artists.isNotEmpty() -> View.VISIBLE
|
||||
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) {
|
||||
text =
|
||||
@@ -218,14 +232,37 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
}
|
||||
}
|
||||
|
||||
galleryblock_tag_group.removeAllViews()
|
||||
galleryBlock.relatedTags.forEach {
|
||||
galleryblock_tag_group.addView(TagChip(context, Tag.parse(it)).apply {
|
||||
setOnClickListener { view ->
|
||||
for (callback in onChipClickedHandler)
|
||||
callback.invoke((view as TagChip).tag)
|
||||
with(galleryblock_tag_group) {
|
||||
onClickListener = {
|
||||
onChipClickedHandler.forEach { callback ->
|
||||
callback.invoke(it)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -268,9 +305,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
|
||||
|
||||
// Make some views invisible to make it thinner
|
||||
if (isThin) {
|
||||
galleryblock_language.visibility = View.GONE
|
||||
galleryblock_type.visibility = View.GONE
|
||||
if (thin) {
|
||||
galleryblock_tag_group.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
@@ -365,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() =
|
||||
galleries.size +
|
||||
(if (showNext) 1 else 0) +
|
||||
|
||||
@@ -18,58 +18,93 @@
|
||||
|
||||
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.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.LazyHeaders
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.facebook.drawee.backends.pipeline.Fresco
|
||||
import com.facebook.drawee.controller.BaseControllerListener
|
||||
import com.facebook.drawee.drawable.ScalingUtils
|
||||
import com.facebook.drawee.interfaces.DraweeController
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import com.facebook.imagepipeline.image.ImageInfo
|
||||
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.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import xyz.quaver.Code
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.getReferer
|
||||
import xyz.quaver.hitomi.imageUrlFromImage
|
||||
import xyz.quaver.hiyobi.createImgList
|
||||
import xyz.quaver.io.util.readBytes
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.services.DownloadService
|
||||
import xyz.quaver.pupil.ui.ReaderActivity
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
import java.io.File
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ReaderAdapter(private val activity: ReaderActivity,
|
||||
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||
class ReaderAdapter(
|
||||
private val activity: ReaderActivity,
|
||||
private val galleryID: Int
|
||||
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||
|
||||
var reader: Reader? = null
|
||||
val timer = Timer()
|
||||
|
||||
private val glide = Glide.with(activity)
|
||||
|
||||
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 {
|
||||
return LayoutInflater.from(parent.context).inflate(
|
||||
R.layout.item_reader, parent, false
|
||||
).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)
|
||||
}
|
||||
}
|
||||
@@ -81,114 +116,134 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
||||
if (cache == null)
|
||||
cache = Cache.getInstance(holder.view.context, galleryID)
|
||||
|
||||
if (isFullScreen) {
|
||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
|
||||
holder.view.container.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||
if (!isFullScreen) {
|
||||
holder.view.setBackgroundResource(R.drawable.reader_item_boundary)
|
||||
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = 0
|
||||
dimensionRatio =
|
||||
"${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
||||
}
|
||||
} else {
|
||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
|
||||
holder.view.container.layoutParams.height = 0
|
||||
|
||||
(holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
|
||||
.dimensionRatio = "W,${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
||||
holder.view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||
dimensionRatio = null
|
||||
}
|
||||
|
||||
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
||||
onItemClickListener?.invoke(position)
|
||||
}
|
||||
|
||||
holder.view.container.setOnClickListener {
|
||||
onItemClickListener?.invoke(position)
|
||||
holder.view.background = null
|
||||
}
|
||||
|
||||
holder.view.reader_index.text = (position+1).toString()
|
||||
|
||||
if (Preferences["cache_disable"]) {
|
||||
val lowQuality: Boolean = Preferences["low_quality"]
|
||||
|
||||
val url = when (reader!!.code) {
|
||||
Code.HITOMI ->
|
||||
GlideUrl(
|
||||
imageUrlFromImage(
|
||||
galleryID,
|
||||
reader!!.galleryInfo.files[position],
|
||||
!lowQuality
|
||||
)
|
||||
, LazyHeaders.Builder().addHeader("Referer", getReferer(galleryID)).build())
|
||||
Code.HIYOBI ->
|
||||
GlideUrl(createImgList(galleryID, reader!!, lowQuality)[position].path)
|
||||
else -> null
|
||||
}
|
||||
holder.view.image.post {
|
||||
glide
|
||||
.load(url!!)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false)
|
||||
.fitCenter()
|
||||
.apply {
|
||||
if (BuildConfig.CENSOR)
|
||||
override(5, 8)
|
||||
}
|
||||
.error(R.drawable.image_broken_variant)
|
||||
.into(holder.view.image)
|
||||
}
|
||||
} else {
|
||||
val image = cache!!.getImage(position)
|
||||
val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
|
||||
|
||||
if (progress?.isInfinite() == true && image != null) {
|
||||
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
glide
|
||||
.load(image.readBytes())
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.fitCenter()
|
||||
.apply {
|
||||
if (BuildConfig.CENSOR)
|
||||
override(5, 8)
|
||||
}
|
||||
.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) } }
|
||||
}
|
||||
holder.view.progress_group.visibility = View.INVISIBLE
|
||||
holder.view.image.showImage(image.uri)
|
||||
} else {
|
||||
holder.view.reader_item_progressbar.visibility = View.VISIBLE
|
||||
|
||||
glide.clear(holder.view.image)
|
||||
|
||||
holder.view.progress_group.visibility = View.VISIBLE
|
||||
holder.view.reader_item_progressbar.progress =
|
||||
if (progress?.isInfinite() == true)
|
||||
100
|
||||
else
|
||||
progress?.roundToInt() ?: 0
|
||||
|
||||
holder.view.image.setImageDrawable(null)
|
||||
holder.clear()
|
||||
|
||||
timer.schedule(1000) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
delay(1000)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import com.github.piasy.biv.view.BigImageView
|
||||
import xyz.quaver.pupil.R
|
||||
|
||||
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 {
|
||||
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) {
|
||||
glide
|
||||
.load(thumbnails[position])
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.apply {
|
||||
if (BuildConfig.CENSOR)
|
||||
override(5, 8)
|
||||
}
|
||||
.into(holder.view)
|
||||
holder.view.showImage(Uri.parse(thumbnails[position]))
|
||||
}
|
||||
|
||||
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 androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.RequestManager
|
||||
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)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(RecyclerView(parent.context).apply {
|
||||
layoutManager = GridLayoutManager(parent.context, 3)
|
||||
adapter = ThumbnailAdapter(glide, listOf())
|
||||
val layoutManager = GridLayoutManager(parent.context, 3)
|
||||
val adapter = ThumbnailAdapter(listOf())
|
||||
|
||||
this.layoutManager = layoutManager
|
||||
this.adapter = adapter
|
||||
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))
|
||||
notifyDataSetChanged()
|
||||
|
||||
holder.view.layoutManager?.scrollToPosition(itemCount-1)
|
||||
(holder.view.layoutManager as GridLayoutManager).scrollToPosition(8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.SparseArray
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
@@ -37,17 +36,19 @@ import okhttp3.Callback
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import okio.*
|
||||
import xyz.quaver.pupil.PupilInterceptor
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.interceptors
|
||||
import xyz.quaver.pupil.*
|
||||
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.DownloadManager
|
||||
import xyz.quaver.pupil.util.ellipsize
|
||||
import xyz.quaver.pupil.util.normalizeID
|
||||
import xyz.quaver.pupil.util.requestBuilders
|
||||
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
|
||||
class DownloadService : Service() {
|
||||
@@ -66,7 +67,7 @@ class DownloadService : Service() {
|
||||
.setOngoing(true)
|
||||
}
|
||||
|
||||
private val notification = SparseArray<NotificationCompat.Builder?>()
|
||||
private val notification = ConcurrentHashMap<Int, NotificationCompat.Builder?>()
|
||||
|
||||
private fun initNotification(galleryID: Int) {
|
||||
val intent = Intent(this, ReaderActivity::class.java)
|
||||
@@ -87,7 +88,7 @@ class DownloadService : Service() {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT),
|
||||
).build()
|
||||
|
||||
notification.put(galleryID, NotificationCompat.Builder(this, "download").apply {
|
||||
notification[galleryID] = NotificationCompat.Builder(this, "download").apply {
|
||||
setContentTitle(getString(R.string.reader_loading))
|
||||
setContentText(getString(R.string.reader_notification_text))
|
||||
setSmallIcon(R.drawable.ic_notification)
|
||||
@@ -95,7 +96,7 @@ class DownloadService : Service() {
|
||||
addAction(action)
|
||||
setProgress(0, 0, true)
|
||||
setOngoing(true)
|
||||
})
|
||||
}
|
||||
|
||||
notify(galleryID)
|
||||
}
|
||||
@@ -120,7 +121,7 @@ class DownloadService : Service() {
|
||||
.setProgress(max, progress, false)
|
||||
.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()) }
|
||||
else
|
||||
notificationManager.cancel(galleryID)
|
||||
@@ -194,7 +195,8 @@ class DownloadService : Service() {
|
||||
* 0 <= value < 100 -> Download in progress
|
||||
* 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
|
||||
|
||||
@@ -215,11 +217,12 @@ class DownloadService : Service() {
|
||||
val ext = call.request().url().encodedPath().split('.').last()
|
||||
|
||||
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 {
|
||||
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 {
|
||||
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
||||
notify(galleryID)
|
||||
@@ -285,14 +288,16 @@ class DownloadService : Service() {
|
||||
fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
|
||||
cancel(galleryID)
|
||||
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
||||
Cache.delete(galleryID)
|
||||
Cache.delete(this@DownloadService, galleryID)
|
||||
|
||||
startId?.let { stopSelf(it) }
|
||||
}
|
||||
|
||||
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
|
||||
if (progress.indexOfKey(galleryID) >= 0)
|
||||
cancel(galleryID)
|
||||
if (DownloadManager.getInstance(this@DownloadService).isDownloading(galleryID))
|
||||
return@launch
|
||||
|
||||
cleanCache(this@DownloadService)
|
||||
|
||||
val cache = Cache.getInstance(this@DownloadService, galleryID)
|
||||
|
||||
@@ -303,17 +308,25 @@ class DownloadService : Service() {
|
||||
// Gallery doesn't exist
|
||||
if (reader == null) {
|
||||
delete(galleryID)
|
||||
progress.put(galleryID, null)
|
||||
progress[galleryID] = mutableListOf()
|
||||
return@launch
|
||||
}
|
||||
|
||||
progress.put(galleryID, MutableList(reader.galleryInfo.files.size) { 0F })
|
||||
histories.add(galleryID)
|
||||
|
||||
cache.metadata.imageList?.forEachIndexed { index, image ->
|
||||
progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 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
|
||||
@@ -334,7 +347,7 @@ class DownloadService : Service() {
|
||||
}
|
||||
|
||||
reader.requestBuilders.forEachIndexed { index, it ->
|
||||
if (progress[galleryID]?.get(index)?.isInfinite() != true) {
|
||||
if (progress[galleryID]?.get(index)?.isInfinite() == false) {
|
||||
val request = it.tag(Tag(galleryID, index, startId)).build()
|
||||
client.newCall(request).enqueue(callback)
|
||||
}
|
||||
|
||||
@@ -18,36 +18,33 @@
|
||||
|
||||
package xyz.quaver.pupil.types
|
||||
|
||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import kotlinx.android.parcel.IgnoredOnParcel
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import xyz.quaver.hitomi.Suggestion
|
||||
import xyz.quaver.pupil.util.translations
|
||||
|
||||
@Parcelize
|
||||
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)
|
||||
|
||||
override fun getBody(): String {
|
||||
return s
|
||||
}
|
||||
@IgnoredOnParcel
|
||||
override val body =
|
||||
if (translations[s] != null)
|
||||
"${translations[s]} ($s)"
|
||||
else
|
||||
s
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class Suggestion(val str: String) : SearchSuggestion {
|
||||
override fun getBody() = str
|
||||
}
|
||||
class Suggestion(override val body: String) : SearchSuggestion
|
||||
|
||||
@Parcelize
|
||||
class NoResultSuggestion(val str: String) : SearchSuggestion {
|
||||
override fun getBody() = str
|
||||
}
|
||||
class NoResultSuggestion(override val body: String) : SearchSuggestion
|
||||
|
||||
@Parcelize
|
||||
class LoadingSuggestion(val str: String) : SearchSuggestion {
|
||||
override fun getBody() = str
|
||||
}
|
||||
class LoadingSuggestion(override val body: String) : SearchSuggestion
|
||||
|
||||
@Parcelize
|
||||
@Suppress("PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY")
|
||||
class FavoriteHistorySwitch(private val body: String) : SearchSuggestion {
|
||||
override fun getBody() = body
|
||||
}
|
||||
class FavoriteHistorySwitch(override val body: String) : SearchSuggestion
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
@@ -34,6 +35,13 @@ 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)
|
||||
@@ -53,20 +61,7 @@ open class BaseActivity : AppCompatActivity() {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
|
||||
if (locked)
|
||||
startActivityForResult(Intent(this, LockActivity::class.java), R.id.request_lock.normalizeID())
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when(requestCode) {
|
||||
R.id.request_lock.normalizeID() -> {
|
||||
if (resultCode == Activity.RESULT_OK)
|
||||
locked = false
|
||||
else
|
||||
finish()
|
||||
}
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
lockLauncher.launch(Intent(this, LockActivity::class.java))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,10 +39,10 @@ import xyz.quaver.pupil.util.Lock
|
||||
import xyz.quaver.pupil.util.LockManager
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
|
||||
private var lastUnlocked = 0L
|
||||
class LockActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var lockManager: LockManager
|
||||
private var lastUnlocked = 0L
|
||||
private var mode: String? = null
|
||||
|
||||
private val patternLockFragment = PatternLockFragment().apply {
|
||||
|
||||
@@ -23,16 +23,19 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.*
|
||||
import android.widget.*
|
||||
import android.text.util.Linkify
|
||||
import android.view.KeyEvent
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.view.GravityCompat
|
||||
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.navigation.NavigationView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -40,6 +43,10 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.activity_main_content.*
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.floatingsearchview.FloatingSearchView
|
||||
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import xyz.quaver.floatingsearchview.util.view.MenuView
|
||||
import xyz.quaver.floatingsearchview.util.view.SearchInputView
|
||||
import xyz.quaver.hitomi.doSearch
|
||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||
import xyz.quaver.hitomi.getSuggestionsForQuery
|
||||
@@ -49,9 +56,13 @@ import xyz.quaver.pupil.services.DownloadService
|
||||
import xyz.quaver.pupil.types.*
|
||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
||||
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.DownloadManager
|
||||
import xyz.quaver.pupil.util.restore
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
@@ -59,7 +70,6 @@ import kotlin.math.roundToInt
|
||||
|
||||
class MainActivity :
|
||||
BaseActivity(),
|
||||
FloatingSearchView.OnMenuItemClickListener,
|
||||
NavigationView.OnNavigationItemSelectedListener
|
||||
{
|
||||
|
||||
@@ -95,9 +105,10 @@ class MainActivity :
|
||||
private var loadingJob: Job? = null
|
||||
private var currentPage = 0
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
if (intent.action == Intent.ACTION_VIEW) {
|
||||
intent.dataString?.let { url ->
|
||||
@@ -111,8 +122,6 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
if (Preferences["download_folder", ""].isEmpty())
|
||||
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
||||
|
||||
@@ -140,7 +149,7 @@ class MainActivity :
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel()
|
||||
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.updateAll = false
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
@@ -180,20 +189,6 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when(requestCode) {
|
||||
R.id.request_settings.normalizeID() -> {
|
||||
runOnUiThread {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
}
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
var prevP1 = 0
|
||||
main_appbar_layout.addOnOffsetChangedListener(
|
||||
@@ -212,6 +207,8 @@ class MainActivity :
|
||||
}
|
||||
)
|
||||
|
||||
Linkify.addLinks(main_noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) })
|
||||
|
||||
//NavigationView
|
||||
main_nav_view.setNavigationItemSelectedListener(this)
|
||||
|
||||
@@ -261,11 +258,7 @@ class MainActivity :
|
||||
if (it?.isEmpty() == false) {
|
||||
val galleryID = it.random()
|
||||
|
||||
GalleryDialog(
|
||||
this@MainActivity,
|
||||
Glide.with(this@MainActivity),
|
||||
galleryID
|
||||
).apply {
|
||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
@@ -297,11 +290,21 @@ class MainActivity :
|
||||
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
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()
|
||||
}
|
||||
@@ -316,7 +319,7 @@ class MainActivity :
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun setupRecyclerView() {
|
||||
with(main_recyclerview) {
|
||||
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply {
|
||||
adapter = GalleryBlockAdapter(galleries).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
@@ -330,9 +333,7 @@ class MainActivity :
|
||||
}
|
||||
onDownloadClickedHandler = { position ->
|
||||
val galleryID = galleries[position]
|
||||
if (Preferences["cache_disable"])
|
||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
||||
else {
|
||||
|
||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
||||
DownloadService.cancel(this@MainActivity, galleryID)
|
||||
}
|
||||
@@ -340,7 +341,6 @@ class MainActivity :
|
||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||
DownloadService.download(this@MainActivity, galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
closeAllItems()
|
||||
}
|
||||
@@ -380,13 +380,9 @@ class MainActivity :
|
||||
if (v !is CardView)
|
||||
return@listener false
|
||||
|
||||
val galleryID = galleries[position]
|
||||
val galleryID = galleries.getOrNull(position) ?: return@listener true
|
||||
|
||||
GalleryDialog(
|
||||
this@MainActivity,
|
||||
Glide.with(this@MainActivity),
|
||||
galleryID
|
||||
).apply {
|
||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
@@ -619,14 +615,14 @@ class MainActivity :
|
||||
else -> {
|
||||
searchHistory.map {
|
||||
Suggestion(it)
|
||||
}.takeLast(20) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
|
||||
}.takeLast(10) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
|
||||
}
|
||||
}.reversed()
|
||||
|
||||
private var suggestionJob : Job? = null
|
||||
private fun setupSearchBar() {
|
||||
with(main_searchview as FloatingSearchViewDayNight) {
|
||||
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
|
||||
with(main_searchview as xyz.quaver.pupil.ui.view.FloatingSearchView) {
|
||||
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
|
||||
override fun onMenuOpened() {
|
||||
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
||||
}
|
||||
@@ -634,7 +630,15 @@ class MainActivity :
|
||||
override fun onMenuClosed() {
|
||||
//Do Nothing
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
post {
|
||||
findViewById<MenuView>(R.id.menu_view).menuItems.firstOrNull {
|
||||
(it as MenuItem).itemId == R.id.main_menu_thin
|
||||
}?.let {
|
||||
(it as MenuItem).isChecked = Preferences["thin"]
|
||||
}
|
||||
}
|
||||
|
||||
onHistoryDeleteClickedListener = {
|
||||
searchHistory.remove(it)
|
||||
@@ -645,9 +649,11 @@ class MainActivity :
|
||||
swapSuggestions(defaultSuggestions)
|
||||
}
|
||||
|
||||
setOnMenuItemClickListener(this@MainActivity)
|
||||
onMenuItemClickListener = {
|
||||
onActionMenuItemSelected(it)
|
||||
}
|
||||
|
||||
setOnQueryChangeListener { _, query ->
|
||||
onQueryChangeListener = lambda@{ _, query ->
|
||||
this@MainActivity.query = query
|
||||
|
||||
suggestionJob?.cancel()
|
||||
@@ -655,12 +661,14 @@ class MainActivity :
|
||||
if (query.isEmpty() or query.endsWith(' ')) {
|
||||
swapSuggestions(defaultSuggestions)
|
||||
|
||||
return@setOnQueryChangeListener
|
||||
return@lambda
|
||||
}
|
||||
|
||||
swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString())))
|
||||
|
||||
val currentQuery = query.split(" ").last().replace('_', ' ')
|
||||
val currentQuery = query.split(" ").last()
|
||||
.replace(Regex("^-"), "")
|
||||
.replace('_', ' ')
|
||||
|
||||
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
val suggestions = kotlin.runCatching {
|
||||
@@ -681,7 +689,7 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
|
||||
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
||||
onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener {
|
||||
override fun onFocus() {
|
||||
if (query.isEmpty() or query.endsWith(' '))
|
||||
swapSuggestions(defaultSuggestions)
|
||||
@@ -698,19 +706,24 @@ class MainActivity :
|
||||
loadBlocks()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
attachNavigationDrawerToMenuButton(main_drawer_layout)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActionMenuItemSelected(item: MenuItem?) {
|
||||
fun onActionMenuItemSelected(item: MenuItem?) {
|
||||
when(item?.itemId) {
|
||||
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), R.id.request_settings.normalizeID())
|
||||
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 {
|
||||
isThin = !isThin
|
||||
this.thin = thin
|
||||
|
||||
Preferences["thin"] = thin
|
||||
}
|
||||
|
||||
adapter = adapter // Force to redraw
|
||||
@@ -968,14 +981,4 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
Glide.get(this).onLowMemory()
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
Glide.get(this).onTrimMemory(level)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,44 +18,52 @@
|
||||
|
||||
package xyz.quaver.pupil.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
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.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.PagerSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
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.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.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import xyz.quaver.Code
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||
import xyz.quaver.pupil.favorites
|
||||
import xyz.quaver.pupil.histories
|
||||
import xyz.quaver.pupil.services.DownloadService
|
||||
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.DownloadManager
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
import kotlin.concurrent.timer
|
||||
import xyz.quaver.pupil.util.startCamera
|
||||
|
||||
class ReaderActivity : BaseActivity() {
|
||||
|
||||
@@ -68,18 +76,18 @@ class ReaderActivity : BaseActivity() {
|
||||
field = value
|
||||
|
||||
(reader_recyclerview.adapter as ReaderAdapter).isFullScreen = value
|
||||
|
||||
reader_progressbar.visibility = when {
|
||||
value -> View.VISIBLE
|
||||
else -> View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var cache: Cache
|
||||
var downloader: DownloadService? = null
|
||||
private val conn = object: ServiceConnection {
|
||||
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?) {
|
||||
@@ -87,13 +95,29 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private val timer = Timer()
|
||||
private var autoTimer: Timer? = null
|
||||
|
||||
private val snapHelper = PagerSnapHelper()
|
||||
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_reader)
|
||||
@@ -110,38 +134,7 @@ class ReaderActivity : BaseActivity() {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -218,17 +211,32 @@ class ReaderActivity : BaseActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
timer.cancel()
|
||||
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
|
||||
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
|
||||
|
||||
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
|
||||
DownloadService.cancel(this, galleryID)
|
||||
if (cameraEnabled)
|
||||
startCamera(this, cameraCallback)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
closeCamera()
|
||||
|
||||
if (downloader != null)
|
||||
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() {
|
||||
@@ -250,7 +258,7 @@ class ReaderActivity : BaseActivity() {
|
||||
//currentPage is 1-based
|
||||
return when(keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage-2, 0)
|
||||
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-2, 0)
|
||||
|
||||
true
|
||||
}
|
||||
@@ -263,48 +271,51 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initDownloader() {
|
||||
DownloadService.download(this, galleryID, true)
|
||||
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
|
||||
private var update = true
|
||||
private fun initDownloadListener() {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
while (update) {
|
||||
delay(1000)
|
||||
|
||||
timer.schedule(1000, 1000) {
|
||||
val downloader = downloader ?: return@schedule
|
||||
val downloader = downloader ?: continue
|
||||
|
||||
if (downloader.progress.indexOfKey(galleryID) < 0) //loading
|
||||
return@schedule
|
||||
if (!downloader.progress.containsKey(galleryID)) //loading
|
||||
continue
|
||||
|
||||
if (downloader.progress[galleryID] == null) { //Gallery not found
|
||||
timer.cancel()
|
||||
if (downloader.progress[galleryID]?.isEmpty() == true) { //Gallery not found
|
||||
update = false
|
||||
Snackbar
|
||||
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||
.show()
|
||||
|
||||
return@launch
|
||||
}
|
||||
|
||||
histories.add(galleryID)
|
||||
|
||||
runOnUiThread {
|
||||
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||
reader_download_progressbar.progress = downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
||||
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||
reader_download_progressbar.progress =
|
||||
downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
||||
|
||||
if (title == getString(R.string.reader_loading)) {
|
||||
val reader = cache.metadata.reader
|
||||
|
||||
if (reader != null) {
|
||||
with (reader_recyclerview.adapter as ReaderAdapter) {
|
||||
with(reader_recyclerview.adapter as ReaderAdapter) {
|
||||
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_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) {
|
||||
Code.HITOMI -> R.drawable.hitomi
|
||||
Code.HIYOBI -> R.drawable.ic_hiyobi
|
||||
else -> android.R.color.transparent
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +339,7 @@ class ReaderActivity : BaseActivity() {
|
||||
scrollMode(false)
|
||||
fullscreen(true)
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,7 +359,6 @@ class ReaderActivity : BaseActivity() {
|
||||
return
|
||||
currentPage = layoutManager.findFirstVisibleItemPosition()+1
|
||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}"
|
||||
this@ReaderActivity.reader_progressbar.progress = currentPage
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -357,9 +367,6 @@ class ReaderActivity : BaseActivity() {
|
||||
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
||||
|
||||
setOnClickListener {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
|
||||
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)) {
|
||||
@@ -367,40 +374,35 @@ class ReaderActivity : BaseActivity() {
|
||||
animateDownloadFAB(false)
|
||||
} else {
|
||||
downloadManager.addDownloadFolder(galleryID)
|
||||
DownloadService.download(context, galleryID, true)
|
||||
animateDownloadFAB(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(reader_fab_retry) {
|
||||
setImageResource(R.drawable.refresh)
|
||||
setOnClickListener {
|
||||
downloader?.cancel(galleryID)
|
||||
downloader?.download(galleryID)
|
||||
DownloadService.download(context, galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
with(reader_fab_auto) {
|
||||
setImageResource(R.drawable.clock_start)
|
||||
setImageResource(R.drawable.eye_white)
|
||||
setOnClickListener {
|
||||
if (autoTimer == null) {
|
||||
autoTimer = timer(initialDelay = 10000L, period = 10000L) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
with(this@ReaderActivity.reader_recyclerview) {
|
||||
val lastItem =
|
||||
(layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
|
||||
|
||||
if (lastItem < adapter!!.itemCount - 1)
|
||||
(layoutManager as LinearLayoutManager).scrollToPosition(lastItem + 1)
|
||||
when {
|
||||
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED -> {
|
||||
toggleCamera()
|
||||
}
|
||||
Build.VERSION.SDK_INT >= 23 && shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
|
||||
AlertDialog.Builder(this@ReaderActivity)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(R.string.camera_denied)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
setImageResource(R.drawable.clock_end)
|
||||
} else {
|
||||
autoTimer?.cancel()
|
||||
autoTimer = null
|
||||
setImageResource(R.drawable.clock_start)
|
||||
else ->
|
||||
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,10 +424,22 @@ class ReaderActivity : BaseActivity() {
|
||||
flags = flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
supportActionBar?.hide()
|
||||
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 {
|
||||
flags = flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
|
||||
supportActionBar?.show()
|
||||
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
|
||||
@@ -440,7 +454,11 @@ class ReaderActivity : BaseActivity() {
|
||||
reader_recyclerview.layoutManager = LinearLayoutManager(this)
|
||||
} else {
|
||||
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)
|
||||
@@ -475,13 +493,119 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
Glide.get(this).onLowMemory()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
Glide.get(this).onTrimMemory(level)
|
||||
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,24 +18,10 @@
|
||||
|
||||
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.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
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.util.Preferences
|
||||
import xyz.quaver.pupil.util.normalizeID
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
|
||||
@@ -56,19 +42,4 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@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")
|
||||
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 {
|
||||
Cache.getInstance(requireContext(), galleryID).getGalleryBlock()
|
||||
}
|
||||
|
||||
@@ -26,26 +26,87 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.DialogFragment
|
||||
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 net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
||||
import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.toFile
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.byteToString
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import xyz.quaver.pupil.util.migrate
|
||||
import xyz.quaver.pupil.util.normalizeID
|
||||
import java.io.File
|
||||
|
||||
class DownloadLocationDialogFragment : DialogFragment() {
|
||||
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")
|
||||
private fun build() : View? {
|
||||
val context = context ?: return null
|
||||
@@ -90,7 +151,7 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
||||
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!
|
||||
val config = DirectoryChooserConfig.builder()
|
||||
.newDirectoryName("Pupil")
|
||||
@@ -101,7 +162,7 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
||||
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
|
||||
}
|
||||
|
||||
startActivityForResult(intent, R.id.request_download_folder_old.normalizeID())
|
||||
requestDownloadFolderOldLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
entries[null] = this
|
||||
@@ -132,65 +193,4 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
||||
|
||||
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
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout.LayoutParams
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.dialog_gallery.*
|
||||
import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
|
||||
@@ -41,10 +42,10 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import xyz.quaver.hitomi.Gallery
|
||||
import xyz.quaver.hitomi.getGallery
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
||||
import xyz.quaver.pupil.favoriteTags
|
||||
import xyz.quaver.pupil.histories
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
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.downloader.Cache
|
||||
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))>()
|
||||
|
||||
@@ -74,7 +77,6 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryID", galleryID)
|
||||
})
|
||||
histories.add(galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,19 +106,19 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
}
|
||||
}
|
||||
|
||||
glide
|
||||
.load(gallery.cover)
|
||||
.apply {
|
||||
if (BuildConfig.CENSOR)
|
||||
override(5, 8)
|
||||
}.into(gallery_cover)
|
||||
gallery_cover.showImage(Uri.parse(gallery.cover))
|
||||
|
||||
addDetails(gallery)
|
||||
addThumbnails(gallery)
|
||||
addRelated(gallery)
|
||||
}
|
||||
} 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) },
|
||||
gallery.series.map { Tag("series", 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 ->
|
||||
when {
|
||||
tag.area != null -> tag
|
||||
@@ -183,7 +196,8 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
gallery_details.setText(R.string.gallery_thumbnails)
|
||||
|
||||
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(
|
||||
@@ -203,7 +217,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val galleries = ArrayList<Int>()
|
||||
|
||||
val adapter = GalleryBlockAdapter(glide, galleries).apply {
|
||||
val adapter = GalleryBlockAdapter(galleries).apply {
|
||||
onChipClickedHandler.add { tag ->
|
||||
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||
handler.invoke(tag)
|
||||
@@ -223,14 +237,9 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryID", galleries[position])
|
||||
})
|
||||
histories.add(galleries[position])
|
||||
}
|
||||
onItemLongClickListener = { _, position, _ ->
|
||||
GalleryDialog(
|
||||
context,
|
||||
glide,
|
||||
galleries[position]
|
||||
).apply {
|
||||
GalleryDialog(context, galleries[position]).apply {
|
||||
onChipClickedHandler.add { tag ->
|
||||
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlinx.android.synthetic.main.dialog_proxy.view.*
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
@@ -40,11 +41,10 @@ import xyz.quaver.pupil.util.getProxyInfo
|
||||
import xyz.quaver.pupil.util.proxyInfo
|
||||
import java.net.Proxy
|
||||
|
||||
class ProxyDialog(context: Context) : Dialog(context) {
|
||||
class ProxyDialog(context: Context) : AlertDialog(context) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setContentView(build())
|
||||
window?.attributes?.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
setView(build())
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
@@ -62,6 +62,11 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
if (response.code() != 200) {
|
||||
response.close()
|
||||
return
|
||||
}
|
||||
|
||||
Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", ""))
|
||||
|
||||
@@ -31,6 +31,7 @@ import xyz.quaver.io.util.deleteRecursively
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.histories
|
||||
import xyz.quaver.pupil.util.byteToString
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import java.io.File
|
||||
|
||||
@@ -61,6 +62,8 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
||||
if (dir.exists())
|
||||
dir.deleteRecursively()
|
||||
|
||||
Cache.instances.clear()
|
||||
|
||||
summary = context.getString(R.string.settings_storage_usage, byteToString(0))
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var size = 0L
|
||||
|
||||
@@ -22,25 +22,22 @@ import android.app.Activity
|
||||
import android.content.*
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.preference.*
|
||||
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.getChild
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.favorites
|
||||
import xyz.quaver.pupil.ui.LockActivity
|
||||
import xyz.quaver.pupil.ui.SettingsActivity
|
||||
import xyz.quaver.pupil.ui.dialog.*
|
||||
import xyz.quaver.pupil.util.*
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
|
||||
class SettingsFragment :
|
||||
PreferenceFragmentCompat(),
|
||||
@@ -48,6 +45,16 @@ class SettingsFragment :
|
||||
Preference.OnPreferenceChangeListener,
|
||||
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() {
|
||||
super.onResume()
|
||||
|
||||
@@ -89,7 +96,7 @@ class SettingsFragment :
|
||||
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||
putExtra("force", true)
|
||||
}
|
||||
startActivityForResult(intent, R.id.request_lock.normalizeID())
|
||||
lockLauncher.launch(intent)
|
||||
}
|
||||
"mirrors" -> {
|
||||
MirrorDialog(requireContext())
|
||||
@@ -103,7 +110,7 @@ class SettingsFragment :
|
||||
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
|
||||
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
|
||||
}
|
||||
@@ -117,6 +124,9 @@ class SettingsFragment :
|
||||
this ?: return false
|
||||
|
||||
when (key) {
|
||||
"tag_translation" -> {
|
||||
updateTranslations()
|
||||
}
|
||||
"nomedia" -> {
|
||||
val create = (newValue as? Boolean) ?: return false
|
||||
|
||||
@@ -237,6 +247,27 @@ class 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" -> {
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
@@ -267,19 +298,4 @@ class SettingsFragment :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when(requestCode) {
|
||||
R.id.request_lock.normalizeID() -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
parentFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, LockSettingsFragment())
|
||||
.addToBackStack("Lock")
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.arlib.floatingsearchview
|
||||
package xyz.quaver.pupil.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.PorterDuff
|
||||
@@ -36,32 +36,34 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter
|
||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import com.arlib.floatingsearchview.util.view.SearchInputView
|
||||
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 FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
FloatingSearchView(context, attrs),
|
||||
FloatingSearchView.OnSearchListener,
|
||||
SearchSuggestionsAdapter.OnBindSuggestionCallback,
|
||||
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
|
||||
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI or searchInputView.imeOptions
|
||||
|
||||
searchInputView.addTextChangedListener(this)
|
||||
setOnSearchListener(this)
|
||||
setOnBindSuggestionCallback(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) {
|
||||
@@ -82,15 +84,18 @@ class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, att
|
||||
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
||||
when (searchSuggestion) {
|
||||
is TagSuggestion -> {
|
||||
with(searchInputView.text) {
|
||||
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
||||
append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ")
|
||||
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) {
|
||||
with(searchInputView.text!!) {
|
||||
clear()
|
||||
append(searchSuggestion.str)
|
||||
append(searchSuggestion.body)
|
||||
}
|
||||
}
|
||||
is FavoriteHistorySwitch -> onFavoriteHistorySwitchClickListener?.invoke()
|
||||
@@ -99,7 +104,7 @@ class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, att
|
||||
|
||||
override fun onSearchAction(currentQuery: String?) {}
|
||||
|
||||
override fun onBindSuggestion(
|
||||
fun onBindSuggestion(
|
||||
suggestionView: View?,
|
||||
leftIcon: ImageView?,
|
||||
textView: TextView?,
|
||||
@@ -159,9 +164,7 @@ class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, att
|
||||
}
|
||||
}
|
||||
|
||||
if (item.t == -1) {
|
||||
textView?.text = item.s
|
||||
} else {
|
||||
if (item.t > 0) {
|
||||
(suggestionView as? LinearLayout)?.let {
|
||||
val count = it.findViewById<TextView>(R.id.count)
|
||||
if (count == null)
|
||||
@@ -196,7 +199,7 @@ class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, att
|
||||
isClickable = true
|
||||
|
||||
setOnClickListener {
|
||||
onHistoryDeleteClickedListener?.invoke(item.str)
|
||||
onHistoryDeleteClickedListener?.invoke(item.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,10 +215,4 @@ class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, att
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hack to remove color attributes which should not be reused
|
||||
override fun onSaveInstanceState(): Parcelable? {
|
||||
super.onSaveInstanceState()
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -23,17 +23,19 @@ import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.chip.Chip
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.favoriteTags
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.util.translations
|
||||
import xyz.quaver.pupil.util.wordCapitalize
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class TagChip(context: Context, tag: Tag) : Chip(context) {
|
||||
class TagChip(context: Context, _tag: Tag) : Chip(context) {
|
||||
|
||||
val tag: Tag =
|
||||
tag.let {
|
||||
_tag.let {
|
||||
when {
|
||||
it.area != null -> it
|
||||
else -> Tag("tag", tag.tag)
|
||||
else -> Tag("tag", _tag.tag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,23 +46,52 @@ class TagChip(context: Context, tag: Tag) : Chip(context) {
|
||||
}.toMap()
|
||||
|
||||
init {
|
||||
chipIcon = when(tag.area) {
|
||||
when(tag.area) {
|
||||
"male" -> {
|
||||
setChipBackgroundColorResource(R.color.material_blue_700)
|
||||
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" -> {
|
||||
setChipBackgroundColorResource(R.color.material_pink_600)
|
||||
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) {
|
||||
"language" -> languages[tag.tag]
|
||||
else -> tag.tag.wordCapitalize()
|
||||
else -> (translations[tag.tag] ?: tag.tag).wordCapitalize()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ 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 {
|
||||
|
||||
@@ -38,8 +39,8 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
|
||||
load()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun load() {
|
||||
synchronized(this) {
|
||||
set.clear()
|
||||
kotlin.runCatching {
|
||||
Json.decodeFromString(serializer, file.readText())
|
||||
@@ -47,31 +48,36 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
|
||||
set.addAll(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
fun save() {
|
||||
synchronized(this) {
|
||||
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()
|
||||
|
||||
@@ -80,6 +86,7 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
|
||||
}
|
||||
}
|
||||
|
||||
@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,297 +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
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@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,383 +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.createImgList
|
||||
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
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@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)
|
||||
}
|
||||
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,46 +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
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@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.ContextWrapper
|
||||
import android.util.SparseArray
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
@@ -37,6 +39,9 @@ import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.*
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
@@ -50,7 +55,7 @@ data class Metadata(
|
||||
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
||||
|
||||
companion object {
|
||||
val instances = SparseArray<Cache>()
|
||||
val instances = ConcurrentHashMap<Int, Cache>()
|
||||
|
||||
fun getInstance(context: Context, galleryID: Int) =
|
||||
instances[galleryID] ?: synchronized(this) {
|
||||
@@ -58,9 +63,9 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun delete(galleryID: Int) {
|
||||
instances[galleryID]?.cacheFolder?.deleteRecursively()
|
||||
instances.delete(galleryID)
|
||||
fun delete(context: Context, galleryID: Int) {
|
||||
File(context.cacheDir, "imageCache/$galleryID").deleteRecursively()
|
||||
instances.remove(galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,19 +135,24 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun getThumbnail(): ByteArray? =
|
||||
findFile(".thumbnail")?.readBytes()
|
||||
suspend fun getThumbnail(): Uri =
|
||||
findFile(".thumbnail")?.uri
|
||||
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
|
||||
kotlin.runCatching {
|
||||
val request = Request.Builder()
|
||||
.url(it)
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().body()?.use { it.bytes() }
|
||||
}.getOrNull()?.also { kotlin.run {
|
||||
cacheFolder.getChild(".thumbnail").writeBytes(it)
|
||||
} }
|
||||
} }
|
||||
client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() }
|
||||
}.getOrNull()?.let { thumbnail -> kotlin.runCatching {
|
||||
cacheFolder.getChild(".thumbnail").also {
|
||||
if (!it.exists())
|
||||
it.createNewFile()
|
||||
|
||||
it.writeBytes(thumbnail)
|
||||
}
|
||||
}.getOrNull()?.uri }
|
||||
} } ?: Uri.EMPTY
|
||||
|
||||
suspend fun getReader(): Reader? {
|
||||
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
||||
@@ -184,21 +194,57 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
|
||||
fun getImage(index: Int): FileX? =
|
||||
metadata.imageList?.get(index)?.let { findFile(it) }
|
||||
metadata.imageList?.getOrNull(index)?.let { findFile(it) }
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
fun putImage(index: Int, fileName: String, data: ByteArray) {
|
||||
val file = cacheFolder.getChild(fileName)
|
||||
|
||||
if (!file.exists())
|
||||
file.createNewFile()
|
||||
file.writeBytes(data)
|
||||
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
||||
}
|
||||
|
||||
private val lock = ConcurrentHashMap<Int, Mutex>()
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
||||
val downloadFolder = downloadFolder ?: return@launch
|
||||
|
||||
if (lock[galleryID]?.isLocked == true)
|
||||
return@launch
|
||||
|
||||
(lock[galleryID] ?: Mutex().also { lock[galleryID] = it }).withLock {
|
||||
val cacheMetadata = cacheFolder.getChild(".metadata")
|
||||
val downloadMetadata = downloadFolder.getChild(".metadata")
|
||||
|
||||
if (!cacheMetadata.exists())
|
||||
return@launch
|
||||
|
||||
if (cacheMetadata.exists()) {
|
||||
kotlin.runCatching {
|
||||
if (!downloadMetadata.exists())
|
||||
downloadMetadata.createNewFile()
|
||||
|
||||
downloadMetadata.writeText(Json.encodeToString(metadata))
|
||||
}
|
||||
}
|
||||
|
||||
val cacheThumbnail = cacheFolder.getChild(".thumbnail")
|
||||
val downloadThumbnail = downloadFolder.getChild(".thumbnail")
|
||||
|
||||
if (cacheThumbnail.exists()) {
|
||||
kotlin.runCatching {
|
||||
if (!downloadThumbnail.exists())
|
||||
downloadThumbnail.createNewFile()
|
||||
|
||||
downloadThumbnail.outputStream()?.use { target -> target.channel.truncate(0L); cacheThumbnail.inputStream()?.use { source ->
|
||||
source.copyTo(target)
|
||||
} }
|
||||
cacheThumbnail.delete()
|
||||
}
|
||||
}
|
||||
|
||||
metadata.imageList?.forEach { imageName ->
|
||||
imageName ?: return@forEach
|
||||
val target = downloadFolder.getChild(imageName)
|
||||
@@ -208,22 +254,16 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
return@forEach
|
||||
|
||||
kotlin.runCatching {
|
||||
if (!target.exists())
|
||||
target.createNewFile()
|
||||
source.readBytes()?.let { target.writeBytes(it) }
|
||||
|
||||
target.outputStream()?.use { target -> target.channel.truncate(0L); source.inputStream()?.use { source ->
|
||||
source.copyTo(target)
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
val cacheMetadata = cacheFolder.getChild(".metadata")
|
||||
val downloadMetadata = downloadFolder.getChild(".metadata")
|
||||
|
||||
if (cacheMetadata.exists()) {
|
||||
kotlin.runCatching {
|
||||
downloadMetadata.createNewFile()
|
||||
downloadMetadata.writeText(Json.encodeToString(metadata))
|
||||
cacheMetadata.delete()
|
||||
cacheFolder.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
cacheFolder.delete()
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
||||
}.invoke()
|
||||
}
|
||||
|
||||
return downloadFolderMapInstance!!
|
||||
return downloadFolderMapInstance ?: mutableMapOf()
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,9 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
||||
|
||||
val folder = downloadFolder.getChild(name)
|
||||
|
||||
if (!folder.exists())
|
||||
if (folder.exists())
|
||||
return
|
||||
|
||||
folder.mkdir()
|
||||
|
||||
downloadFolderMap[galleryID] = folder.name
|
||||
|
||||
@@ -19,35 +19,49 @@
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.storage.StorageManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
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.FileOutputStream
|
||||
import java.lang.reflect.Array
|
||||
import java.net.URL
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Use downloader.Cache instead")
|
||||
fun getCachedGallery(context: Context, galleryID: Int) =
|
||||
File(getDownloadDirectory(context), galleryID.toString()).let {
|
||||
if (it.exists())
|
||||
it
|
||||
else
|
||||
File(context.cacheDir, "imageCache/$galleryID")
|
||||
val mutex = Mutex()
|
||||
fun cleanCache(context: Context) = CoroutineScope(Dispatchers.IO).launch {
|
||||
if (mutex.isLocked) return@launch
|
||||
|
||||
mutex.withLock {
|
||||
val cacheFolder = File(context.cacheDir, "imageCache")
|
||||
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()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@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)!!
|
||||
size
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Use FileX instead")
|
||||
fun File.isParentOf(another: File) =
|
||||
another.absolutePath.startsWith(this.absolutePath)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,9 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import xyz.quaver.Code
|
||||
@@ -93,14 +96,14 @@ fun GalleryBlock.formatDownloadFolder(): String =
|
||||
formatMap.entries.fold(it) { str, (k, v) ->
|
||||
str.replace(k, v.invoke(this), true)
|
||||
}
|
||||
}.replace("/", "")
|
||||
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||
|
||||
fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
||||
format.let {
|
||||
formatMap.entries.fold(it) { str, (k, v) ->
|
||||
str.replace(k, v.invoke(this), true)
|
||||
}
|
||||
}.replace("/", "")
|
||||
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||
|
||||
val Reader.requestBuilders: List<Request.Builder>
|
||||
get() {
|
||||
@@ -129,3 +132,9 @@ fun String.ellipsize(n: Int): String =
|
||||
this.slice(0 until n) + "…"
|
||||
else
|
||||
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.net.Uri
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.webkit.URLUtil
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -52,7 +50,9 @@ import xyz.quaver.hitomi.getGalleryBlock
|
||||
import xyz.quaver.hitomi.getReader
|
||||
import xyz.quaver.io.FileX
|
||||
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.R
|
||||
import xyz.quaver.pupil.client
|
||||
@@ -160,7 +160,6 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
||||
val msg = extractReleaseNote(update, Locale.getDefault())
|
||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
|
||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
//Cancel any download queued before
|
||||
@@ -214,7 +213,7 @@ fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
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)
|
||||
onSuccess?.invoke(it)
|
||||
}
|
||||
@@ -314,7 +313,7 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
||||
)
|
||||
|
||||
synchronized(Cache) {
|
||||
Cache.delete(galleryID)
|
||||
Cache.delete(this@migrate, galleryID)
|
||||
}
|
||||
downloadFolderMap[galleryID] = folder.name
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/account_group.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,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" />
|
||||
<!--drawable/account_group.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 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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/account_star.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="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" />
|
||||
<!--drawable/account_star.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="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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/arrow_right.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#fff" android:pathData="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" />
|
||||
<!--drawable/arrow_right.xml-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#fff" android:pathData="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11H4z"/>
|
||||
</vector>
|
||||
@@ -1,37 +1,14 @@
|
||||
<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">
|
||||
<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">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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 android:name="vector" android:width="24dp" android:height="24dp" 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>
|
||||
</aapt:attr>
|
||||
<target android:name="clip">
|
||||
<aapt:attr name="android:animation">
|
||||
<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"/>
|
||||
<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"/>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/backspace_outline.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="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" />
|
||||
<!--drawable/backspace_outline.xml-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/book_open.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,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" />
|
||||
<!--drawable/book_open.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 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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/brush.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="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" />
|
||||
<!--drawable/brush.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="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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/cancel.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,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" />
|
||||
<!--drawable/cancel.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 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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/clock_end.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,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" />
|
||||
<!--drawable/clock_end.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 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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/clock_start.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,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" />
|
||||
<!--drawable/clock_start.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 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"/>
|
||||
</vector>
|
||||
@@ -1,8 +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,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
|
||||
<!--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>
|
||||
@@ -1,8 +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,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
|
||||
<!--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 -->
|
||||
<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">
|
||||
<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" />
|
||||
<!--drawable/fingerprint.xml-->
|
||||
<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">
|
||||
<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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/gender_female.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,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" />
|
||||
<!--drawable/gender_female.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 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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/gender_female.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,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" />
|
||||
<!--drawable/gender_female.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 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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/gender_male.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="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" />
|
||||
<!--drawable/gender_male.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="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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/gender_male.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="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" />
|
||||
<!--drawable/gender_male.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="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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/github-circle.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="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" />
|
||||
<!--drawable/github-circle.xml-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="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"/>
|
||||
</vector>
|
||||
@@ -1,8 +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,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" />
|
||||
<!--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"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
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 android:height="24dp" android:viewportHeight="24.0" android:viewportWidth="24.0" 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>
|
||||
|
||||
@@ -1,73 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Pupil, Hitomi.la viewer for Android
|
||||
~ Copyright (C) 2019 tom5079
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<animated-vector
|
||||
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">
|
||||
<!--~ Pupil, Hitomi.la viewer for Android ~ Copyright (C) 2019 tom5079 ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License as published by ~ the Free Software Foundation, either version 3 of the License, or ~ (at your option) any later version. ~ ~ This program is distributed in the hope that it will be useful, ~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ GNU General Public License for more details. ~ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see <http://www.gnu.org/licenses/>.-->
|
||||
<animated-vector 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">
|
||||
<vector
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
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 android:name="vector" android:width="24dp" 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"/>
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
<target android:name="path_2">
|
||||
<aapt:attr name="android:animation">
|
||||
<set>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
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 xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" 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 15a1.5 1.5 0 1 1 1.5 1.5A1.5 1.5 0 0 1 8.5 15z"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/message.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4C22,2.89 21.1,2 20,2Z" />
|
||||
<!--drawable/message.xml-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M20 2H4a2 2 0 0 0-2 2v18l4-4h14a2 2 0 0 0 2-2V4c0-1.11-0.9-2-2-2z"/>
|
||||
</vector>
|
||||
@@ -1,9 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
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 xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" 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>
|
||||
|
||||
@@ -1,22 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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">
|
||||
<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 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">
|
||||
<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"?>
|
||||
<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">
|
||||
<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 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">
|
||||
<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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
tools:ignore="NewApi">
|
||||
<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">
|
||||
<vector
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
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 android:name="vector" android:width="24dp" 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>
|
||||
</aapt:attr>
|
||||
<target android:name="path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="trimPathEnd"
|
||||
android:duration="1000"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:valueType="floatType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
<objectAnimator 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>
|
||||
</target>
|
||||
</animated-vector>
|
||||
|
||||
@@ -1,38 +1,13 @@
|
||||
<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">
|
||||
<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">
|
||||
<vector
|
||||
android:name="vector"
|
||||
android:width="24dp"
|
||||
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 android:name="vector" android:width="24dp" 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>
|
||||
</aapt:attr>
|
||||
<target android:name="path">
|
||||
<aapt:attr name="android:animation">
|
||||
<objectAnimator
|
||||
android:propertyName="trimPathEnd"
|
||||
android:duration="1000"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:valueType="floatType"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in"/>
|
||||
<objectAnimator 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>
|
||||
</target>
|
||||
</animated-vector>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<vector android:height="24dp" android:width="24dp"
|
||||
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="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 android:height="24dp" android:width="24dp" 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"/>
|
||||
</vector>
|
||||
@@ -1,4 +1,3 @@
|
||||
<vector android:height="24dp" android:width="24dp"
|
||||
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="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 android:height="24dp" android:width="24dp" 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"/>
|
||||
</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 -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M21,5V11.59L18,8.58L14,12.59L10,8.59L6,12.59L3,9.58V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5M18,11.42L21,14.43V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V12.42L6,15.41L10,11.41L14,15.41" />
|
||||
<!--drawable/image_broken_variant.xml-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M21 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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/lastpass.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="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" />
|
||||
<!--drawable/lastpass.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="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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/lock_pattern.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="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" />
|
||||
<!--drawable/lock_pattern.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="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"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/menu.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#fff" android:pathData="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />
|
||||
<!--drawable/menu.xml-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="?attr/colorControlNormal" android:pathData="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/numeric.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#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" />
|
||||
<!--drawable/numeric.xml-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#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"/>
|
||||
</vector>
|
||||
@@ -1,23 +1,5 @@
|
||||
<?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" />
|
||||
<!--~ 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"/>
|
||||
</shape>
|
||||
@@ -1,34 +1,10 @@
|
||||
<?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 ~ Copyright (C) 2020 tom5079 ~ ~ This program is free software: you can redistribute it and/or modify ~ it under the terms of the GNU General Public License as published by ~ the Free Software Foundation, either version 3 of the License, or ~ (at your option) any later version. ~ ~ This program is distributed in the hope that it will be useful, ~ but WITHOUT ANY WARRANTY; without even the implied warranty of ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ~ GNU General Public License for more details. ~ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see <http://www.gnu.org/licenses/>.-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:bottom="1dp"
|
||||
android:left="1dp"
|
||||
android:right="1dp"
|
||||
android:top="1dp">
|
||||
<shape android:shape="rectangle" >
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="#555555" />
|
||||
|
||||
<solid android:color="@color/transparent" />
|
||||
<item android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<stroke android:width="1dp" android:color="#555555"/>
|
||||
<solid android:color="@android:color/transparent"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/refresh.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="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z" />
|
||||
<!--drawable/refresh.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="M17.65 6.35C16.2 4.9 14.21 4 12 4a8 8 0 0 0-8 8 8 8 0 0 0 8 8c3.73 0 6.84-2.55 7.73-6h-2.08c-0.82 2.33-3.04 4-5.65 4a6 6 0 0 1-6-6 6 6 0 0 1 6-6c1.66 0 3.14 0.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/shuffle_variant.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#fff" android:pathData="M17,3L22.25,7.5L17,12L22.25,16.5L17,21V18H14.26L11.44,15.18L13.56,13.06L15.5,15H17V12L17,9H15.5L6.5,18H2V15H5.26L14.26,6H17V3M2,6H6.5L9.32,8.82L7.2,10.94L5.26,9H2V6Z" />
|
||||
<!--drawable/shuffle_variant.xml-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#fff" android:pathData="M17 3l5.25 4.5L17 12l5.25 4.5L17 21v-3h-2.74l-2.82-2.82 2.12-2.12L15.5 15H17V9h-1.5l-9 9H2v-3h3.26l9-9H17V3M2 6h4.5l2.82 2.82-2.12 2.12L5.26 9H2V6z"/>
|
||||
</vector>
|
||||
8
app/src/main/res/drawable/sort_variant.xml
Normal file
8
app/src/main/res/drawable/sort_variant.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<!-- drawable/sort_variant.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M3,13H15V11H3M3,6V8H21V6M3,18H9V16H3V18Z" />
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/swap_horizontal.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="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z" />
|
||||
<!--drawable/swap_horizontal.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="M21 9l-4-4v3h-7v2h7v3M7 11l-4 4 4 4v-3h7v-2H7v-3z"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,4 @@
|
||||
<!-- drawable/tag.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="M5.5,7A1.5,1.5 0 0,1 4,5.5A1.5,1.5 0 0,1 5.5,4A1.5,1.5 0 0,1 7,5.5A1.5,1.5 0 0,1 5.5,7M21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4C2.89,2 2,2.89 2,4V11C2,11.55 2.22,12.05 2.59,12.41L11.58,21.41C11.95,21.77 12.45,22 13,22C13.55,22 14.05,21.77 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.44 21.77,11.94 21.41,11.58Z" />
|
||||
<!--drawable/tag.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="M5.5 7A1.5 1.5 0 0 1 4 5.5 1.5 1.5 0 0 1 5.5 4 1.5 1.5 0 0 1 7 5.5 1.5 1.5 0 0 1 5.5 7m15.91 4.58l-9-9C12.05 2.22 11.55 2 11 2H4C2.89 2 2 2.89 2 4v7c0 0.55 0.22 1.05 0.59 1.41l8.99 9C11.95 21.77 12.45 22 13 22c0.55 0 1.05-0.23 1.41-0.59l7-7C21.78 14.05 22 13.55 22 13c0-0.56-0.23-1.06-0.59-1.42z"/>
|
||||
</vector>
|
||||
@@ -1,34 +1,5 @@
|
||||
<?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="rectangle">
|
||||
|
||||
<corners
|
||||
android:topLeftRadius="44dp"
|
||||
android:topRightRadius="44dp"
|
||||
android:bottomLeftRadius="44dp" />
|
||||
|
||||
<padding
|
||||
android:paddingLeft="22dp"
|
||||
android:paddingRight="22dp" />
|
||||
|
||||
<solid android:color="@color/colorPrimary" />
|
||||
|
||||
</shape>
|
||||
<!--~ 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="72dp" android:viewportWidth="64" android:viewportHeight="192">
|
||||
<path android:pathData="M36 0h1l1 1h3l1 1h1l1 1h2l1 1h1l1 1h1l1 1 1 1 1 1 1 1h1v1l1 1 1 1 1 1v1l1 1 1 1v1l1 1v2l1 1v1l1 1v3l1 1v138l-1 1v3l-1 1v1l-1 1v2l-1 1v1l-1 1v1l-1 1-1 1-1 1-1 1v1h-1l-1 1-1 1-1 1-1 1h-1l-1 1h-1l-1 1h-2l-1 1h-1l-1 1h-3l-1 1H27l-1-1h-3l-1-1h-1l-1-1h-2l-1-1h-1l-1-1h-1l-1-1-1-1-1-1-1-1H9v-1l-1-1-1-1-1-1v-1l-1-1-1-1v-1l-1-1v-2l-1-1v-1l-1-1v-3l-1-1V27l1-1v-3l1-1v-1l1-1v-2l1-1v-1l1-1v-1l1-1 1-1 1-1 1-1V9h1l1-1 1-1 1-1 1-1h1l1-1h1l1-1h2l1-1h1l1-1h3l1-1h9z" android:fillColor="#4fc3f7"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M42 80L32 70 22 80m20 32l-10 10-10-10" android:strokeWidth="2" android:fillAlpha="0" android:strokeColor="#000"/>
|
||||
</vector>
|
||||
|
||||
@@ -1,27 +1,6 @@
|
||||
<?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 ~ 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/>.-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:state_pressed="true"
|
||||
android:drawable="@drawable/thumb"/>
|
||||
|
||||
<item
|
||||
android:drawable="@drawable/thumb"/>
|
||||
<item android:state_pressed="true" android:drawable="@drawable/thumb"/>
|
||||
<item android:drawable="@drawable/thumb"/>
|
||||
</selector>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user