Compare commits
67 Commits
5.0.1
...
5.1.3-hotf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
10
.idea/jarRepositories.xml
generated
10
.idea/jarRepositories.xml
generated
@@ -61,5 +61,15 @@
|
|||||||
<option name="name" value="MavenLocal" />
|
<option name="name" value="MavenLocal" />
|
||||||
<option name="url" value="file:/$USER_HOME$/.m2/repository" />
|
<option name="url" value="file:/$USER_HOME$/.m2/repository" />
|
||||||
</remote-repository>
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven3" />
|
||||||
|
<option name="name" value="maven3" />
|
||||||
|
<option name="url" value="https://dl.bintray.com/tom5079/maven" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="maven3" />
|
||||||
|
<option name="name" value="maven3" />
|
||||||
|
<option name="url" value="http://dl.bintray.com/piasy/maven" />
|
||||||
|
</remote-repository>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@@ -2,6 +2,5 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/gh-pages" vcs="Git" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
14
README.md
14
README.md
@@ -1,18 +1,12 @@
|
|||||||
# Pupil
|
|
||||||
|
|
||||||

|

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

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

|

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

|
|
||||||
*Reader Screen*
|
|
||||||
|
|
||||||
Images are censored to be SFW
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
|
|||||||
141
app/build.gradle
141
app/build.gradle
@@ -1,27 +1,44 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: "com.android.application"
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: "kotlin-android"
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: "kotlin-kapt"
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: "kotlin-android-extensions"
|
||||||
apply plugin: 'kotlinx-serialization'
|
apply plugin: "kotlinx-serialization"
|
||||||
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
apply plugin: "com.google.android.gms.oss-licenses-plugin"
|
||||||
|
|
||||||
if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
|
if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
|
||||||
logger.lifecycle("Firebase Enabled")
|
logger.lifecycle("Firebase Enabled")
|
||||||
apply plugin: 'com.google.gms.google-services'
|
apply plugin: "com.google.gms.google-services"
|
||||||
apply plugin: 'com.google.firebase.crashlytics'
|
apply plugin: "com.google.firebase.crashlytics"
|
||||||
apply plugin: 'com.google.firebase.firebase-perf'
|
apply plugin: "com.google.firebase.firebase-perf"
|
||||||
} else {
|
} else {
|
||||||
logger.lifecycle("Firebase Disabled")
|
logger.lifecycle("Firebase Disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
okhttp_version = "3.12.12"
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
all {
|
||||||
|
resolutionStrategy {
|
||||||
|
eachDependency { DependencyResolveDetails details ->
|
||||||
|
if (details.requested.group == "com.squareup.okhttp3" && details.requested.name == "okhttp") {
|
||||||
|
// OkHttp drops support before 5.0 since 3.13.0
|
||||||
|
details.useVersion okhttp_version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "xyz.quaver.pupil"
|
applicationId "xyz.quaver.pupil"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 59
|
versionCode 62
|
||||||
versionName "5.0.1"
|
versionName "5.1.3-hotfix1"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
@@ -34,8 +51,7 @@ android {
|
|||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
versionNameSuffix "-DEBUG"
|
versionNameSuffix "-DEBUG"
|
||||||
|
|
||||||
buildConfigField('Boolean', 'CENSOR', 'false')
|
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
|
|
||||||
ext.enableCrashlytics = false
|
ext.enableCrashlytics = false
|
||||||
ext.alwaysUpdateBuildId = false
|
ext.alwaysUpdateBuildId = false
|
||||||
@@ -44,70 +60,77 @@ android {
|
|||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
|
|
||||||
buildConfigField('Boolean', 'CENSOR', 'false')
|
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental"
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
buildToolsVersion = '29.0.3'
|
buildToolsVersion = "29.0.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
||||||
//implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0-RC2"
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation "androidx.activity:activity-ktx:1.2.0-beta01"
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
|
||||||
|
implementation "androidx.preference:preference:1.1.1"
|
||||||
|
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
|
||||||
|
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||||
implementation "androidx.biometric:biometric:1.0.1"
|
implementation "androidx.biometric:biometric:1.0.1"
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.2.5'
|
implementation "androidx.work:work-runtime-ktx:2.4.0"
|
||||||
|
|
||||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||||
implementation 'com.google.android.material:material:1.3.0-alpha02'
|
|
||||||
implementation 'com.google.firebase:firebase-core:17.5.0'
|
implementation "com.google.android.material:material:1.3.0-alpha03"
|
||||||
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
|
||||||
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
|
implementation "com.google.firebase:firebase-core:17.5.0"
|
||||||
implementation 'com.google.firebase:firebase-perf:19.0.8'
|
implementation "com.google.firebase:firebase-analytics:17.5.0"
|
||||||
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
implementation "com.google.firebase:firebase-crashlytics:17.2.2"
|
||||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
implementation "com.google.firebase:firebase-perf:19.0.9"
|
||||||
implementation 'com.github.clans:fab:1.6.4'
|
|
||||||
//implementation 'com.quiph.ui:recyclerviewfastscroller:0.2.1'
|
implementation "com.google.android.gms:play-services-oss-licenses:17.0.0"
|
||||||
|
implementation "com.google.android.gms:play-services-mlkit-face-detection:16.1.1"
|
||||||
|
|
||||||
|
implementation "com.github.clans:fab:1.6.4"
|
||||||
|
|
||||||
|
//implementation "com.quiph.ui:recyclerviewfastscroller:0.2.1"
|
||||||
|
|
||||||
|
implementation 'com.github.piasy:BigImageViewer:1.6.7'
|
||||||
|
implementation 'com.github.piasy:FrescoImageLoader:1.6.7'
|
||||||
|
implementation 'com.github.piasy:FrescoImageViewFactory:1.6.7'
|
||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
|
||||||
implementation ("com.github.bumptech.glide:okhttp3-integration:4.11.0") {
|
implementation "com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2"
|
||||||
transitive = false
|
|
||||||
}
|
implementation "net.rdrei.android.dirchooser:library:3.2@aar"
|
||||||
implementation 'com.github.bumptech.glide:annotations:4.11.0'
|
implementation "com.gu:option:1.3"
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
|
||||||
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
implementation "com.andrognito.patternlockview:patternlockview:1.0.0"
|
||||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
|
//implementation "com.andrognito.pinlockview:pinlockview:2.1.0"
|
||||||
transitive = false
|
|
||||||
}
|
|
||||||
implementation 'com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2'
|
|
||||||
implementation 'net.rdrei.android.dirchooser:library:3.2@aar'
|
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
|
||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
|
||||||
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
|
||||||
implementation "ru.noties.markwon:core:3.1.0"
|
implementation "ru.noties.markwon:core:3.1.0"
|
||||||
implementation ("xyz.quaver:libpupil:1.6") {
|
|
||||||
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
|
implementation "xyz.quaver:libpupil:1.7.2"
|
||||||
}
|
implementation "xyz.quaver:documentfilex:0.3.1"
|
||||||
implementation "xyz.quaver:documentfilex:0.2.15"
|
implementation "xyz.quaver:floatingsearchview:1.0.7"
|
||||||
testImplementation 'junit:junit:4.13'
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
testImplementation "junit:junit:4.13"
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation "androidx.test:rules:1.3.0"
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
androidTestImplementation "androidx.test:runner:1.3.0"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
androidExtensions {
|
androidExtensions {
|
||||||
|
|||||||
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
|
-dontobfuscate
|
||||||
|
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
|
||||||
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
|
||||||
<init>(...);
|
|
||||||
}
|
|
||||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
|
||||||
**[] $VALUES;
|
|
||||||
public *;
|
|
||||||
}
|
|
||||||
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
|
|
||||||
*** rewind();
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
|
||||||
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl
|
|
||||||
|
|
||||||
-keepattributes *Annotation*, InnerClasses
|
-keepattributes *Annotation*, InnerClasses
|
||||||
-dontnote kotlinx.serialization.SerializationKt
|
-dontnote kotlinx.serialization.SerializationKt
|
||||||
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
|
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
|
||||||
@@ -48,4 +33,3 @@
|
|||||||
}
|
}
|
||||||
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
||||||
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
||||||
-keep class xyz.quaver.pupil.util.Preferences
|
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"properties": [],
|
"properties": [],
|
||||||
"versionCode": 59,
|
"versionCode": 62,
|
||||||
"versionName": "5.0.1",
|
"versionName": "5.1.3-hotfix1",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="21"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="21" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Pupil"
|
android:name=".Pupil"
|
||||||
@@ -24,6 +27,10 @@
|
|||||||
tools:replace="android:theme"
|
tools:replace="android:theme"
|
||||||
tools:ignore="UnusedAttribute">
|
tools:ignore="UnusedAttribute">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.mlkit.vision.DEPENDENCIES"
|
||||||
|
android:value="face" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
|||||||
@@ -26,14 +26,13 @@ import android.os.Build
|
|||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.github.piasy.biv.BigImageViewer
|
||||||
|
import com.github.piasy.biv.loader.fresco.FrescoImageLoader
|
||||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||||
import com.google.android.gms.security.ProviderInstaller
|
import com.google.android.gms.security.ProviderInstaller
|
||||||
import com.google.firebase.analytics.FirebaseAnalytics
|
import com.google.firebase.analytics.FirebaseAnalytics
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@@ -71,11 +70,9 @@ val client: OkHttpClient
|
|||||||
|
|
||||||
class Pupil : Application() {
|
class Pupil : Application() {
|
||||||
|
|
||||||
init {
|
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
|
|
||||||
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
preferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
val userID = Preferences["user_id", ""].let { userID ->
|
val userID = Preferences["user_id", ""].let { userID ->
|
||||||
@@ -115,25 +112,16 @@ class Pupil : Application() {
|
|||||||
Preferences.remove("download_folder")
|
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)
|
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
|
||||||
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
|
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
|
||||||
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
|
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
|
||||||
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
|
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)
|
if (BuildConfig.DEBUG)
|
||||||
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
|
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
|
||||||
|
|
||||||
@@ -145,6 +133,8 @@ class Pupil : Application() {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BigImageViewer.initialize(FrescoImageLoader.with(this))
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Pupil, Hitomi.la viewer for Android
|
|
||||||
* Copyright (C) 2020 tom5079
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package xyz.quaver.pupil
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.Registry
|
|
||||||
import com.bumptech.glide.annotation.GlideModule
|
|
||||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
|
||||||
import com.bumptech.glide.module.AppGlideModule
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
@GlideModule
|
|
||||||
class PupilGlideModule : AppGlideModule() {
|
|
||||||
|
|
||||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
|
||||||
registry.append(
|
|
||||||
GlideUrl::class.java,
|
|
||||||
InputStream::class.java,
|
|
||||||
OkHttpUrlLoader.Factory(client)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -26,40 +26,30 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
import com.bumptech.glide.load.DataSource
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
|
||||||
import com.bumptech.glide.request.RequestListener
|
|
||||||
import com.bumptech.glide.request.target.Target
|
|
||||||
import com.daimajia.swipe.SwipeLayout
|
import com.daimajia.swipe.SwipeLayout
|
||||||
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
||||||
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
||||||
|
import com.github.piasy.biv.loader.ImageLoader
|
||||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import xyz.quaver.hitomi.getGallery
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import xyz.quaver.hitomi.getReader
|
import xyz.quaver.hitomi.getReader
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.pupil.BuildConfig
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.ui.view.TagChip
|
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
import java.util.*
|
import java.io.File
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.concurrent.schedule
|
|
||||||
|
|
||||||
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
class GalleryBlockAdapter(private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||||
|
|
||||||
enum class ViewType {
|
enum class ViewType {
|
||||||
NEXT,
|
NEXT,
|
||||||
@@ -67,33 +57,43 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
PREV
|
PREV
|
||||||
}
|
}
|
||||||
|
|
||||||
val timer = Timer()
|
var updateAll = true
|
||||||
|
var thin: Boolean = Preferences["thin"]
|
||||||
var isThin = false
|
|
||||||
|
|
||||||
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
var timerTask: TimerTask? = null
|
private var galleryID: Int = 0
|
||||||
|
|
||||||
private fun updateProgress(context: Context, galleryID: Int) {
|
init {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
while (updateAll) {
|
||||||
|
updateProgress(view.context)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProgress(context: Context) {
|
||||||
val cache = Cache.getInstance(context, galleryID)
|
val cache = Cache.getInstance(context, galleryID)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (cache.metadata.reader == null || Preferences["cache_disable"]) {
|
if (cache.metadata.reader == null) {
|
||||||
view.galleryblock_progressbar.visibility = View.GONE
|
view.galleryblock_progressbar_layout.visibility = View.GONE
|
||||||
view.galleryblock_progress_complete.visibility = View.GONE
|
view.galleryblock_progress_complete.visibility = View.INVISIBLE
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
with(view.galleryblock_progressbar) {
|
with(view.galleryblock_progressbar) {
|
||||||
val imageList = cache.metadata.imageList!!
|
val imageList = cache.metadata.imageList!!
|
||||||
|
|
||||||
progress = imageList.filterNotNull().size
|
progress = imageList.count { it != null }
|
||||||
max = imageList.size
|
max = imageList.size
|
||||||
|
|
||||||
|
with(view.galleryblock_progressbar_layout) {
|
||||||
if (visibility == View.GONE)
|
if (visibility == View.GONE)
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
if (progress == max) {
|
if (!imageList.contains(null)) {
|
||||||
val downloadManager = DownloadManager.getInstance(context)
|
val downloadManager = DownloadManager.getInstance(context)
|
||||||
|
|
||||||
if (completeFlag.get(galleryID, false)) {
|
if (completeFlag.get(galleryID, false)) {
|
||||||
@@ -125,9 +125,14 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun bind(galleryID: Int) {
|
fun bind(galleryID: Int) {
|
||||||
|
this.galleryID = galleryID
|
||||||
|
updateProgress(view.context)
|
||||||
|
|
||||||
val cache = Cache.getInstance(view.context, galleryID)
|
val cache = Cache.getInstance(view.context, galleryID)
|
||||||
|
|
||||||
val galleryBlock = cache.metadata.galleryBlock ?: return
|
val galleryBlock = runBlocking {
|
||||||
|
cache.getGalleryBlock()
|
||||||
|
} ?: return
|
||||||
|
|
||||||
with(view) {
|
with(view) {
|
||||||
val resources = context.resources
|
val resources = context.resources
|
||||||
@@ -140,63 +145,61 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
val artists = galleryBlock.artists
|
val artists = galleryBlock.artists
|
||||||
val series = galleryBlock.series
|
val series = galleryBlock.series
|
||||||
|
|
||||||
if (isThin)
|
galleryblock_thumbnail.apply {
|
||||||
galleryblock_thumbnail.layoutParams.width = context.resources.getDimensionPixelSize(
|
setOnClickListener {
|
||||||
R.dimen.galleryblock_thumbnail_thin
|
view.performClick()
|
||||||
)
|
}
|
||||||
|
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 {
|
override fun onCacheHit(imageType: Int, image: File?) {}
|
||||||
it.start()
|
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 {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val thumbnail = cache.getThumbnail()
|
cache.getThumbnail().let { launch(Dispatchers.Main) {
|
||||||
|
showImage(it)
|
||||||
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() }
|
|
||||||
}
|
}
|
||||||
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
|
galleryblock_title.text = galleryBlock.title
|
||||||
with(galleryblock_artist) {
|
with(galleryblock_artist) {
|
||||||
text = artists.joinToString(", ") { it.wordCapitalize() }
|
text = artists.joinToString { it.wordCapitalize() }
|
||||||
visibility = when {
|
visibility = when {
|
||||||
artists.isNotEmpty() -> View.VISIBLE
|
artists.isNotEmpty() -> View.VISIBLE
|
||||||
else -> View.GONE
|
else -> View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val gallery = runCatching {
|
||||||
|
getGallery(galleryID)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
if (gallery?.groups?.isNotEmpty() != true)
|
||||||
|
return@launch
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
text = context.getString(
|
||||||
|
R.string.galleryblock_artist_with_group,
|
||||||
|
artists.joinToString { it.wordCapitalize() },
|
||||||
|
gallery.groups.joinToString { it.wordCapitalize() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
with(galleryblock_series) {
|
with(galleryblock_series) {
|
||||||
text =
|
text =
|
||||||
@@ -218,14 +221,32 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryblock_tag_group.removeAllViews()
|
with(galleryblock_tag_group) {
|
||||||
galleryBlock.relatedTags.forEach {
|
onClickListener = {
|
||||||
galleryblock_tag_group.addView(TagChip(context, Tag.parse(it)).apply {
|
onChipClickedHandler.forEach { callback ->
|
||||||
setOnClickListener { view ->
|
callback.invoke(it)
|
||||||
for (callback in onChipClickedHandler)
|
|
||||||
callback.invoke((view as TagChip).tag)
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
tags.clear()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryblock_id.text = galleryBlock.id.toString()
|
galleryblock_id.text = galleryBlock.id.toString()
|
||||||
@@ -268,9 +289,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
|
|
||||||
|
|
||||||
// Make some views invisible to make it thinner
|
// Make some views invisible to make it thinner
|
||||||
if (isThin) {
|
if (thin) {
|
||||||
galleryblock_language.visibility = View.GONE
|
|
||||||
galleryblock_type.visibility = View.GONE
|
|
||||||
galleryblock_tag_group.visibility = View.GONE
|
galleryblock_tag_group.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,15 +384,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
super.onViewDetachedFromWindow(holder)
|
|
||||||
|
|
||||||
if (holder is GalleryViewHolder) {
|
|
||||||
holder.timerTask?.cancel()
|
|
||||||
holder.timerTask = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() =
|
override fun getItemCount() =
|
||||||
galleries.size +
|
galleries.size +
|
||||||
(if (showNext) 1 else 0) +
|
(if (showNext) 1 else 0) +
|
||||||
|
|||||||
@@ -18,58 +18,94 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.content.Context
|
||||||
|
import android.graphics.DiscretePathEffect
|
||||||
|
import android.graphics.drawable.Animatable
|
||||||
|
import android.net.Uri
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import com.bumptech.glide.load.DataSource
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.facebook.drawee.controller.BaseControllerListener
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import com.facebook.drawee.drawable.ScalingUtils
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
import com.facebook.drawee.interfaces.DraweeController
|
||||||
import com.bumptech.glide.load.model.LazyHeaders
|
import com.facebook.drawee.view.SimpleDraweeView
|
||||||
import com.bumptech.glide.request.RequestListener
|
import com.facebook.imagepipeline.image.ImageInfo
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.github.piasy.biv.view.BigImageView
|
||||||
|
import com.github.piasy.biv.view.ImageShownCallback
|
||||||
|
import com.github.piasy.biv.view.ImageViewFactory
|
||||||
import kotlinx.android.synthetic.main.item_reader.view.*
|
import kotlinx.android.synthetic.main.item_reader.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import xyz.quaver.Code
|
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.getReferer
|
|
||||||
import xyz.quaver.hitomi.imageUrlFromImage
|
|
||||||
import xyz.quaver.hiyobi.createImgList
|
|
||||||
import xyz.quaver.io.util.readBytes
|
|
||||||
import xyz.quaver.pupil.BuildConfig
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import xyz.quaver.pupil.util.Preferences
|
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import java.util.*
|
import java.io.File
|
||||||
import kotlin.concurrent.schedule
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ReaderAdapter(private val activity: ReaderActivity,
|
class ReaderAdapter(
|
||||||
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
private val activity: ReaderActivity,
|
||||||
|
private val galleryID: Int
|
||||||
|
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||||
|
|
||||||
var reader: Reader? = null
|
var reader: Reader? = null
|
||||||
val timer = Timer()
|
|
||||||
|
|
||||||
private val glide = Glide.with(activity)
|
|
||||||
|
|
||||||
var isFullScreen = false
|
var isFullScreen = false
|
||||||
|
|
||||||
var onItemClickListener : ((Int) -> (Unit))? = null
|
var onItemClickListener : (() -> (Unit))? = null
|
||||||
|
|
||||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
|
fun clear() {
|
||||||
|
view.image.mainView.let {
|
||||||
|
when (it) {
|
||||||
|
is SubsamplingScaleImageView ->
|
||||||
|
it.recycle()
|
||||||
|
is SimpleDraweeView ->
|
||||||
|
it.controller = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return LayoutInflater.from(parent.context).inflate(
|
return LayoutInflater.from(parent.context).inflate(
|
||||||
R.layout.item_reader, parent, false
|
R.layout.item_reader, parent, false
|
||||||
).let {
|
).let {
|
||||||
|
with(it) {
|
||||||
|
image.setImageViewFactory(FrescoImageViewFactory().apply {
|
||||||
|
updateView = { imageInfo ->
|
||||||
|
it.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
|
dimensionRatio = "${imageInfo.width}:${imageInfo.height}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
image.setImageShownCallback(object : ImageShownCallback {
|
||||||
|
override fun onMainImageShown() {
|
||||||
|
it.image.mainView.let { v ->
|
||||||
|
when (v) {
|
||||||
|
is SubsamplingScaleImageView ->
|
||||||
|
if (!isFullScreen) it.image.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onThumbnailShown() {}
|
||||||
|
})
|
||||||
|
image.setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||||
|
image.setOnClickListener {
|
||||||
|
this.performClick()
|
||||||
|
}
|
||||||
|
setOnClickListener {
|
||||||
|
onItemClickListener?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ViewHolder(it)
|
ViewHolder(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,126 +117,134 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
|||||||
if (cache == null)
|
if (cache == null)
|
||||||
cache = Cache.getInstance(holder.view.context, galleryID)
|
cache = Cache.getInstance(holder.view.context, galleryID)
|
||||||
|
|
||||||
if (isFullScreen) {
|
if (!isFullScreen) {
|
||||||
holder.view.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
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 {
|
} else {
|
||||||
holder.view.layoutParams.height = ConstraintLayout.LayoutParams.WRAP_CONTENT
|
holder.view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
(holder.view.progress_layout.layoutParams as ConstraintLayout.LayoutParams)
|
height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||||
.dimensionRatio = "${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
dimensionRatio = null
|
||||||
}
|
}
|
||||||
|
holder.view.background = null
|
||||||
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
|
||||||
onItemClickListener?.invoke(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.view.setOnClickListener {
|
|
||||||
onItemClickListener?.invoke(position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.view.reader_index.text = (position+1).toString()
|
holder.view.reader_index.text = (position+1).toString()
|
||||||
|
|
||||||
if (Preferences["cache_disable"]) {
|
|
||||||
val 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)
|
|
||||||
.error(R.drawable.image_broken_variant)
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
else
|
|
||||||
override(
|
|
||||||
holder.view.context.resources.displayMetrics.widthPixels,
|
|
||||||
holder.view.context.resources.getDimensionPixelSize(R.dimen.reader_max_height)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.error(R.drawable.image_broken_variant)
|
|
||||||
.into(holder.view.image)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val image = cache!!.getImage(position)
|
val image = cache!!.getImage(position)
|
||||||
val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
|
val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
|
||||||
|
|
||||||
if (progress?.isInfinite() == true && image != null) {
|
if (progress?.isInfinite() == true && image != null) {
|
||||||
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
holder.view.progress_group.visibility = View.INVISIBLE
|
||||||
|
holder.view.image.showImage(image.uri)
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
glide
|
|
||||||
.load(image.readBytes())
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
else
|
|
||||||
override(
|
|
||||||
holder.view.context.resources.displayMetrics.widthPixels,
|
|
||||||
holder.view.context.resources.getDimensionPixelSize(R.dimen.reader_max_height)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.error(R.drawable.image_broken_variant)
|
|
||||||
.listener(object: RequestListener<Drawable> {
|
|
||||||
override fun onLoadFailed(
|
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
cache!!.metadata.imageList?.set(position, null)
|
|
||||||
image.delete()
|
|
||||||
DownloadService.cancel(holder.view.context, galleryID)
|
|
||||||
DownloadService.download(holder.view.context, galleryID, true)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(
|
|
||||||
resource: Drawable?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
dataSource: DataSource?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
) = false
|
|
||||||
}).let { launch(Dispatchers.Main) { it.into(holder.view.image) } }
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
holder.view.reader_item_progressbar.visibility = View.VISIBLE
|
holder.view.progress_group.visibility = View.VISIBLE
|
||||||
|
|
||||||
glide.clear(holder.view.image)
|
|
||||||
|
|
||||||
holder.view.reader_item_progressbar.progress =
|
holder.view.reader_item_progressbar.progress =
|
||||||
if (progress?.isInfinite() == true)
|
if (progress?.isInfinite() == true)
|
||||||
100
|
100
|
||||||
else
|
else
|
||||||
progress?.roundToInt() ?: 0
|
progress?.roundToInt() ?: 0
|
||||||
|
|
||||||
holder.view.image.setImageDrawable(null)
|
holder.clear()
|
||||||
|
|
||||||
timer.schedule(1000) {
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
delay(1000)
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
|
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
|
holder.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class FrescoImageViewFactory : ImageViewFactory() {
|
||||||
|
var updateView: ((ImageInfo) -> Unit)? = null
|
||||||
|
|
||||||
|
override fun createAnimatedImageView(
|
||||||
|
context: Context, imageType: Int,
|
||||||
|
initScaleType: Int
|
||||||
|
): View {
|
||||||
|
val view = SimpleDraweeView(context)
|
||||||
|
view.hierarchy.actualImageScaleType = scaleType(initScaleType)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadAnimatedContent(
|
||||||
|
view: View, imageType: Int,
|
||||||
|
imageFile: File
|
||||||
|
) {
|
||||||
|
if (view is SimpleDraweeView) {
|
||||||
|
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setUri(Uri.parse("file://" + imageFile.absolutePath))
|
||||||
|
.setAutoPlayAnimations(true)
|
||||||
|
.setControllerListener(object: BaseControllerListener<ImageInfo>() {
|
||||||
|
override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
|
||||||
|
imageInfo?.let { updateView?.invoke(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
|
||||||
|
imageInfo?.let { updateView?.invoke(it) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
view.controller = controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createThumbnailView(
|
||||||
|
context: Context,
|
||||||
|
scaleType: ImageView.ScaleType, willLoadFromNetwork: Boolean
|
||||||
|
): View {
|
||||||
|
return if (willLoadFromNetwork) {
|
||||||
|
val thumbnailView = SimpleDraweeView(context)
|
||||||
|
thumbnailView.hierarchy.actualImageScaleType = scaleType(scaleType)
|
||||||
|
thumbnailView
|
||||||
|
} else {
|
||||||
|
super.createThumbnailView(context, scaleType, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadThumbnailContent(view: View, thumbnail: Uri) {
|
||||||
|
if (view is SimpleDraweeView) {
|
||||||
|
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setUri(thumbnail)
|
||||||
|
.build()
|
||||||
|
view.controller = controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scaleType(value: Int): ScalingUtils.ScaleType {
|
||||||
|
return when (value) {
|
||||||
|
BigImageView.INIT_SCALE_TYPE_CENTER -> ScalingUtils.ScaleType.CENTER
|
||||||
|
BigImageView.INIT_SCALE_TYPE_CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
|
||||||
|
BigImageView.INIT_SCALE_TYPE_CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
|
||||||
|
BigImageView.INIT_SCALE_TYPE_FIT_END -> ScalingUtils.ScaleType.FIT_END
|
||||||
|
BigImageView.INIT_SCALE_TYPE_FIT_START -> ScalingUtils.ScaleType.FIT_START
|
||||||
|
BigImageView.INIT_SCALE_TYPE_FIT_XY -> ScalingUtils.ScaleType.FIT_XY
|
||||||
|
BigImageView.INIT_SCALE_TYPE_FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
|
||||||
|
else -> ScalingUtils.ScaleType.FIT_CENTER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scaleType(scaleType: ImageView.ScaleType): ScalingUtils.ScaleType {
|
||||||
|
return when (scaleType) {
|
||||||
|
ImageView.ScaleType.CENTER -> ScalingUtils.ScaleType.CENTER
|
||||||
|
ImageView.ScaleType.CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
|
||||||
|
ImageView.ScaleType.CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
|
||||||
|
ImageView.ScaleType.FIT_END -> ScalingUtils.ScaleType.FIT_END
|
||||||
|
ImageView.ScaleType.FIT_START -> ScalingUtils.ScaleType.FIT_START
|
||||||
|
ImageView.ScaleType.FIT_XY -> ScalingUtils.ScaleType.FIT_XY
|
||||||
|
ImageView.ScaleType.FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
|
||||||
|
else -> ScalingUtils.ScaleType.FIT_CENTER
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,32 +18,35 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.RequestManager
|
import com.github.piasy.biv.view.BigImageView
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.BuildConfig
|
|
||||||
|
|
||||||
class ThumbnailAdapter(private val glide: RequestManager, var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
class ThumbnailAdapter(var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
||||||
|
|
||||||
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: BigImageView) : RecyclerView.ViewHolder(view) {
|
||||||
|
fun clear() {
|
||||||
|
view.ssiv?.recycle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(ImageView(parent.context))
|
return ViewHolder(BigImageView(parent.context).apply {
|
||||||
|
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
glide
|
holder.view.showImage(Uri.parse(thumbnails[position]))
|
||||||
.load(thumbnails[position])
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
}
|
|
||||||
.into(holder.view)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = thumbnails.size
|
override fun getItemCount() = thumbnails.size
|
||||||
|
|
||||||
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
|
holder.clear()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -21,17 +21,19 @@ package xyz.quaver.pupil.adapters
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class ThumbnailPageAdapter(private val glide: RequestManager, private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailPageAdapter.ViewHolder>() {
|
class ThumbnailPageAdapter(private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailPageAdapter.ViewHolder>() {
|
||||||
|
|
||||||
class ViewHolder(val view: RecyclerView) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: RecyclerView) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(RecyclerView(parent.context).apply {
|
return ViewHolder(RecyclerView(parent.context).apply {
|
||||||
layoutManager = GridLayoutManager(parent.context, 3)
|
val layoutManager = GridLayoutManager(parent.context, 3)
|
||||||
adapter = ThumbnailAdapter(glide, listOf())
|
val adapter = ThumbnailAdapter(listOf())
|
||||||
|
|
||||||
|
this.layoutManager = layoutManager
|
||||||
|
this.adapter = adapter
|
||||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -41,7 +43,7 @@ class ThumbnailPageAdapter(private val glide: RequestManager, private val thumbn
|
|||||||
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
|
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
|
|
||||||
holder.view.layoutManager?.scrollToPosition(itemCount-1)
|
(holder.view.layoutManager as GridLayoutManager).scrollToPosition(8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import android.app.PendingIntent
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.SparseArray
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
@@ -37,17 +36,19 @@ import okhttp3.Callback
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okio.*
|
import okio.*
|
||||||
import xyz.quaver.pupil.PupilInterceptor
|
import xyz.quaver.pupil.*
|
||||||
import xyz.quaver.pupil.R
|
|
||||||
import xyz.quaver.pupil.client
|
|
||||||
import xyz.quaver.pupil.interceptors
|
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
|
import xyz.quaver.pupil.util.cleanCache
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.ellipsize
|
import xyz.quaver.pupil.util.ellipsize
|
||||||
import xyz.quaver.pupil.util.normalizeID
|
import xyz.quaver.pupil.util.normalizeID
|
||||||
import xyz.quaver.pupil.util.requestBuilders
|
import xyz.quaver.pupil.util.requestBuilders
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.log10
|
||||||
|
|
||||||
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
||||||
class DownloadService : Service() {
|
class DownloadService : Service() {
|
||||||
@@ -66,7 +67,7 @@ class DownloadService : Service() {
|
|||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val notification = SparseArray<NotificationCompat.Builder?>()
|
private val notification = ConcurrentHashMap<Int, NotificationCompat.Builder?>()
|
||||||
|
|
||||||
private fun initNotification(galleryID: Int) {
|
private fun initNotification(galleryID: Int) {
|
||||||
val intent = Intent(this, ReaderActivity::class.java)
|
val intent = Intent(this, ReaderActivity::class.java)
|
||||||
@@ -87,7 +88,7 @@ class DownloadService : Service() {
|
|||||||
PendingIntent.FLAG_UPDATE_CURRENT),
|
PendingIntent.FLAG_UPDATE_CURRENT),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
notification.put(galleryID, NotificationCompat.Builder(this, "download").apply {
|
notification[galleryID] = NotificationCompat.Builder(this, "download").apply {
|
||||||
setContentTitle(getString(R.string.reader_loading))
|
setContentTitle(getString(R.string.reader_loading))
|
||||||
setContentText(getString(R.string.reader_notification_text))
|
setContentText(getString(R.string.reader_notification_text))
|
||||||
setSmallIcon(R.drawable.ic_notification)
|
setSmallIcon(R.drawable.ic_notification)
|
||||||
@@ -95,7 +96,7 @@ class DownloadService : Service() {
|
|||||||
addAction(action)
|
addAction(action)
|
||||||
setProgress(0, 0, true)
|
setProgress(0, 0, true)
|
||||||
setOngoing(true)
|
setOngoing(true)
|
||||||
})
|
}
|
||||||
|
|
||||||
notify(galleryID)
|
notify(galleryID)
|
||||||
}
|
}
|
||||||
@@ -120,7 +121,7 @@ class DownloadService : Service() {
|
|||||||
.setProgress(max, progress, false)
|
.setProgress(max, progress, false)
|
||||||
.setContentText("$progress/$max")
|
.setContentText("$progress/$max")
|
||||||
|
|
||||||
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null)
|
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null || galleryID == priority)
|
||||||
notification.let { notificationManager.notify(galleryID, it.build()) }
|
notification.let { notificationManager.notify(galleryID, it.build()) }
|
||||||
else
|
else
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID)
|
||||||
@@ -194,7 +195,8 @@ class DownloadService : Service() {
|
|||||||
* 0 <= value < 100 -> Download in progress
|
* 0 <= value < 100 -> Download in progress
|
||||||
* Float.POSITIVE_INFINITY -> Download completed
|
* Float.POSITIVE_INFINITY -> Download completed
|
||||||
*/
|
*/
|
||||||
val progress = SparseArray<MutableList<Float>?>()
|
val progress = ConcurrentHashMap<Int, MutableList<Float>>()
|
||||||
|
var priority = 0
|
||||||
|
|
||||||
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
|
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
|
||||||
|
|
||||||
@@ -216,10 +218,11 @@ class DownloadService : Service() {
|
|||||||
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val image = response.also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() } ?: throw Exception()
|
val image = response.also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() } ?: throw Exception()
|
||||||
|
val padding = ceil(progress[galleryID]?.size?.let { log10(it.toFloat()) } ?: 0F).toInt()
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
Cache.getInstance(this@DownloadService, galleryID).putImage(index, "$index.$ext", image)
|
Cache.getInstance(this@DownloadService, galleryID).putImage(index, "${index.toString().padStart(padding, '0')}.$ext", image)
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
|
||||||
notify(galleryID)
|
notify(galleryID)
|
||||||
@@ -285,14 +288,16 @@ class DownloadService : Service() {
|
|||||||
fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
|
fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
cancel(galleryID)
|
cancel(galleryID)
|
||||||
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
||||||
Cache.delete(galleryID)
|
Cache.delete(this@DownloadService, galleryID)
|
||||||
|
|
||||||
startId?.let { stopSelf(it) }
|
startId?.let { stopSelf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
|
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (progress.indexOfKey(galleryID) >= 0)
|
if (DownloadManager.getInstance(this@DownloadService).isDownloading(galleryID))
|
||||||
cancel(galleryID)
|
return@launch
|
||||||
|
|
||||||
|
cleanCache(this@DownloadService)
|
||||||
|
|
||||||
val cache = Cache.getInstance(this@DownloadService, galleryID)
|
val cache = Cache.getInstance(this@DownloadService, galleryID)
|
||||||
|
|
||||||
@@ -303,17 +308,25 @@ class DownloadService : Service() {
|
|||||||
// Gallery doesn't exist
|
// Gallery doesn't exist
|
||||||
if (reader == null) {
|
if (reader == null) {
|
||||||
delete(galleryID)
|
delete(galleryID)
|
||||||
progress.put(galleryID, null)
|
progress[galleryID] = mutableListOf()
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
progress[galleryID]?.set(index, if (image != null) Float.POSITIVE_INFINITY else 0F)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isCompleted(galleryID)) {
|
if (isCompleted(galleryID)) {
|
||||||
|
if (DownloadManager.getInstance(this@DownloadService)
|
||||||
|
.getDownloadFolder(galleryID) != null )
|
||||||
|
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
||||||
|
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID)
|
||||||
startId?.let { stopSelf(it) }
|
startId?.let { stopSelf(it) }
|
||||||
return@launch
|
return@launch
|
||||||
@@ -334,7 +347,7 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reader.requestBuilders.forEachIndexed { index, it ->
|
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()
|
val request = it.tag(Tag(galleryID, index, startId)).build()
|
||||||
client.newCall(request).enqueue(callback)
|
client.newCall(request).enqueue(callback)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,36 +18,28 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
import kotlinx.android.parcel.IgnoredOnParcel
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import xyz.quaver.hitomi.Suggestion
|
import xyz.quaver.hitomi.Suggestion
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
|
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
|
||||||
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
||||||
|
|
||||||
override fun getBody(): String {
|
@IgnoredOnParcel
|
||||||
return s
|
override val body = s
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
class Suggestion(val str: String) : SearchSuggestion {
|
class Suggestion(override val body: String) : SearchSuggestion
|
||||||
override fun getBody() = str
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
class NoResultSuggestion(val str: String) : SearchSuggestion {
|
class NoResultSuggestion(override val body: String) : SearchSuggestion
|
||||||
override fun getBody() = str
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
class LoadingSuggestion(val str: String) : SearchSuggestion {
|
class LoadingSuggestion(override val body: String) : SearchSuggestion
|
||||||
override fun getBody() = str
|
|
||||||
}
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@Suppress("PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY")
|
@Suppress("PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY")
|
||||||
class FavoriteHistorySwitch(private val body: String) : SearchSuggestion {
|
class FavoriteHistorySwitch(override val body: String) : SearchSuggestion
|
||||||
override fun getBody() = body
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,7 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.PersistableBundle
|
import android.os.PersistableBundle
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.CallSuper
|
import androidx.annotation.CallSuper
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
@@ -34,6 +35,13 @@ open class BaseActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private var locked: Boolean = true
|
private var locked: Boolean = true
|
||||||
|
|
||||||
|
private val lockLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK)
|
||||||
|
locked = false
|
||||||
|
else
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
|
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
|
||||||
super.onCreate(savedInstanceState, persistentState)
|
super.onCreate(savedInstanceState, persistentState)
|
||||||
@@ -53,20 +61,7 @@ open class BaseActivity : AppCompatActivity() {
|
|||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
|
||||||
if (locked)
|
if (locked)
|
||||||
startActivityForResult(Intent(this, LockActivity::class.java), R.id.request_lock.normalizeID())
|
lockLauncher.launch(Intent(this, LockActivity::class.java))
|
||||||
}
|
|
||||||
|
|
||||||
@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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -23,16 +23,17 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.*
|
import android.text.util.Linkify
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.core.text.util.LinkifyCompat
|
||||||
import androidx.core.view.GravityCompat
|
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.appbar.AppBarLayout
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
@@ -40,6 +41,10 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.activity_main_content.*
|
import kotlinx.android.synthetic.main.activity_main_content.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import 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.doSearch
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||||
import xyz.quaver.hitomi.getSuggestionsForQuery
|
import xyz.quaver.hitomi.getSuggestionsForQuery
|
||||||
@@ -49,9 +54,13 @@ import xyz.quaver.pupil.services.DownloadService
|
|||||||
import xyz.quaver.pupil.types.*
|
import xyz.quaver.pupil.types.*
|
||||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
||||||
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import xyz.quaver.pupil.util.checkUpdate
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
|
import xyz.quaver.pupil.util.restore
|
||||||
|
import java.util.regex.Pattern
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -59,7 +68,6 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
class MainActivity :
|
class MainActivity :
|
||||||
BaseActivity(),
|
BaseActivity(),
|
||||||
FloatingSearchView.OnMenuItemClickListener,
|
|
||||||
NavigationView.OnNavigationItemSelectedListener
|
NavigationView.OnNavigationItemSelectedListener
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -95,9 +103,10 @@ class MainActivity :
|
|||||||
private var loadingJob: Job? = null
|
private var loadingJob: Job? = null
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
if (intent.action == Intent.ACTION_VIEW) {
|
if (intent.action == Intent.ACTION_VIEW) {
|
||||||
intent.dataString?.let { url ->
|
intent.dataString?.let { url ->
|
||||||
@@ -111,8 +120,6 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
|
|
||||||
if (Preferences["download_folder", ""].isEmpty())
|
if (Preferences["download_folder", ""].isEmpty())
|
||||||
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
|
||||||
|
|
||||||
@@ -140,7 +147,7 @@ class MainActivity :
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel()
|
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.updateAll = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
@@ -180,20 +187,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() {
|
private fun initView() {
|
||||||
var prevP1 = 0
|
var prevP1 = 0
|
||||||
main_appbar_layout.addOnOffsetChangedListener(
|
main_appbar_layout.addOnOffsetChangedListener(
|
||||||
@@ -212,6 +205,8 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Linkify.addLinks(main_noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) })
|
||||||
|
|
||||||
//NavigationView
|
//NavigationView
|
||||||
main_nav_view.setNavigationItemSelectedListener(this)
|
main_nav_view.setNavigationItemSelectedListener(this)
|
||||||
|
|
||||||
@@ -261,11 +256,7 @@ class MainActivity :
|
|||||||
if (it?.isEmpty() == false) {
|
if (it?.isEmpty() == false) {
|
||||||
val galleryID = it.random()
|
val galleryID = it.random()
|
||||||
|
|
||||||
GalleryDialog(
|
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||||
this@MainActivity,
|
|
||||||
Glide.with(this@MainActivity),
|
|
||||||
galleryID
|
|
||||||
).apply {
|
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
@@ -297,11 +288,21 @@ class MainActivity :
|
|||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
|
|
||||||
putExtra("galleryID", galleryID)
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivity(intent)
|
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||||
|
onChipClickedHandler.add {
|
||||||
|
runOnUiThread {
|
||||||
|
query = it.toQuery()
|
||||||
|
currentPage = 0
|
||||||
|
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
@@ -316,7 +317,7 @@ class MainActivity :
|
|||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
with(main_recyclerview) {
|
with(main_recyclerview) {
|
||||||
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply {
|
adapter = GalleryBlockAdapter(galleries).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
@@ -330,9 +331,7 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
onDownloadClickedHandler = { position ->
|
onDownloadClickedHandler = { position ->
|
||||||
val galleryID = galleries[position]
|
val galleryID = galleries[position]
|
||||||
if (Preferences["cache_disable"])
|
|
||||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
|
||||||
else {
|
|
||||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
||||||
DownloadService.cancel(this@MainActivity, galleryID)
|
DownloadService.cancel(this@MainActivity, galleryID)
|
||||||
}
|
}
|
||||||
@@ -340,7 +339,6 @@ class MainActivity :
|
|||||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||||
DownloadService.download(this@MainActivity, galleryID)
|
DownloadService.download(this@MainActivity, galleryID)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
closeAllItems()
|
closeAllItems()
|
||||||
}
|
}
|
||||||
@@ -380,13 +378,9 @@ class MainActivity :
|
|||||||
if (v !is CardView)
|
if (v !is CardView)
|
||||||
return@listener false
|
return@listener false
|
||||||
|
|
||||||
val galleryID = galleries[position]
|
val galleryID = galleries.getOrNull(position) ?: return@listener true
|
||||||
|
|
||||||
GalleryDialog(
|
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||||
this@MainActivity,
|
|
||||||
Glide.with(this@MainActivity),
|
|
||||||
galleryID
|
|
||||||
).apply {
|
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
@@ -619,14 +613,14 @@ class MainActivity :
|
|||||||
else -> {
|
else -> {
|
||||||
searchHistory.map {
|
searchHistory.map {
|
||||||
Suggestion(it)
|
Suggestion(it)
|
||||||
}.takeLast(20) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
|
}.takeLast(10) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
|
||||||
}
|
}
|
||||||
}.reversed()
|
}.reversed()
|
||||||
|
|
||||||
private var suggestionJob : Job? = null
|
private var suggestionJob : Job? = null
|
||||||
private fun setupSearchBar() {
|
private fun setupSearchBar() {
|
||||||
with(main_searchview as FloatingSearchViewDayNight) {
|
with(main_searchview as xyz.quaver.pupil.ui.view.FloatingSearchView) {
|
||||||
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
|
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
|
||||||
override fun onMenuOpened() {
|
override fun onMenuOpened() {
|
||||||
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
||||||
}
|
}
|
||||||
@@ -634,7 +628,15 @@ class MainActivity :
|
|||||||
override fun onMenuClosed() {
|
override fun onMenuClosed() {
|
||||||
//Do Nothing
|
//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 = {
|
onHistoryDeleteClickedListener = {
|
||||||
searchHistory.remove(it)
|
searchHistory.remove(it)
|
||||||
@@ -645,9 +647,11 @@ class MainActivity :
|
|||||||
swapSuggestions(defaultSuggestions)
|
swapSuggestions(defaultSuggestions)
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnMenuItemClickListener(this@MainActivity)
|
onMenuItemClickListener = {
|
||||||
|
onActionMenuItemSelected(it)
|
||||||
|
}
|
||||||
|
|
||||||
setOnQueryChangeListener { _, query ->
|
onQueryChangeListener = lambda@{ _, query ->
|
||||||
this@MainActivity.query = query
|
this@MainActivity.query = query
|
||||||
|
|
||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
@@ -655,12 +659,14 @@ class MainActivity :
|
|||||||
if (query.isEmpty() or query.endsWith(' ')) {
|
if (query.isEmpty() or query.endsWith(' ')) {
|
||||||
swapSuggestions(defaultSuggestions)
|
swapSuggestions(defaultSuggestions)
|
||||||
|
|
||||||
return@setOnQueryChangeListener
|
return@lambda
|
||||||
}
|
}
|
||||||
|
|
||||||
swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString())))
|
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 {
|
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val suggestions = kotlin.runCatching {
|
val suggestions = kotlin.runCatching {
|
||||||
@@ -681,7 +687,7 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener {
|
||||||
override fun onFocus() {
|
override fun onFocus() {
|
||||||
if (query.isEmpty() or query.endsWith(' '))
|
if (query.isEmpty() or query.endsWith(' '))
|
||||||
swapSuggestions(defaultSuggestions)
|
swapSuggestions(defaultSuggestions)
|
||||||
@@ -698,19 +704,24 @@ class MainActivity :
|
|||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
attachNavigationDrawerToMenuButton(main_drawer_layout)
|
attachNavigationDrawerToMenuButton(main_drawer_layout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionMenuItemSelected(item: MenuItem?) {
|
fun onActionMenuItemSelected(item: MenuItem?) {
|
||||||
when(item?.itemId) {
|
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 -> {
|
R.id.main_menu_thin -> {
|
||||||
|
val thin = !item.isChecked
|
||||||
|
|
||||||
|
item.isChecked = thin
|
||||||
main_recyclerview.apply {
|
main_recyclerview.apply {
|
||||||
(adapter as GalleryBlockAdapter).apply {
|
(adapter as GalleryBlockAdapter).apply {
|
||||||
isThin = !isThin
|
this.thin = thin
|
||||||
|
|
||||||
|
Preferences["thin"] = thin
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = adapter // Force to redraw
|
adapter = adapter // Force to redraw
|
||||||
@@ -968,14 +979,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,45 +18,52 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.Toast
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnticipateInterpolator
|
||||||
|
import android.view.animation.OvershootInterpolator
|
||||||
|
import android.view.animation.TranslateAnimation
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.PagerSnapHelper
|
import androidx.recyclerview.widget.PagerSnapHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import com.google.mlkit.vision.face.Face
|
||||||
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||||
import kotlinx.android.synthetic.main.activity_reader.*
|
import kotlinx.android.synthetic.main.activity_reader.*
|
||||||
import kotlinx.android.synthetic.main.activity_reader.view.*
|
import kotlinx.android.synthetic.main.activity_reader.view.*
|
||||||
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
||||||
|
import kotlinx.android.synthetic.main.reader_eye_card.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import xyz.quaver.Code
|
import xyz.quaver.Code
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.histories
|
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import xyz.quaver.pupil.util.camera
|
||||||
|
import xyz.quaver.pupil.util.closeCamera
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.util.*
|
import xyz.quaver.pupil.util.startCamera
|
||||||
import kotlin.concurrent.schedule
|
|
||||||
import kotlin.concurrent.timer
|
|
||||||
|
|
||||||
class ReaderActivity : BaseActivity() {
|
class ReaderActivity : BaseActivity() {
|
||||||
|
|
||||||
@@ -69,18 +76,18 @@ class ReaderActivity : BaseActivity() {
|
|||||||
field = value
|
field = value
|
||||||
|
|
||||||
(reader_recyclerview.adapter as ReaderAdapter).isFullScreen = value
|
(reader_recyclerview.adapter as ReaderAdapter).isFullScreen = value
|
||||||
|
|
||||||
reader_progressbar.visibility = when {
|
|
||||||
value -> View.VISIBLE
|
|
||||||
else -> View.GONE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var cache: Cache
|
private lateinit var cache: Cache
|
||||||
var downloader: DownloadService? = null
|
var downloader: DownloadService? = null
|
||||||
private val conn = object: ServiceConnection {
|
private val conn = object: ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
downloader = (service as DownloadService.Binder).service
|
downloader = (service as DownloadService.Binder).service.also {
|
||||||
|
it.priority = 0
|
||||||
|
|
||||||
|
if (!it.progress.containsKey(galleryID))
|
||||||
|
DownloadService.download(this@ReaderActivity, galleryID, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
@@ -88,13 +95,29 @@ class ReaderActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val timer = Timer()
|
|
||||||
private var autoTimer: Timer? = null
|
|
||||||
|
|
||||||
private val snapHelper = PagerSnapHelper()
|
private val snapHelper = PagerSnapHelper()
|
||||||
|
|
||||||
private var menu: Menu? = null
|
private var menu: Menu? = null
|
||||||
|
|
||||||
|
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||||
|
if (isGranted)
|
||||||
|
toggleCamera()
|
||||||
|
else
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.error)
|
||||||
|
.setMessage(R.string.camera_denied)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Eye {
|
||||||
|
LEFT,
|
||||||
|
RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cameraEnabled = false
|
||||||
|
private var eyeType: Eye? = null
|
||||||
|
private var eyeTime: Long = 0L
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_reader)
|
setContentView(R.layout.activity_reader)
|
||||||
@@ -111,38 +134,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences["cache_disable"]) {
|
initDownloadListener()
|
||||||
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()
|
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,17 +211,32 @@ class ReaderActivity : BaseActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onResume() {
|
||||||
super.onDestroy()
|
super.onResume()
|
||||||
|
|
||||||
timer.cancel()
|
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
|
||||||
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
|
|
||||||
|
|
||||||
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
|
if (cameraEnabled)
|
||||||
DownloadService.cancel(this, galleryID)
|
startCamera(this, cameraCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
closeCamera()
|
||||||
|
|
||||||
if (downloader != null)
|
if (downloader != null)
|
||||||
unbindService(conn)
|
unbindService(conn)
|
||||||
|
|
||||||
|
downloader?.priority = galleryID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
|
||||||
|
update = false
|
||||||
|
|
||||||
|
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
|
||||||
|
DownloadService.cancel(this, galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -264,48 +271,51 @@ class ReaderActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initDownloader() {
|
private var update = true
|
||||||
DownloadService.download(this, galleryID, true)
|
private fun initDownloadListener() {
|
||||||
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
while (update) {
|
||||||
|
delay(1000)
|
||||||
|
|
||||||
timer.schedule(1000, 1000) {
|
val downloader = downloader ?: continue
|
||||||
val downloader = downloader ?: return@schedule
|
|
||||||
|
|
||||||
if (downloader.progress.indexOfKey(galleryID) < 0) //loading
|
if (!downloader.progress.containsKey(galleryID)) //loading
|
||||||
return@schedule
|
continue
|
||||||
|
|
||||||
if (downloader.progress[galleryID] == null) { //Gallery not found
|
if (downloader.progress[galleryID]?.isEmpty() == true) { //Gallery not found
|
||||||
timer.cancel()
|
update = false
|
||||||
Snackbar
|
Snackbar
|
||||||
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||||
.show()
|
.show()
|
||||||
|
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
histories.add(galleryID)
|
|
||||||
|
|
||||||
runOnUiThread {
|
|
||||||
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||||
reader_download_progressbar.progress = downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
reader_download_progressbar.progress =
|
||||||
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
||||||
|
|
||||||
if (title == getString(R.string.reader_loading)) {
|
if (title == getString(R.string.reader_loading)) {
|
||||||
val reader = cache.metadata.reader
|
val reader = cache.metadata.reader
|
||||||
|
|
||||||
if (reader != null) {
|
if (reader != null) {
|
||||||
with (reader_recyclerview.adapter as ReaderAdapter) {
|
with(reader_recyclerview.adapter as ReaderAdapter) {
|
||||||
this.reader = reader
|
this.reader = reader
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
title = reader.galleryInfo.title
|
title = reader.galleryInfo.title
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}"
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title =
|
||||||
|
"$currentPage/${reader.galleryInfo.files.size}"
|
||||||
|
|
||||||
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
|
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(
|
||||||
|
this@ReaderActivity,
|
||||||
when (reader.code) {
|
when (reader.code) {
|
||||||
Code.HITOMI -> R.drawable.hitomi
|
Code.HITOMI -> R.drawable.hitomi
|
||||||
Code.HIYOBI -> R.drawable.ic_hiyobi
|
Code.HIYOBI -> R.drawable.ic_hiyobi
|
||||||
else -> android.R.color.transparent
|
else -> android.R.color.transparent
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,7 +359,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
return
|
return
|
||||||
currentPage = layoutManager.findFirstVisibleItemPosition()+1
|
currentPage = layoutManager.findFirstVisibleItemPosition()+1
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}"
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}"
|
||||||
this@ReaderActivity.reader_progressbar.progress = currentPage
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -358,9 +368,6 @@ class ReaderActivity : BaseActivity() {
|
|||||||
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
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)
|
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
|
||||||
|
|
||||||
if (downloadManager.isDownloading(galleryID)) {
|
if (downloadManager.isDownloading(galleryID)) {
|
||||||
@@ -368,40 +375,35 @@ class ReaderActivity : BaseActivity() {
|
|||||||
animateDownloadFAB(false)
|
animateDownloadFAB(false)
|
||||||
} else {
|
} else {
|
||||||
downloadManager.addDownloadFolder(galleryID)
|
downloadManager.addDownloadFolder(galleryID)
|
||||||
|
DownloadService.download(context, galleryID, true)
|
||||||
animateDownloadFAB(true)
|
animateDownloadFAB(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
with(reader_fab_retry) {
|
with(reader_fab_retry) {
|
||||||
setImageResource(R.drawable.refresh)
|
setImageResource(R.drawable.refresh)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
downloader?.cancel(galleryID)
|
DownloadService.download(context, galleryID)
|
||||||
downloader?.download(galleryID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
with(reader_fab_auto) {
|
with(reader_fab_auto) {
|
||||||
setImageResource(R.drawable.clock_start)
|
setImageResource(R.drawable.eye_white)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
if (autoTimer == null) {
|
when {
|
||||||
autoTimer = timer(initialDelay = 10000L, period = 10000L) {
|
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED -> {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
toggleCamera()
|
||||||
with(this@ReaderActivity.reader_recyclerview) {
|
|
||||||
val lastItem =
|
|
||||||
(layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
|
|
||||||
|
|
||||||
if (lastItem < adapter!!.itemCount - 1)
|
|
||||||
(layoutManager as LinearLayoutManager).scrollToPosition(lastItem + 1)
|
|
||||||
}
|
}
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
else ->
|
||||||
setImageResource(R.drawable.clock_end)
|
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||||
} else {
|
|
||||||
autoTimer?.cancel()
|
|
||||||
autoTimer = null
|
|
||||||
setImageResource(R.drawable.clock_start)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,13 +490,119 @@ class ReaderActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLowMemory() {
|
val cameraCallback: (List<Face>) -> Unit = callback@{ faces ->
|
||||||
super.onLowMemory()
|
eye_card.dot.let {
|
||||||
Glide.get(this).onLowMemory()
|
it.visibility = View.VISIBLE
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
delay(50)
|
||||||
|
it.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTrimMemory(level: Int) {
|
if (faces.size != 1)
|
||||||
super.onTrimMemory(level)
|
ContextCompat.getDrawable(this, R.drawable.eye_off).let {
|
||||||
Glide.get(this).onTrimMemory(level)
|
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
|
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.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.favorites
|
|
||||||
import xyz.quaver.pupil.ui.fragment.LockSettingsFragment
|
|
||||||
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
||||||
import xyz.quaver.pupil.util.Preferences
|
|
||||||
import xyz.quaver.pupil.util.normalizeID
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
class SettingsActivity : BaseActivity() {
|
class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
@@ -56,19 +42,4 @@ class SettingsActivity : BaseActivity() {
|
|||||||
|
|
||||||
return true
|
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")
|
@SuppressLint("InflateParams")
|
||||||
private fun build(): View {
|
private fun build(): View {
|
||||||
val galleryID = Cache.instances.let { if (it.size() == 0) 1199708 else it.keyAt((0 until it.size()).random()) }
|
val galleryID = Cache.instances.let { if (it.size == 0) 1199708 else it.keys.elementAt((0 until it.size).random()) }
|
||||||
val galleryBlock = runBlocking {
|
val galleryBlock = runBlocking {
|
||||||
Cache.getInstance(requireContext(), galleryID).getGalleryBlock()
|
Cache.getInstance(requireContext(), galleryID).getGalleryBlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,26 +26,87 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.android.synthetic.main.dialog_download_folder_name.view.*
|
||||||
import kotlinx.android.synthetic.main.item_download_folder.view.*
|
import kotlinx.android.synthetic.main.item_download_folder.view.*
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
|
import xyz.quaver.io.util.toFile
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.byteToString
|
import xyz.quaver.pupil.util.byteToString
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.migrate
|
import xyz.quaver.pupil.util.migrate
|
||||||
import xyz.quaver.pupil.util.normalizeID
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class DownloadLocationDialogFragment : DialogFragment() {
|
class DownloadLocationDialogFragment : DialogFragment() {
|
||||||
private val entries = mutableMapOf<File?, View>()
|
private val entries = mutableMapOf<File?, View>()
|
||||||
|
|
||||||
|
private val requestDownloadFolderLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
|
val activity = activity ?: return@registerForActivityResult
|
||||||
|
val context = context ?: return@registerForActivityResult
|
||||||
|
val dialog = dialog ?: return@registerForActivityResult
|
||||||
|
|
||||||
|
it.data?.data?.also { uri ->
|
||||||
|
val takeFlags: Int =
|
||||||
|
activity.intent.flags and
|
||||||
|
(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||||
|
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
|
|
||||||
|
if (kotlin.runCatching { FileX(context, uri).canWrite() }.getOrDefault(false)) {
|
||||||
|
entries[null]?.location_available?.text = uri.toFile(context)?.canonicalPath
|
||||||
|
Preferences["download_folder"] = uri.toString()
|
||||||
|
} else {
|
||||||
|
Snackbar.make(
|
||||||
|
dialog.window!!.decorView.rootView,
|
||||||
|
R.string.settings_download_folder_not_writable,
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
|
||||||
|
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
|
||||||
|
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
|
||||||
|
entries[key]!!.button.isChecked = true
|
||||||
|
if (key == null) entries[key]!!.location_available.text = downloadFolder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requestDownloadFolderOldLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
val context = context ?: return@registerForActivityResult
|
||||||
|
val dialog = dialog ?: return@registerForActivityResult
|
||||||
|
|
||||||
|
if (it.resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
|
||||||
|
val directory = it.data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
|
||||||
|
|
||||||
|
if (!File(directory).canWrite()) {
|
||||||
|
Snackbar.make(
|
||||||
|
dialog.window!!.decorView.rootView,
|
||||||
|
R.string.settings_download_folder_not_writable,
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
|
||||||
|
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
|
||||||
|
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
|
||||||
|
entries[key]!!.button.isChecked = true
|
||||||
|
if (key == null) entries[key]!!.location_available.text = downloadFolder
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entries[null]?.location_available?.text = directory
|
||||||
|
Preferences["download_folder"] = File(directory).toURI().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
private fun build() : View? {
|
private fun build() : View? {
|
||||||
val context = context ?: return null
|
val context = context ?: return null
|
||||||
@@ -90,7 +151,7 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
startActivityForResult(intent, R.id.request_download_folder.normalizeID())
|
requestDownloadFolderLauncher.launch(intent)
|
||||||
} else { // Can't use SAF on old Androids!
|
} else { // Can't use SAF on old Androids!
|
||||||
val config = DirectoryChooserConfig.builder()
|
val config = DirectoryChooserConfig.builder()
|
||||||
.newDirectoryName("Pupil")
|
.newDirectoryName("Pupil")
|
||||||
@@ -101,7 +162,7 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
|
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
startActivityForResult(intent, R.id.request_download_folder_old.normalizeID())
|
requestDownloadFolderOldLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entries[null] = this
|
entries[null] = this
|
||||||
@@ -132,65 +193,4 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
when (requestCode) {
|
|
||||||
R.id.request_download_folder.normalizeID() -> {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
val activity = activity ?: return
|
|
||||||
val context = context ?: return
|
|
||||||
val dialog = dialog ?: return
|
|
||||||
|
|
||||||
data?.data?.also { uri ->
|
|
||||||
val takeFlags: Int =
|
|
||||||
activity.intent.flags and
|
|
||||||
(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
|
||||||
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
|
||||||
|
|
||||||
if (kotlin.runCatching { FileX(context, uri).canWrite() }.getOrDefault(false))
|
|
||||||
Preferences["download_folder"] = uri.toString()
|
|
||||||
else {
|
|
||||||
Snackbar.make(
|
|
||||||
dialog.window!!.decorView.rootView,
|
|
||||||
R.string.settings_download_folder_not_writable,
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
|
|
||||||
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
|
|
||||||
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
|
|
||||||
entries[key]!!.button.isChecked = true
|
|
||||||
if (key == null) entries[key]!!.location_available.text = downloadFolder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.request_download_folder_old.normalizeID() -> {
|
|
||||||
val context = context ?: return
|
|
||||||
val dialog = dialog ?: return
|
|
||||||
|
|
||||||
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
|
|
||||||
val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
|
|
||||||
|
|
||||||
if (!File(directory).canWrite()) {
|
|
||||||
Snackbar.make(
|
|
||||||
dialog.window!!.decorView.rootView,
|
|
||||||
R.string.settings_download_folder_not_writable,
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
|
|
||||||
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
|
|
||||||
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
|
|
||||||
entries[key]!!.button.isChecked = true
|
|
||||||
if (key == null) entries[key]!!.location_available.text = downloadFolder
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Preferences["download_folder"] = File(directory).canonicalPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -18,18 +18,19 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.dialog
|
package xyz.quaver.pupil.ui.dialog
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout.LayoutParams
|
import android.widget.LinearLayout.LayoutParams
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.android.synthetic.main.dialog_gallery.*
|
import kotlinx.android.synthetic.main.dialog_gallery.*
|
||||||
import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
|
import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
|
||||||
@@ -41,10 +42,10 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import xyz.quaver.hitomi.Gallery
|
import xyz.quaver.hitomi.Gallery
|
||||||
import xyz.quaver.hitomi.getGallery
|
import xyz.quaver.hitomi.getGallery
|
||||||
import xyz.quaver.pupil.BuildConfig
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||||
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
import xyz.quaver.pupil.histories
|
import xyz.quaver.pupil.histories
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
@@ -52,8 +53,10 @@ import xyz.quaver.pupil.ui.view.TagChip
|
|||||||
import xyz.quaver.pupil.util.ItemClickSupport
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) {
|
class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(context) {
|
||||||
|
|
||||||
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
||||||
|
|
||||||
@@ -74,7 +77,6 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||||
putExtra("galleryID", galleryID)
|
putExtra("galleryID", galleryID)
|
||||||
})
|
})
|
||||||
histories.add(galleryID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,19 +106,19 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glide
|
gallery_cover.showImage(Uri.parse(gallery.cover))
|
||||||
.load(gallery.cover)
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
}.into(gallery_cover)
|
|
||||||
|
|
||||||
addDetails(gallery)
|
addDetails(gallery)
|
||||||
addThumbnails(gallery)
|
addThumbnails(gallery)
|
||||||
addRelated(gallery)
|
addRelated(gallery)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show()
|
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).apply {
|
||||||
|
if (Locale.getDefault().language == "ko")
|
||||||
|
setAction(context.getText(R.string.https_text)) {
|
||||||
|
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.https))))
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +143,18 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
listOf(gallery.language).map { Tag("language", it) },
|
listOf(gallery.language).map { Tag("language", it) },
|
||||||
gallery.series.map { Tag("series", it) },
|
gallery.series.map { Tag("series", it) },
|
||||||
gallery.characters.map { Tag("character", it) },
|
gallery.characters.map { Tag("character", it) },
|
||||||
gallery.tags.map {
|
gallery.tags.sortedBy {
|
||||||
|
val tag = Tag.parse(it)
|
||||||
|
|
||||||
|
if (favoriteTags.contains(tag))
|
||||||
|
-1
|
||||||
|
else
|
||||||
|
when(Tag.parse(it).area) {
|
||||||
|
"female" -> 0
|
||||||
|
"male" -> 1
|
||||||
|
else -> 2
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
Tag.parse(it).let { tag ->
|
Tag.parse(it).let { tag ->
|
||||||
when {
|
when {
|
||||||
tag.area != null -> tag
|
tag.area != null -> tag
|
||||||
@@ -183,7 +196,8 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
gallery_details.setText(R.string.gallery_thumbnails)
|
gallery_details.setText(R.string.gallery_thumbnails)
|
||||||
|
|
||||||
val pager = ViewPager2(context).apply {
|
val pager = ViewPager2(context).apply {
|
||||||
adapter = ThumbnailPageAdapter(glide, gallery.thumbnails)
|
adapter = ThumbnailPageAdapter(gallery.thumbnails)
|
||||||
|
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
gallery_details_contents.addView(
|
gallery_details_contents.addView(
|
||||||
@@ -203,7 +217,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
val galleries = ArrayList<Int>()
|
val galleries = ArrayList<Int>()
|
||||||
|
|
||||||
val adapter = GalleryBlockAdapter(glide, galleries).apply {
|
val adapter = GalleryBlockAdapter(galleries).apply {
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||||
handler.invoke(tag)
|
handler.invoke(tag)
|
||||||
@@ -223,14 +237,9 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
|||||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||||
putExtra("galleryID", galleries[position])
|
putExtra("galleryID", galleries[position])
|
||||||
})
|
})
|
||||||
histories.add(galleries[position])
|
|
||||||
}
|
}
|
||||||
onItemLongClickListener = { _, position, _ ->
|
onItemLongClickListener = { _, position, _ ->
|
||||||
GalleryDialog(
|
GalleryDialog(context, galleries[position]).apply {
|
||||||
context,
|
|
||||||
glide,
|
|
||||||
galleries[position]
|
|
||||||
).apply {
|
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import kotlinx.android.synthetic.main.dialog_proxy.view.*
|
import kotlinx.android.synthetic.main.dialog_proxy.view.*
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -40,11 +41,10 @@ import xyz.quaver.pupil.util.getProxyInfo
|
|||||||
import xyz.quaver.pupil.util.proxyInfo
|
import xyz.quaver.pupil.util.proxyInfo
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
|
||||||
class ProxyDialog(context: Context) : Dialog(context) {
|
class ProxyDialog(context: Context) : AlertDialog(context) {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
setContentView(build())
|
setView(build())
|
||||||
window?.attributes?.width = ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import xyz.quaver.io.util.deleteRecursively
|
|||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.histories
|
import xyz.quaver.pupil.histories
|
||||||
import xyz.quaver.pupil.util.byteToString
|
import xyz.quaver.pupil.util.byteToString
|
||||||
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -61,6 +62,8 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
if (dir.exists())
|
if (dir.exists())
|
||||||
dir.deleteRecursively()
|
dir.deleteRecursively()
|
||||||
|
|
||||||
|
Cache.instances.clear()
|
||||||
|
|
||||||
summary = context.getString(R.string.settings_storage_usage, byteToString(0))
|
summary = context.getString(R.string.settings_storage_usage, byteToString(0))
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
var size = 0L
|
var size = 0L
|
||||||
|
|||||||
@@ -22,25 +22,21 @@ import android.app.Activity
|
|||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
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 xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.favorites
|
|
||||||
import xyz.quaver.pupil.ui.LockActivity
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
import xyz.quaver.pupil.ui.SettingsActivity
|
import xyz.quaver.pupil.ui.SettingsActivity
|
||||||
import xyz.quaver.pupil.ui.dialog.*
|
import xyz.quaver.pupil.ui.dialog.*
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.nio.charset.Charset
|
|
||||||
|
|
||||||
class SettingsFragment :
|
class SettingsFragment :
|
||||||
PreferenceFragmentCompat(),
|
PreferenceFragmentCompat(),
|
||||||
@@ -48,6 +44,16 @@ class SettingsFragment :
|
|||||||
Preference.OnPreferenceChangeListener,
|
Preference.OnPreferenceChangeListener,
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private val lockLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if (it.resultCode == Activity.RESULT_OK) {
|
||||||
|
parentFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.settings, LockSettingsFragment())
|
||||||
|
.addToBackStack("Lock")
|
||||||
|
.commitAllowingStateLoss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
@@ -89,7 +95,7 @@ class SettingsFragment :
|
|||||||
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
putExtra("force", true)
|
putExtra("force", true)
|
||||||
}
|
}
|
||||||
startActivityForResult(intent, R.id.request_lock.normalizeID())
|
lockLauncher.launch(intent)
|
||||||
}
|
}
|
||||||
"mirrors" -> {
|
"mirrors" -> {
|
||||||
MirrorDialog(requireContext())
|
MirrorDialog(requireContext())
|
||||||
@@ -267,19 +273,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/>.
|
* 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.content.Context
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
@@ -36,21 +36,21 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter
|
import xyz.quaver.floatingsearchview.FloatingSearchView
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import com.arlib.floatingsearchview.util.view.SearchInputView
|
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.R
|
||||||
import xyz.quaver.pupil.favoriteTags
|
import xyz.quaver.pupil.favoriteTags
|
||||||
import xyz.quaver.pupil.types.*
|
import xyz.quaver.pupil.types.*
|
||||||
import java.util.*
|
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(context, attrs),
|
||||||
FloatingSearchView.OnSearchListener,
|
FloatingSearchView.OnSearchListener,
|
||||||
SearchSuggestionsAdapter.OnBindSuggestionCallback,
|
|
||||||
TextWatcher
|
TextWatcher
|
||||||
{
|
{
|
||||||
|
|
||||||
private val searchInputView = findViewById<SearchInputView>(R.id.search_bar_text)
|
private val searchInputView = findViewById<SearchInputView>(R.id.search_bar_text)
|
||||||
|
|
||||||
var onHistoryDeleteClickedListener: ((String) -> Unit)? = null
|
var onHistoryDeleteClickedListener: ((String) -> Unit)? = null
|
||||||
@@ -60,8 +60,10 @@ class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, att
|
|||||||
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
||||||
|
|
||||||
searchInputView.addTextChangedListener(this)
|
searchInputView.addTextChangedListener(this)
|
||||||
setOnSearchListener(this)
|
onSearchListener = this
|
||||||
setOnBindSuggestionCallback(this)
|
onBindSuggestionCallback = { a, b, c, d, e ->
|
||||||
|
onBindSuggestion(a, b, c, d, e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
override fun 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?) {
|
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
||||||
when (searchSuggestion) {
|
when (searchSuggestion) {
|
||||||
is TagSuggestion -> {
|
is TagSuggestion -> {
|
||||||
with(searchInputView.text) {
|
val tag = "${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")}"
|
||||||
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
with(searchInputView.text!!) {
|
||||||
append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ")
|
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ') + 1, length)
|
||||||
|
|
||||||
|
if (!this.contains(tag))
|
||||||
|
append("$tag ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Suggestion -> {
|
is Suggestion -> {
|
||||||
with(searchInputView.text) {
|
with(searchInputView.text!!) {
|
||||||
clear()
|
clear()
|
||||||
append(searchSuggestion.str)
|
append(searchSuggestion.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is FavoriteHistorySwitch -> onFavoriteHistorySwitchClickListener?.invoke()
|
is FavoriteHistorySwitch -> onFavoriteHistorySwitchClickListener?.invoke()
|
||||||
@@ -99,7 +104,7 @@ class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, att
|
|||||||
|
|
||||||
override fun onSearchAction(currentQuery: String?) {}
|
override fun onSearchAction(currentQuery: String?) {}
|
||||||
|
|
||||||
override fun onBindSuggestion(
|
fun onBindSuggestion(
|
||||||
suggestionView: View?,
|
suggestionView: View?,
|
||||||
leftIcon: ImageView?,
|
leftIcon: ImageView?,
|
||||||
textView: TextView?,
|
textView: TextView?,
|
||||||
@@ -196,7 +201,7 @@ class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, att
|
|||||||
isClickable = true
|
isClickable = true
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
onHistoryDeleteClickedListener?.invoke(item.str)
|
onHistoryDeleteClickedListener?.invoke(item.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,10 +217,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,18 @@ import android.content.Context
|
|||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class TagChip(context: Context, tag: Tag) : Chip(context) {
|
class TagChip(context: Context, _tag: Tag) : Chip(context) {
|
||||||
|
|
||||||
val tag: Tag =
|
val tag: Tag =
|
||||||
tag.let {
|
_tag.let {
|
||||||
when {
|
when {
|
||||||
it.area != null -> it
|
it.area != null -> it
|
||||||
else -> Tag("tag", tag.tag)
|
else -> Tag("tag", _tag.tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,18 +45,47 @@ class TagChip(context: Context, tag: Tag) : Chip(context) {
|
|||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
chipIcon = when(tag.area) {
|
when(tag.area) {
|
||||||
"male" -> {
|
"male" -> {
|
||||||
setChipBackgroundColorResource(R.color.material_blue_700)
|
setChipBackgroundColorResource(R.color.material_blue_700)
|
||||||
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||||
ContextCompat.getDrawable(context, R.drawable.gender_male_white)
|
setCloseIconTintResource(android.R.color.white)
|
||||||
|
chipIcon = ContextCompat.getDrawable(context, R.drawable.gender_male_white)
|
||||||
}
|
}
|
||||||
"female" -> {
|
"female" -> {
|
||||||
setChipBackgroundColorResource(R.color.material_pink_600)
|
setChipBackgroundColorResource(R.color.material_pink_600)
|
||||||
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
setTextColor(ContextCompat.getColor(context, android.R.color.white))
|
||||||
ContextCompat.getDrawable(context, R.drawable.gender_female_white)
|
setCloseIconTintResource(android.R.color.white)
|
||||||
|
chipIcon = ContextCompat.getDrawable(context, R.drawable.gender_female_white)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (favoriteTags.contains(tag))
|
||||||
|
setChipBackgroundColorResource(R.color.material_orange_500)
|
||||||
|
|
||||||
|
isCloseIconVisible = true
|
||||||
|
closeIcon = ContextCompat.getDrawable(context,
|
||||||
|
if (favoriteTags.contains(tag))
|
||||||
|
R.drawable.ic_star_filled
|
||||||
|
else
|
||||||
|
R.drawable.ic_star_empty
|
||||||
|
)
|
||||||
|
|
||||||
|
setOnCloseIconClickListener {
|
||||||
|
if (favoriteTags.contains(tag)) {
|
||||||
|
favoriteTags.remove(tag)
|
||||||
|
closeIcon = ContextCompat.getDrawable(context, R.drawable.ic_star_empty)
|
||||||
|
|
||||||
|
when(tag.area) {
|
||||||
|
"male" -> setChipBackgroundColorResource(R.color.material_blue_700)
|
||||||
|
"female" -> setChipBackgroundColorResource(R.color.material_pink_600)
|
||||||
|
else -> chipBackgroundColor = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
favoriteTags.add(tag)
|
||||||
|
closeIcon = ContextCompat.getDrawable(context, R.drawable.ic_star_filled)
|
||||||
|
setChipBackgroundColorResource(R.color.material_orange_500)
|
||||||
}
|
}
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text = when (tag.area) {
|
text = when (tag.area) {
|
||||||
|
|||||||
90
app/src/main/java/xyz/quaver/pupil/ui/view/TagChipGroup.kt
Normal file
90
app/src/main/java/xyz/quaver/pupil/ui/view/TagChipGroup.kt
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
this.removeAllViews()
|
||||||
|
|
||||||
|
tags.take(maxChipSize).forEach {
|
||||||
|
this.addView(TagChip(context, it).apply {
|
||||||
|
setOnClickListener {
|
||||||
|
onClickListener?.invoke(this.tag)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxChipSize > 0 && this.size > maxChipSize)
|
||||||
|
addView(moreView)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
applyAttributes(context.obtainStyledAttributes(attr, R.styleable.TagChipGroup))
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -22,8 +22,9 @@ import kotlinx.serialization.*
|
|||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
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 {
|
class SavedSet <T: Any> (private val file: File, private val any: T, private val set: MutableSet<T> = Collections.synchronizedSet(mutableSetOf())) : MutableSet<T> by set {
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@@ -59,6 +60,8 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
|
|||||||
override fun add(element: T): Boolean {
|
override fun add(element: T): Boolean {
|
||||||
load()
|
load()
|
||||||
|
|
||||||
|
set.remove(element)
|
||||||
|
|
||||||
return set.add(element).also {
|
return set.add(element).also {
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,8 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
|
|||||||
override fun addAll(elements: Collection<T>): Boolean {
|
override fun addAll(elements: Collection<T>): Boolean {
|
||||||
load()
|
load()
|
||||||
|
|
||||||
|
set.removeAll(elements)
|
||||||
|
|
||||||
return set.addAll(elements).also {
|
return set.addAll(elements).also {
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|||||||
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,389 +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) {
|
|
||||||
if (response.code() != 200) {
|
|
||||||
response.close()
|
|
||||||
onFailure(call, IOException())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.util.SparseArray
|
import android.net.Uri
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
@@ -37,7 +39,9 @@ import xyz.quaver.io.FileX
|
|||||||
import xyz.quaver.io.util.*
|
import xyz.quaver.io.util.*
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
@@ -51,7 +55,7 @@ data class Metadata(
|
|||||||
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val instances = SparseArray<Cache>()
|
val instances = ConcurrentHashMap<Int, Cache>()
|
||||||
|
|
||||||
fun getInstance(context: Context, galleryID: Int) =
|
fun getInstance(context: Context, galleryID: Int) =
|
||||||
instances[galleryID] ?: synchronized(this) {
|
instances[galleryID] ?: synchronized(this) {
|
||||||
@@ -59,9 +63,9 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun delete(galleryID: Int) {
|
fun delete(context: Context, galleryID: Int) {
|
||||||
instances[galleryID]?.cacheFolder?.deleteRecursively()
|
File(context.cacheDir, "imageCache/$galleryID").deleteRecursively()
|
||||||
instances.delete(galleryID)
|
instances.remove(galleryID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,8 +135,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
suspend fun getThumbnail(): ByteArray? =
|
suspend fun getThumbnail(): Uri =
|
||||||
findFile(".thumbnail")?.readBytes()
|
findFile(".thumbnail")?.uri
|
||||||
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
|
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
@@ -140,10 +144,15 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() }
|
client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() }
|
||||||
}.getOrNull()?.also { kotlin.run {
|
}.getOrNull()?.let { thumbnail -> kotlin.runCatching {
|
||||||
cacheFolder.getChild(".thumbnail").writeBytes(it)
|
cacheFolder.getChild(".thumbnail").also {
|
||||||
} }
|
if (!it.exists())
|
||||||
} }
|
it.createNewFile()
|
||||||
|
|
||||||
|
it.writeBytes(thumbnail)
|
||||||
|
}
|
||||||
|
}.getOrNull()?.uri }
|
||||||
|
} } ?: Uri.EMPTY
|
||||||
|
|
||||||
suspend fun getReader(): Reader? {
|
suspend fun getReader(): Reader? {
|
||||||
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
||||||
@@ -185,21 +194,57 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getImage(index: Int): FileX? =
|
fun getImage(index: Int): FileX? =
|
||||||
metadata.imageList?.get(index)?.let { findFile(it) }
|
metadata.imageList?.getOrNull(index)?.let { findFile(it) }
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun putImage(index: Int, fileName: String, data: ByteArray) {
|
fun putImage(index: Int, fileName: String, data: ByteArray) {
|
||||||
val file = cacheFolder.getChild(fileName)
|
val file = cacheFolder.getChild(fileName)
|
||||||
|
|
||||||
|
if (!file.exists())
|
||||||
file.createNewFile()
|
file.createNewFile()
|
||||||
file.writeBytes(data)
|
file.writeBytes(data)
|
||||||
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val lock = ConcurrentHashMap<Int, Mutex>()
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val downloadFolder = downloadFolder ?: return@launch
|
val downloadFolder = downloadFolder ?: return@launch
|
||||||
|
|
||||||
|
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 ->
|
metadata.imageList?.forEach { imageName ->
|
||||||
imageName ?: return@forEach
|
imageName ?: return@forEach
|
||||||
val target = downloadFolder.getChild(imageName)
|
val target = downloadFolder.getChild(imageName)
|
||||||
@@ -209,22 +254,16 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
return@forEach
|
return@forEach
|
||||||
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
|
if (!target.exists())
|
||||||
target.createNewFile()
|
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")
|
cacheFolder.deleteRecursively()
|
||||||
val downloadMetadata = downloadFolder.getChild(".metadata")
|
|
||||||
|
|
||||||
if (cacheMetadata.exists()) {
|
|
||||||
kotlin.runCatching {
|
|
||||||
downloadMetadata.createNewFile()
|
|
||||||
downloadMetadata.writeText(Json.encodeToString(metadata))
|
|
||||||
cacheMetadata.delete()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheFolder.delete()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,9 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
val folder = downloadFolder.getChild(name)
|
val folder = downloadFolder.getChild(name)
|
||||||
|
|
||||||
if (!folder.exists())
|
if (folder.exists())
|
||||||
|
return
|
||||||
|
|
||||||
folder.mkdir()
|
folder.mkdir()
|
||||||
|
|
||||||
downloadFolderMap[galleryID] = folder.name
|
downloadFolderMap[galleryID] = folder.name
|
||||||
|
|||||||
@@ -19,35 +19,47 @@
|
|||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.storage.StorageManager
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import androidx.core.content.ContextCompat
|
import kotlinx.coroutines.Dispatchers
|
||||||
import androidx.core.net.toUri
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import xyz.quaver.pupil.histories
|
||||||
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.lang.reflect.Array
|
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
val mutex = Mutex()
|
||||||
@Deprecated("Use downloader.Cache instead")
|
fun cleanCache(context: Context) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
fun getCachedGallery(context: Context, galleryID: Int) =
|
if (mutex.isLocked) return@launch
|
||||||
File(getDownloadDirectory(context), galleryID.toString()).let {
|
|
||||||
if (it.exists())
|
mutex.withLock {
|
||||||
it
|
val cacheFolder = File(context.cacheDir, "imageCache")
|
||||||
else
|
val downloadManager = DownloadManager.getInstance(context)
|
||||||
File(context.cacheDir, "imageCache/$galleryID")
|
|
||||||
|
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")
|
size
|
||||||
@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)!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
if (cacheSize.invoke() > limit)
|
||||||
@Deprecated("Use FileX instead")
|
while (cacheSize.invoke() > limit/2) {
|
||||||
fun File.isParentOf(another: File) =
|
val caches = cacheFolder.list() ?: return@withLock
|
||||||
another.absolutePath.startsWith(this.absolutePath)
|
|
||||||
|
(histories.firstOrNull {
|
||||||
|
caches.contains(it.toString()) && !downloadManager.isDownloading(it)
|
||||||
|
} ?: return@withLock).let {
|
||||||
|
Cache.delete(context, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,14 +93,14 @@ fun GalleryBlock.formatDownloadFolder(): String =
|
|||||||
formatMap.entries.fold(it) { str, (k, v) ->
|
formatMap.entries.fold(it) { str, (k, v) ->
|
||||||
str.replace(k, v.invoke(this), true)
|
str.replace(k, v.invoke(this), true)
|
||||||
}
|
}
|
||||||
}.replace("/", "")
|
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||||
|
|
||||||
fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
||||||
format.let {
|
format.let {
|
||||||
formatMap.entries.fold(it) { str, (k, v) ->
|
formatMap.entries.fold(it) { str, (k, v) ->
|
||||||
str.replace(k, v.invoke(this), true)
|
str.replace(k, v.invoke(this), true)
|
||||||
}
|
}
|
||||||
}.replace("/", "")
|
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||||
|
|
||||||
val Reader.requestBuilders: List<Request.Builder>
|
val Reader.requestBuilders: List<Request.Builder>
|
||||||
get() {
|
get() {
|
||||||
|
|||||||
@@ -27,13 +27,11 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -52,7 +50,9 @@ import xyz.quaver.hitomi.getGalleryBlock
|
|||||||
import xyz.quaver.hitomi.getReader
|
import xyz.quaver.hitomi.getReader
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.io.util.*
|
import xyz.quaver.io.util.readText
|
||||||
|
import xyz.quaver.io.util.writeBytes
|
||||||
|
import xyz.quaver.io.util.writeText
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
@@ -160,7 +160,6 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
val msg = extractReleaseNote(update, Locale.getDefault())
|
val msg = extractReleaseNote(update, Locale.getDefault())
|
||||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
|
||||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
|
||||||
//Cancel any download queued before
|
//Cancel any download queued before
|
||||||
@@ -314,7 +313,7 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
synchronized(Cache) {
|
synchronized(Cache) {
|
||||||
Cache.delete(galleryID)
|
Cache.delete(this@migrate, galleryID)
|
||||||
}
|
}
|
||||||
downloadFolderMap[galleryID] = folder.name
|
downloadFolderMap[galleryID] = folder.name
|
||||||
|
|
||||||
|
|||||||
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>
|
||||||
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,4 +1,4 @@
|
|||||||
<!--drawable/menu.xml-->
|
<!--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">
|
<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 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z"/>
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<item android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp">
|
<item android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp">
|
||||||
<shape android:shape="rectangle">
|
<shape android:shape="rectangle">
|
||||||
<stroke android:width="1dp" android:color="#555555"/>
|
<stroke android:width="1dp" android:color="#555555"/>
|
||||||
<solid android:color="@color/transparent"/>
|
<solid android:color="@android:color/transparent"/>
|
||||||
</shape>
|
</shape>
|
||||||
</item>
|
</item>
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|||||||
8
app/src/main/res/drawable/sort_variant.xml
Normal file
8
app/src/main/res/drawable/sort_variant.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/sort_variant.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#000" android:pathData="M3,13H15V11H3M3,6V8H21V6M3,18H9V16H3V18Z" />
|
||||||
|
</vector>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
~ Pupil, Hitomi.la viewer for Android
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
~ Copyright (C) 2019 tom5079
|
~ Copyright (C) 2020 tom5079
|
||||||
~
|
~
|
||||||
~ This program is free software: you can redistribute it and/or modify
|
~ 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
|
~ it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
android:id="@+id/main_appbar_layout"
|
android:id="@+id/main_appbar_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent">
|
app:layout_constraintLeft_toLeftOf="parent">
|
||||||
@@ -41,12 +41,35 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
android:background="@color/transparent"
|
android:background="@android:color/transparent"
|
||||||
app:layout_scrollFlags="scroll|enterAlways"
|
app:layout_scrollFlags="scroll|enterAlways"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:handleDrawable="@drawable/thumb"
|
||||||
|
app:handleHasFixedSize="true"
|
||||||
|
app:handleHeight="72dp"
|
||||||
|
app:handleWidth="24dp"
|
||||||
|
app:disableTrack="true"
|
||||||
|
app:hideHandleAfter="1000"
|
||||||
|
app:trackMarginStart="64dp"
|
||||||
|
app:addLastItemPadding="true"
|
||||||
|
app:popupDrawable="@android:color/transparent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/main_recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="64dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
|
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||||
|
|
||||||
<androidx.core.widget.ContentLoadingProgressBar
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
style="?android:attr/progressBarStyle"
|
style="?android:attr/progressBarStyle"
|
||||||
android:id="@+id/main_progressbar"
|
android:id="@+id/main_progressbar"
|
||||||
@@ -60,30 +83,11 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
android:text="@string/main_no_result"
|
android:text="@string/main_no_result"
|
||||||
|
android:linksClickable="true"
|
||||||
android:visibility="invisible"/>
|
android:visibility="invisible"/>
|
||||||
|
|
||||||
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:handleDrawable="@drawable/thumb"
|
|
||||||
app:handleHasFixedSize="true"
|
|
||||||
app:handleHeight="72dp"
|
|
||||||
app:handleWidth="24dp"
|
|
||||||
app:trackMarginStart="64dp"
|
|
||||||
app:addLastItemPadding="true"
|
|
||||||
app:popupDrawable="@color/transparent">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/main_recyclerview"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingTop="64dp"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
|
||||||
|
|
||||||
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
|
||||||
|
|
||||||
<com.github.clans.fab.FloatingActionMenu
|
<com.github.clans.fab.FloatingActionMenu
|
||||||
android:id="@+id/main_fab"
|
android:id="@+id/main_fab"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -124,21 +128,20 @@
|
|||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<com.arlib.floatingsearchview.FloatingSearchViewDayNight
|
<xyz.quaver.pupil.ui.view.FloatingSearchView
|
||||||
android:id="@+id/main_searchview"
|
android:id="@+id/main_searchview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:floatingSearch_suggestionRightIconColor="@color/material_orange_500"
|
app:searchBarMarginLeft="6dp"
|
||||||
app:floatingSearch_searchBarMarginLeft="8dp"
|
app:searchBarMarginRight="6dp"
|
||||||
app:floatingSearch_searchBarMarginRight="8dp"
|
app:searchBarMarginTop="6dp"
|
||||||
app:floatingSearch_searchBarMarginTop="8dp"
|
app:searchHint="@string/search_hint"
|
||||||
app:floatingSearch_searchHint="@string/search_hint"
|
app:suggestionAnimDuration="250"
|
||||||
app:floatingSearch_suggestionsListAnimDuration="250"
|
app:showSearchKey="true"
|
||||||
app:floatingSearch_showSearchKey="true"
|
app:leftActionMode="showHamburger"
|
||||||
app:floatingSearch_leftActionMode="showHamburger"
|
app:menu="@menu/main"
|
||||||
app:floatingSearch_menu="@menu/main"
|
app:dismissOnOutsideTouch="true"
|
||||||
app:floatingSearch_dismissOnOutsideTouch="true"
|
app:close_search_on_keyboard_dismiss="false"
|
||||||
app:floatingSearch_close_search_on_keyboard_dismiss="true"
|
|
||||||
tools:ignore="NewApi" />
|
tools:ignore="NewApi" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/dark_gray"
|
android:background="@android:color/darker_gray"
|
||||||
tools:context=".ui.ReaderActivity">
|
tools:context=".ui.ReaderActivity">
|
||||||
|
|
||||||
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||||
@@ -33,9 +33,11 @@
|
|||||||
app:handleDrawable="@drawable/thumb"
|
app:handleDrawable="@drawable/thumb"
|
||||||
app:handleHeight="72dp"
|
app:handleHeight="72dp"
|
||||||
app:handleWidth="24dp"
|
app:handleWidth="24dp"
|
||||||
|
app:disableTrack="true"
|
||||||
|
app:hideHandleAfter="1000"
|
||||||
app:handleHasFixedSize="true"
|
app:handleHasFixedSize="true"
|
||||||
app:addLastItemPadding="true"
|
app:addLastItemPadding="true"
|
||||||
app:popupDrawable="@color/transparent">
|
app:popupDrawable="@android:color/transparent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/reader_recyclerview"
|
android:id="@+id/reader_recyclerview"
|
||||||
@@ -45,28 +47,19 @@
|
|||||||
|
|
||||||
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||||
|
|
||||||
<LinearLayout
|
<include layout="@layout/reader_eye_card"
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/eye_card"
|
||||||
|
android:visibility="gone"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_margin="8dp"/>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/reader_download_progressbar"
|
android:id="@+id/reader_download_progressbar"
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="4dp"
|
android:layout_height="4dp"/>
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/reader_progressbar"
|
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:progressTint="@color/material_green_a700"
|
|
||||||
tools:ignore="UnusedAttribute"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.github.clans.fab.FloatingActionMenu
|
<com.github.clans.fab.FloatingActionMenu
|
||||||
android:id="@+id/reader_fab"
|
android:id="@+id/reader_fab"
|
||||||
@@ -80,6 +73,7 @@
|
|||||||
android:id="@+id/reader_fab_download"
|
android:id="@+id/reader_fab_download"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/ic_download"
|
||||||
app:fab_label="@string/reader_fab_download"
|
app:fab_label="@string/reader_fab_download"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
@@ -87,6 +81,7 @@
|
|||||||
android:id="@+id/reader_fab_retry"
|
android:id="@+id/reader_fab_retry"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/refresh"
|
||||||
app:fab_label="@string/reader_fab_retry"
|
app:fab_label="@string/reader_fab_retry"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
@@ -94,6 +89,7 @@
|
|||||||
android:id="@+id/reader_fab_auto"
|
android:id="@+id/reader_fab_auto"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/eye_white"
|
||||||
app:fab_label="@string/reader_fab_auto"
|
app:fab_label="@string/reader_fab_auto"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
@@ -101,6 +97,7 @@
|
|||||||
android:id="@+id/reader_fab_fullscreen"
|
android:id="@+id/reader_fab_fullscreen"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/ic_fullscreen"
|
||||||
app:fab_label="@string/reader_fab_fullscreen"
|
app:fab_label="@string/reader_fab_fullscreen"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:padding="8dp">
|
||||||
|
|
||||||
<ImageView
|
<com.github.piasy.biv.view.BigImageView
|
||||||
android:id="@+id/gallery_cover"
|
android:id="@+id/gallery_cover"
|
||||||
android:layout_width="150dp"
|
android:layout_width="150dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
app:cardCornerRadius="8dp"
|
app:cardCornerRadius="8dp"
|
||||||
android:clipChildren="true">
|
android:clipChildren="true"
|
||||||
|
tools:ignore="RtlHardcoded">
|
||||||
|
|
||||||
<com.daimajia.swipe.SwipeLayout
|
<com.daimajia.swipe.SwipeLayout
|
||||||
android:id="@+id/galleryblock_swipe_layout"
|
android:id="@+id/galleryblock_swipe_layout"
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
android:background="@android:color/holo_blue_dark"
|
android:background="@android:color/holo_blue_dark"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:text="@string/main_download"
|
android:text="@string/main_download"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
@@ -63,34 +64,37 @@
|
|||||||
android:background="@android:color/holo_red_dark"
|
android:background="@android:color/holo_red_dark"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:text="@string/main_delete"
|
android:text="@string/main_delete"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/galleryblock_primary"
|
android:id="@+id/galleryblock_primary"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:foreground="?attr/selectableItemBackground"
|
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:clickable="true"
|
android:clickable="true">
|
||||||
tools:ignore="UnusedAttribute">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/galleryblock_progressbar_layout"
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
|
||||||
android:id="@+id/galleryblock_progressbar"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="4dp"
|
android:layout_height="4dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:id="@+id/galleryblock_progressbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="-4dp"
|
||||||
|
android:layout_marginTop="-4dp"
|
||||||
|
android:progress="50"
|
||||||
|
android:layout_gravity="center_vertical"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/galleryblock_progress_complete"
|
android:id="@+id/galleryblock_progress_complete"
|
||||||
@@ -101,15 +105,19 @@
|
|||||||
android:contentDescription="@string/reader_imageview_description"
|
android:contentDescription="@string/reader_imageview_description"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<ImageView
|
</FrameLayout>
|
||||||
|
|
||||||
|
<com.github.piasy.biv.view.BigImageView
|
||||||
android:id="@+id/galleryblock_thumbnail"
|
android:id="@+id/galleryblock_thumbnail"
|
||||||
android:layout_width="150dp"
|
android:layout_width="150dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="0dp"
|
||||||
android:contentDescription="@string/galleryblock_thumbnail_description"
|
android:contentDescription="@string/galleryblock_thumbnail_description"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
|
app:layout_constraintHeight_default="spread"
|
||||||
|
app:layout_constraintHeight_min="200dp"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar"
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar_layout"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
app:layout_constraintBottom_toTopOf="@id/barrier"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextAppearance.AppCompat.Headline"
|
style="@style/TextAppearance.AppCompat.Headline"
|
||||||
@@ -117,18 +125,16 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar_layout"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextAppearance.AppCompat.Medium"
|
style="@style/TextAppearance.AppCompat.Medium"
|
||||||
android:id="@+id/galleryblock_artist"
|
android:id="@+id/galleryblock_artist"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
@@ -138,101 +144,91 @@
|
|||||||
android:id="@+id/galleryblock_series"
|
android:id="@+id/galleryblock_series"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
|
||||||
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||||
app:layout_constraintEnd_toEndOf="parent"/>
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_type"
|
android:id="@+id/galleryblock_type"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
|
||||||
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_language"
|
android:id="@+id/galleryblock_language"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
|
||||||
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" />
|
||||||
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
|
|
||||||
|
|
||||||
<View
|
<xyz.quaver.pupil.ui.view.TagChipGroup
|
||||||
android:id="@+id/galleryblock_padding"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/galleryblock_tag_group"/>
|
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
|
||||||
android:id="@+id/galleryblock_tag_group"
|
android:id="@+id/galleryblock_tag_group"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
app:chipSpacing="4dp"
|
app:chipSpacing="4dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="galleryblock_thumbnail, galleryblock_tag_group"/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
android:id="@+id/divider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:layout_margin="8dp"
|
android:background="?android:attr/listDivider"
|
||||||
android:background="@android:color/darker_gray"/>
|
app:layout_constraintTop_toBottomOf="@id/barrier"
|
||||||
|
android:layout_margin="8dp"/>
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingLeft="8dp"
|
|
||||||
android:paddingRight="8dp"
|
|
||||||
android:paddingBottom="8dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_id"
|
android:id="@+id/galleryblock_id"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
<View
|
android:layout_marginTop="8dp"
|
||||||
android:layout_width="0dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:layout_height="1dp"
|
app:layout_constraintTop_toBottomOf="@id/divider"
|
||||||
android:layout_weight="1"/>
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_pagecount"
|
android:id="@+id/galleryblock_pagecount"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
<View
|
android:layout_marginBottom="8dp"
|
||||||
android:layout_width="0dp"
|
app:layout_constraintTop_toBottomOf="@id/divider"
|
||||||
android:layout_height="1dp"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_weight="1"/>
|
app:layout_constraintLeft_toRightOf="parent"
|
||||||
|
app:layout_constraintRight_toLeftOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/galleryblock_favorite"
|
android:id="@+id/galleryblock_favorite"
|
||||||
android:contentDescription="@string/app_name"
|
android:contentDescription="@string/app_name"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
app:srcCompat="@drawable/ic_star_empty"/>
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:srcCompat="@drawable/ic_star_empty"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/divider"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.daimajia.swipe.SwipeLayout>
|
</com.daimajia.swipe.SwipeLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:srcCompat="@drawable/menu"
|
app:srcCompat="@drawable/menu"
|
||||||
app:tint="?attr/colorControlNormal"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
|||||||
@@ -21,22 +21,15 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintHeight_max="2000dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
android:paddingBottom="8dp"
|
|
||||||
android:background="@drawable/reader_item_boundary">
|
android:background="@drawable/reader_item_boundary">
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.constraintlayout.widget.Guideline
|
||||||
android:id="@+id/progress_layout"
|
android:id="@+id/guideline_center_vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
android:orientation="horizontal"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintGuide_percent="0.5"/>
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/reader_item_progressbar"
|
android:id="@+id/reader_item_progressbar"
|
||||||
@@ -46,22 +39,33 @@
|
|||||||
android:indeterminate="false"
|
android:indeterminate="false"
|
||||||
android:progress="0"
|
android:progress="0"
|
||||||
android:max="100"
|
android:max="100"
|
||||||
android:visibility="visible"/>
|
android:visibility="visible"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/guideline_center_vertical"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/reader_index"
|
android:id="@+id/reader_index"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/guideline_center_vertical"
|
||||||
|
app:layout_constraintLeft_toLeftOf="@id/reader_item_progressbar"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/reader_item_progressbar"
|
||||||
style="@style/TextAppearance.AppCompat.Caption"/>
|
style="@style/TextAppearance.AppCompat.Caption"/>
|
||||||
|
|
||||||
</LinearLayout>
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/progress_group"
|
||||||
<com.github.chrisbanes.photoview.PhotoView
|
android:layout_width="wrap_content"
|
||||||
android:id="@+id/image"
|
|
||||||
android:adjustViewBounds="true"
|
|
||||||
android:scaleType="fitCenter"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:constraint_referenced_ids="reader_item_progressbar, reader_index"/>
|
||||||
|
|
||||||
|
<com.github.piasy.biv.view.BigImageView
|
||||||
|
android:id="@+id/image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:initScaleType="fitCenter"
|
||||||
|
app:optimizeDisplay="true"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
|||||||
67
app/src/main/res/layout/reader_eye_card.xml
Normal file
67
app/src/main/res/layout/reader_eye_card.xml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2020 tom5079
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="16dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/left_eye"
|
||||||
|
android:layout_width="8dp"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
app:srcCompat="@drawable/eye_off"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/right_eye"
|
||||||
|
android:layout_width="8dp"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
app:srcCompat="@drawable/eye_off"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/left_eye"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:layout_margin="4dp"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/dot"
|
||||||
|
android:layout_width="4dp"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:visibility="invisible"
|
||||||
|
app:srcCompat="@drawable/dot"
|
||||||
|
app:layout_constraintLeft_toLeftOf="@id/left_eye"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/right_eye"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
@@ -20,10 +20,10 @@
|
|||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item android:id="@+id/main_menu_thin"
|
<item android:id="@+id/sort"
|
||||||
android:title="@string/main_menu_thin"/>
|
android:title="@string/main_menu_sort"
|
||||||
|
android:icon="@drawable/sort_variant"
|
||||||
<item android:title="@string/main_menu_sort">
|
app:showAsAction="ifRoom">
|
||||||
<menu>
|
<menu>
|
||||||
<group android:checkableBehavior="single">
|
<group android:checkableBehavior="single">
|
||||||
<item android:id="@+id/main_menu_sort_newest"
|
<item android:id="@+id/main_menu_sort_newest"
|
||||||
@@ -41,4 +41,9 @@
|
|||||||
android:title="@string/main_settings"
|
android:title="@string/main_settings"
|
||||||
app:showAsAction="always"/>
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
|
<item android:id="@+id/main_menu_thin"
|
||||||
|
android:title="@string/main_menu_thin"
|
||||||
|
app:showAsAction="never"
|
||||||
|
android:checkable="true"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
@@ -127,12 +127,10 @@
|
|||||||
<string name="settings_lock_fingerprint_prompt">Pupil指紋ロック™</string>
|
<string name="settings_lock_fingerprint_prompt">Pupil指紋ロック™</string>
|
||||||
<string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</string>
|
<string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</string>
|
||||||
<string name="default_query_dialog_filter_loli">登場人物を全て18歳以上にする</string>
|
<string name="default_query_dialog_filter_loli">登場人物を全て18歳以上にする</string>
|
||||||
<string name="settings_cache_disable">キャッシュを使用しない</string>
|
|
||||||
<string name="settings_download_when_cache_disable_warning">キャッシュを使用しないため、ダウンロードできません</string>
|
|
||||||
<string name="settings_user_id">ユーザーID</string>
|
<string name="settings_user_id">ユーザーID</string>
|
||||||
<string name="settings_user_id_toast">ユーザーIDをクリップボードにコピーしました</string>
|
<string name="settings_user_id_toast">ユーザーIDをクリップボードにコピーしました</string>
|
||||||
<string name="reader_fab_retry">リトライ</string>
|
<string name="reader_fab_retry">リトライ</string>
|
||||||
<string name="reader_fab_auto">自動スクロール</string>
|
<string name="reader_fab_auto">まばたき検知スクロール</string>
|
||||||
<string name="search_all">全てのギャラリーを対象に検索</string>
|
<string name="search_all">全てのギャラリーを対象に検索</string>
|
||||||
<string name="settings_rtl">綴じ方向を左にする</string>
|
<string name="settings_rtl">綴じ方向を左にする</string>
|
||||||
<string name="settings_manage_favorites">ブックマーク管理</string>
|
<string name="settings_manage_favorites">ブックマーク管理</string>
|
||||||
@@ -148,4 +146,10 @@
|
|||||||
<string name="settings_oss">オープンソースライセンス</string>
|
<string name="settings_oss">オープンソースライセンス</string>
|
||||||
<string name="search_show_tags">お気に入りのタグを見る</string>
|
<string name="search_show_tags">お気に入りのタグを見る</string>
|
||||||
<string name="search_show_histories">履歴を見る</string>
|
<string name="search_show_histories">履歴を見る</string>
|
||||||
|
<string name="reader_fab_auto_cancel">まばたき検知を中止</string>
|
||||||
|
<string name="camera_denied">カメラ権限が拒否されているため、まばたき検知使用できません</string>
|
||||||
|
<string name="no_camera">この機器には前面カメラが装着されていません</string>
|
||||||
|
<string name="error">エラー</string>
|
||||||
|
<string name="settings_cache_limit">キャッシュサイズ制限</string>
|
||||||
|
<string name="settings_cache_unlimited">制限なし</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
|
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
|
||||||
<string name="settings_search_title">검색 설정</string>
|
<string name="settings_search_title">검색 설정</string>
|
||||||
<string name="settings_title">설정</string>
|
<string name="settings_title">설정</string>
|
||||||
<string name="update_notification_description">apk 다운로드중…</string>
|
<string name="update_notification_description">업데이트 다운로드중…</string>
|
||||||
<string name="update_title">업데이트가 있습니다!</string>
|
<string name="update_title">업데이트가 있습니다!</string>
|
||||||
<string name="warning">경고</string>
|
<string name="warning">경고</string>
|
||||||
<string name="main_no_result">결과 없음</string>
|
<string name="main_no_result">결과 없음\n해결법</string>
|
||||||
<string name="settings_miscellaneous_title">기타</string>
|
<string name="settings_miscellaneous_title">기타</string>
|
||||||
<string name="settings_clear_history">기록 삭제</string>
|
<string name="settings_clear_history">기록 삭제</string>
|
||||||
<string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string>
|
<string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string>
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
<string name="proxy_dialog_error">잘못된 값</string>
|
<string name="proxy_dialog_error">잘못된 값</string>
|
||||||
<string name="proxy_dialog_addr_hint">서버 주소</string>
|
<string name="proxy_dialog_addr_hint">서버 주소</string>
|
||||||
<string name="proxy_dialog_server">서버</string>
|
<string name="proxy_dialog_server">서버</string>
|
||||||
<string name="main_menu_thin">간단히 보기 모드</string>
|
<string name="main_menu_thin">간단히 보기</string>
|
||||||
<string name="main_fab_cancel">다운로드 모두 취소</string>
|
<string name="main_fab_cancel">다운로드 모두 취소</string>
|
||||||
<string name="channel_update">업데이트</string>
|
<string name="channel_update">업데이트</string>
|
||||||
<string name="channel_update_description">업데이트 진행상황 표시</string>
|
<string name="channel_update_description">업데이트 진행상황 표시</string>
|
||||||
@@ -127,12 +127,10 @@
|
|||||||
<string name="settings_lock_fingerprint_prompt">Pupil 지문 인식™</string>
|
<string name="settings_lock_fingerprint_prompt">Pupil 지문 인식™</string>
|
||||||
<string name="settings_lock_fingerprint_prompt_subtitle">힘세고 강한 지문 인식</string>
|
<string name="settings_lock_fingerprint_prompt_subtitle">힘세고 강한 지문 인식</string>
|
||||||
<string name="default_query_dialog_filter_loli">판사님 저는 페도가 아닙니다</string>
|
<string name="default_query_dialog_filter_loli">판사님 저는 페도가 아닙니다</string>
|
||||||
<string name="settings_cache_disable">캐시 비활성화</string>
|
|
||||||
<string name="settings_download_when_cache_disable_warning">캐시를 활성화 해야 다운로드를 진행할 수 있습니다</string>
|
|
||||||
<string name="settings_user_id">유저 ID</string>
|
<string name="settings_user_id">유저 ID</string>
|
||||||
<string name="settings_user_id_toast">유저 ID를 클립보드에 복사했습니다</string>
|
<string name="settings_user_id_toast">유저 ID를 클립보드에 복사했습니다</string>
|
||||||
<string name="reader_fab_retry">재시도</string>
|
<string name="reader_fab_retry">재시도</string>
|
||||||
<string name="reader_fab_auto">자동 스크롤</string>
|
<string name="reader_fab_auto">눈 깜빡임 감지 스크롤</string>
|
||||||
<string name="search_all">모든 갤러리 검색</string>
|
<string name="search_all">모든 갤러리 검색</string>
|
||||||
<string name="settings_rtl">좌측으로 페이지 넘기기</string>
|
<string name="settings_rtl">좌측으로 페이지 넘기기</string>
|
||||||
<string name="settings_manage_favorites">즐겨찾기 관리</string>
|
<string name="settings_manage_favorites">즐겨찾기 관리</string>
|
||||||
@@ -148,4 +146,10 @@
|
|||||||
<string name="settings_oss">오픈 소스 라이선스</string>
|
<string name="settings_oss">오픈 소스 라이선스</string>
|
||||||
<string name="search_show_histories">검색 기록 보기</string>
|
<string name="search_show_histories">검색 기록 보기</string>
|
||||||
<string name="search_show_tags">즐겨찾기 태그 보기</string>
|
<string name="search_show_tags">즐겨찾기 태그 보기</string>
|
||||||
|
<string name="reader_fab_auto_cancel">눈 깜빡임 감지 중지</string>
|
||||||
|
<string name="camera_denied">카메라 권한이 거부되었기 때문에 눈 깜빡임 감지가 불가능합니다</string>
|
||||||
|
<string name="no_camera">이 장치에는 전면 카메라가 없습니다</string>
|
||||||
|
<string name="error">오류</string>
|
||||||
|
<string name="settings_cache_limit">캐시 크기 제한</string>
|
||||||
|
<string name="settings_cache_unlimited">무제한</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string-array name="settings_galleries_per_page">
|
<string-array name="settings_galleries_per_page">
|
||||||
|
<item>5</item>
|
||||||
<item>10</item>
|
<item>10</item>
|
||||||
<item>25</item>
|
<item>25</item>
|
||||||
<item>50</item>
|
<item>50</item>
|
||||||
@@ -58,4 +59,24 @@
|
|||||||
<item>SOCKS</item>
|
<item>SOCKS</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="cache_size">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>4</item>
|
||||||
|
<item>8</item>
|
||||||
|
<item>16</item>
|
||||||
|
<item>32</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="cache_size_text">
|
||||||
|
<item>@string/settings_cache_unlimited</item>
|
||||||
|
<item>1G</item>
|
||||||
|
<item>2G</item>
|
||||||
|
<item>4G</item>
|
||||||
|
<item>8G</item>
|
||||||
|
<item>16G</item>
|
||||||
|
<item>32G</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
24
app/src/main/res/values/attr.xml
Normal file
24
app/src/main/res/values/attr.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="TagChipGroup">
|
||||||
|
<attr name="maxTag" format="integer"/>
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
|
||||||
<dimen name="galleryblock_thumbnail_thin">100dp</dimen>
|
<dimen name="galleryblock_thumbnail_thin">100dp</dimen>
|
||||||
|
|
||||||
<dimen name="reader_max_height">2000dp</dimen>
|
<dimen name="reader_max_height" tools:ignore="PxUsage">2000px</dimen>
|
||||||
|
|
||||||
<dimen name="thumb_width">24dp</dimen>
|
<dimen name="thumb_width">24dp</dimen>
|
||||||
<dimen name="thumb_height">72dp</dimen>
|
<dimen name="thumb_height">72dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="thumbnail_page_height">300dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,13 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<item name="item_click_support" type="id" />
|
<item name="item_click_support" type="id" />
|
||||||
<item name="request_settings" type="id" />
|
|
||||||
<item name="request_lock" type="id" />
|
|
||||||
<item name="request_restore" type="id" />
|
|
||||||
|
|
||||||
<item name="request_download_folder" type="id" />
|
|
||||||
<item name="request_download_folder_old" type="id" />
|
|
||||||
<item name="request_write_permission_and_saf" type="id" />
|
|
||||||
|
|
||||||
<item name="notification_id_update" type="id" />
|
<item name="notification_id_update" type="id" />
|
||||||
<item name="notification_id_import" type="id" />
|
<item name="notification_id_import" type="id" />
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string>
|
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string>
|
||||||
<string name="discord" translatable="false">https://discord.gg/Stj4b5v</string>
|
<string name="discord" translatable="false">https://discord.gg/Stj4b5v</string>
|
||||||
|
|
||||||
|
<!-- Korean only -->
|
||||||
|
<string name="https_text" translatable="false">해결법</string>
|
||||||
|
<string name="https" translatable="false">https://bit.ly/34dUBwy</string>
|
||||||
|
|
||||||
<string name="backup_url" translatable="false">http://ix.io/</string>
|
<string name="backup_url" translatable="false">http://ix.io/</string>
|
||||||
|
|
||||||
<string name="main_settings" translatable="false">Settings</string>
|
<string name="main_settings" translatable="false">Settings</string>
|
||||||
@@ -17,9 +21,12 @@
|
|||||||
<string name="reader_imageview_description" translatable="false">Content ImageView</string>
|
<string name="reader_imageview_description" translatable="false">Content ImageView</string>
|
||||||
<string name="page_indicator_placeholder" translatable="false">-/-</string>
|
<string name="page_indicator_placeholder" translatable="false">-/-</string>
|
||||||
|
|
||||||
|
<string name="galleryblock_artist_with_group" translatable="false">%s (%s)</string>
|
||||||
|
|
||||||
<!-- Translate needed down here -->
|
<!-- Translate needed down here -->
|
||||||
|
|
||||||
<string name="warning">Warning</string>
|
<string name="warning">Warning</string>
|
||||||
|
<string name="error">Error</string>
|
||||||
|
|
||||||
<string name="ignore_update">Ignore</string>
|
<string name="ignore_update">Ignore</string>
|
||||||
|
|
||||||
@@ -49,7 +56,7 @@
|
|||||||
<string name="main_drawer_group_contact_email">Email me!</string>
|
<string name="main_drawer_group_contact_email">Email me!</string>
|
||||||
<string name="main_drawer_grouop_contact_discord">Discord</string>
|
<string name="main_drawer_grouop_contact_discord">Discord</string>
|
||||||
|
|
||||||
<string name="main_menu_thin">Toggle Thin Mode</string>
|
<string name="main_menu_thin">Thin Mode</string>
|
||||||
|
|
||||||
<string name="main_menu_sort">Sort</string>
|
<string name="main_menu_sort">Sort</string>
|
||||||
<string name="main_menu_sort_newest">Newest</string>
|
<string name="main_menu_sort_newest">Newest</string>
|
||||||
@@ -99,12 +106,16 @@
|
|||||||
<string name="reader_go_to_page">Go to page</string>
|
<string name="reader_go_to_page">Go to page</string>
|
||||||
<string name="reader_fab_fullscreen">Fullscreen</string>>
|
<string name="reader_fab_fullscreen">Fullscreen</string>>
|
||||||
<string name="reader_fab_retry">Retry</string>
|
<string name="reader_fab_retry">Retry</string>
|
||||||
<string name="reader_fab_auto">Automatic scroll</string>
|
<string name="reader_fab_auto">Scroll with eye blink</string>
|
||||||
|
<string name="reader_fab_auto_cancel">Stop scroll with eye blink</string>
|
||||||
<string name="reader_fab_download">Background download</string>
|
<string name="reader_fab_download">Background download</string>
|
||||||
<string name="reader_fab_download_cancel">Cancel background download</string>
|
<string name="reader_fab_download_cancel">Cancel background download</string>
|
||||||
<string name="reader_notification_text">Downloading…</string>
|
<string name="reader_notification_text">Downloading…</string>
|
||||||
<string name="reader_notification_complete">Download complete</string>
|
<string name="reader_notification_complete">Download complete</string>
|
||||||
|
|
||||||
|
<string name="camera_denied">Eye blink detection cannot be used without a permission</string>
|
||||||
|
<string name="no_camera">There is no front facing camera in this device</string>
|
||||||
|
|
||||||
<!-- DOWNLOADER -->
|
<!-- DOWNLOADER -->
|
||||||
<string name="downloader_running">Downloader running…</string>
|
<string name="downloader_running">Downloader running…</string>
|
||||||
|
|
||||||
@@ -150,8 +161,9 @@
|
|||||||
<string name="settings_download_folder_available">%s available</string>
|
<string name="settings_download_folder_available">%s available</string>
|
||||||
<string name="settings_download_folder_custom">Custom Location</string>
|
<string name="settings_download_folder_custom">Custom Location</string>
|
||||||
<string name="settings_download_folder_not_writable">This folder is not writable. Please select another folder.</string>
|
<string name="settings_download_folder_not_writable">This folder is not writable. Please select another folder.</string>
|
||||||
<string name="settings_cache_disable">Disable Cache</string>
|
<string name="settings_cache_limit">Cache Limit</string>
|
||||||
<string name="settings_download_when_cache_disable_warning">Download is disabled when the cache is disabled</string>
|
<string name="settings_cache_unlimited">Unlimited</string>
|
||||||
|
<string name="settings_nomedia_title">Hide image from gallery</string>
|
||||||
<string name="settings_low_quality">Low quality images</string>
|
<string name="settings_low_quality">Low quality images</string>
|
||||||
<string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string>
|
<string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string>
|
||||||
|
|
||||||
@@ -170,7 +182,6 @@
|
|||||||
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>
|
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>
|
||||||
<string name="settings_dark_mode_title">Dark mode</string>
|
<string name="settings_dark_mode_title">Dark mode</string>
|
||||||
<string name="settings_dark_mode_summary">Protect yourself against light attacks!</string>
|
<string name="settings_dark_mode_summary">Protect yourself against light attacks!</string>
|
||||||
<string name="settings_nomedia_title">Hide image from gallery</string>
|
|
||||||
<string name="settings_import_old_galleries">Import old galleries</string>
|
<string name="settings_import_old_galleries">Import old galleries</string>
|
||||||
<string name="settings_user_id">User ID</string>
|
<string name="settings_user_id">User ID</string>
|
||||||
<string name="settings_user_id_toast">User ID is copied to clipboard</string>
|
<string name="settings_user_id_toast">User ID is copied to clipboard</string>
|
||||||
|
|||||||
@@ -44,9 +44,13 @@
|
|||||||
app:key="download_folder"
|
app:key="download_folder"
|
||||||
app:title="@string/settings_download_folder"/>
|
app:title="@string/settings_download_folder"/>
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<ListPreference
|
||||||
app:key="cache_disable"
|
app:key="cache_limit"
|
||||||
app:title="@string/settings_cache_disable"/>
|
app:title="@string/settings_cache_limit"
|
||||||
|
app:entries="@array/cache_size_text"
|
||||||
|
app:entryValues="@array/cache_size"
|
||||||
|
app:defaultValue="8"
|
||||||
|
app:useSimpleSummaryProvider="true"/>
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:key="nomedia"
|
app:key="nomedia"
|
||||||
|
|||||||
13
build.gradle
13
build.gradle
@@ -6,25 +6,26 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
classpath "com.android.tools.build:gradle:4.0.1"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
classpath 'com.google.gms:google-services:4.3.3'
|
classpath "com.google.gms:google-services:4.3.3"
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.1'
|
classpath "com.google.firebase:firebase-crashlytics-gradle:2.3.0"
|
||||||
classpath 'com.google.firebase:perf-plugin:1.3.1'
|
classpath "com.google.firebase:perf-plugin:1.3.1"
|
||||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
|
classpath "com.google.android.gms:oss-licenses-plugin:0.10.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
|
maven { url "http://dl.bintray.com/piasy/maven" }
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url 'https://guardian.github.com/maven/repo-releases' }
|
maven { url "https://guardian.github.com/maven/repo-releases" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5555
dependencies.txt
5555
dependencies.txt
File diff suppressed because it is too large
Load Diff
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,4 +1,4 @@
|
|||||||
#Thu Jun 18 15:48:09 KST 2020
|
#Thu Oct 01 20:54:37 KST 2020
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
51
gradlew
vendored
51
gradlew
vendored
@@ -1,5 +1,21 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
##
|
||||||
## Gradle start up script for UN*X
|
## Gradle start up script for UN*X
|
||||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
@@ -109,8 +125,8 @@ if $darwin; then
|
|||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
@@ -138,19 +154,19 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -159,14 +175,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
|||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|||||||
Reference in New Issue
Block a user