Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ddb19530b | ||
|
|
431e56a9f1 | ||
|
|
71093aac4c | ||
|
|
47c9e8127e | ||
|
|
24b801b346 | ||
|
|
70608c3ed9 | ||
|
|
f185196e85 | ||
|
|
a8766a8bbe | ||
|
|
27a8c93cfe | ||
|
|
a3cd29fda9 | ||
|
|
adda8ab640 | ||
|
|
1538ea5fc8 | ||
|
|
2367a97a54 | ||
|
|
090ec0e4af | ||
|
|
de7f552e5c | ||
|
|
d763f5dca0 | ||
|
|
9f41116241 | ||
|
|
57faada201 | ||
|
|
1edb95f0c5 | ||
|
|
9f363d8900 | ||
|
|
0bf2f1b6e1 | ||
|
|
68c7a38390 | ||
|
|
841c8a7a15 | ||
|
|
6c9688183b | ||
|
|
ccd84c91f6 | ||
|
|
318d6f9b52 | ||
|
|
8f5d612ee0 | ||
|
|
56b2a05596 | ||
|
|
4db0022d6a | ||
|
|
67f37d3188 | ||
|
|
ed81cc7207 | ||
|
|
065845f1be | ||
|
|
902f705e89 | ||
|
|
ec2e0ef773 | ||
|
|
d28c5741d0 | ||
|
|
e6e3f9e8f8 | ||
|
|
90e1dc59bd | ||
|
|
0b1c9b097c | ||
|
|
2b553d1116 | ||
|
|
567eec8bc5 | ||
|
|
293ca5b31d | ||
|
|
0d0f2bd827 | ||
|
|
5bc4610061 | ||
|
|
e6b7c107f2 | ||
|
|
51a9bf2570 | ||
|
|
8385f6f390 | ||
|
|
772e9daf57 | ||
|
|
8adc4405c5 | ||
|
|
349da7aa81 | ||
|
|
01a01d481d | ||
|
|
2f8445fb83 | ||
|
|
b04a5fc150 | ||
|
|
bbe29941df | ||
|
|
2720e445ea | ||
|
|
49ba579a59 | ||
|
|
3198c6cbfd | ||
|
|
b3feee6d9d | ||
|
|
f0f53e6bce | ||
|
|
24486d13f2 | ||
|
|
20bc9461de | ||
|
|
c8e94cc295 | ||
|
|
b2bfb0c237 | ||
|
|
0a003da724 | ||
|
|
b4f2a33016 | ||
|
|
ee7ede2885 | ||
|
|
6abc404eb7 | ||
|
|
61afe01e36 | ||
|
|
c3e60f9988 | ||
|
|
593197cd7e | ||
|
|
ee1592b478 | ||
|
|
dfe435c4f3 | ||
|
|
69e85f8b90 |
3
.idea/codeStyles/Project.xml
generated
@@ -1,9 +1,6 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<option name="RIGHT_MARGIN" value="120" />
|
<option name="RIGHT_MARGIN" value="120" />
|
||||||
<AndroidXmlCodeStyleSettings>
|
|
||||||
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
|
|
||||||
</AndroidXmlCodeStyleSettings>
|
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
</JetCodeStyleSettings>
|
</JetCodeStyleSettings>
|
||||||
|
|||||||
2
.idea/gradle.xml
generated
@@ -1,8 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="PLATFORM" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
*Pupil, Hitomi.la viewer for Android*
|
*Pupil, Hitomi.la viewer for Android*
|
||||||
|
|
||||||
[](https://discord.gg/Stj4b5v)
|
[](https://discord.gg/Stj4b5v)
|
||||||
I can speak English, Japanese and Korean. If you have any questions, head over to my discord server or DM me!
|
|
||||||
|
|
||||||
# Screenshot
|
# Screenshot
|
||||||

|

|
||||||
|
|||||||
@@ -7,7 +7,7 @@ apply plugin: 'kotlinx-serialization'
|
|||||||
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: 'io.fabric'
|
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")
|
||||||
@@ -19,8 +19,8 @@ android {
|
|||||||
applicationId "xyz.quaver.pupil"
|
applicationId "xyz.quaver.pupil"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 44
|
versionCode 56
|
||||||
versionName "4.8"
|
versionName "4.18.3"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
@@ -35,9 +35,6 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
|
||||||
shrinkResources true
|
|
||||||
|
|
||||||
buildConfigField('Boolean', 'CENSOR', 'false')
|
buildConfigField('Boolean', 'CENSOR', 'false')
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
@@ -49,42 +46,41 @@ android {
|
|||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
buildToolsVersion = '29.0.2'
|
buildToolsVersion = '29.0.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
def markwonVersion = "3.0.1"
|
def markwonVersion = '3.1.0'
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
implementation "androidx.biometric:biometric:1.0.1"
|
implementation "androidx.biometric:biometric:1.0.1"
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
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.2.0-alpha05'
|
implementation 'com.google.android.material:material:1.3.0-alpha01'
|
||||||
implementation 'com.google.firebase:firebase-core:17.2.2'
|
implementation 'com.google.firebase:firebase-core:17.4.4'
|
||||||
implementation 'com.google.firebase:firebase-perf:19.0.5'
|
implementation 'com.google.firebase:firebase-analytics:17.4.4'
|
||||||
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
|
implementation 'com.google.firebase:firebase-crashlytics:17.1.1'
|
||||||
|
implementation 'com.google.firebase:firebase-perf:19.0.7'
|
||||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
||||||
implementation 'com.github.clans:fab:1.6.4'
|
implementation 'com.github.clans:fab:1.6.4'
|
||||||
|
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
||||||
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
||||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
|
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'net.rdrei.android.dirchooser:library:3.2@aar'
|
implementation 'net.rdrei.android.dirchooser:library:3.2@aar'
|
||||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||||
|
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
||||||
implementation "ru.noties.markwon:core:${markwonVersion}"
|
implementation "ru.noties.markwon:core:${markwonVersion}"
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
|
|||||||
BIN
app/libs/pinlockview-release.aar
Normal file
12
app/proguard-rules.pro
vendored
@@ -23,8 +23,16 @@
|
|||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
||||||
|
<init>(...);
|
||||||
|
}
|
||||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||||
**[] $VALUES;
|
**[] $VALUES;
|
||||||
public *;
|
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
|
||||||
@@ -1 +1,20 @@
|
|||||||
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":44,"versionName":"4.8","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
|
{
|
||||||
|
"version": 1,
|
||||||
|
"artifactType": {
|
||||||
|
"type": "APK",
|
||||||
|
"kind": "Directory"
|
||||||
|
},
|
||||||
|
"applicationId": "xyz.quaver.pupil",
|
||||||
|
"variantName": "release",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "SINGLE",
|
||||||
|
"filters": [],
|
||||||
|
"properties": [],
|
||||||
|
"versionCode": 56,
|
||||||
|
"versionName": "56",
|
||||||
|
"enabled": true,
|
||||||
|
"outputFile": "app-release.apk"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import androidx.test.rule.ActivityTestRule
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||||
import xyz.quaver.hiyobi.cookie
|
import xyz.quaver.hiyobi.cookie
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.getReader
|
import xyz.quaver.hiyobi.getReader
|
||||||
@@ -62,6 +63,13 @@ class ExampleInstrumentedTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_nozomi() {
|
||||||
|
val nozomi = getGalleryIDsFromNozomi(null, "index", "all")
|
||||||
|
|
||||||
|
Log.i("PUPILD", nozomi.size.toString())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_doSearch() {
|
fun test_doSearch() {
|
||||||
val reader = getReader( 1426382)
|
val reader = getReader( 1426382)
|
||||||
@@ -91,7 +99,7 @@ class ExampleInstrumentedTest {
|
|||||||
while(worker.progress.indexOfKey(galleryID) < 0 || worker.progress[galleryID] != null) {
|
while(worker.progress.indexOfKey(galleryID) < 0 || worker.progress[galleryID] != null) {
|
||||||
Log.i("PUPILD", worker.progress[galleryID]?.joinToString(" ") ?: "null")
|
Log.i("PUPILD", worker.progress[galleryID]?.joinToString(" ") ?: "null")
|
||||||
|
|
||||||
if (worker.progress[galleryID]?.all { !it.isFinite() } == true)
|
if (worker.progress[galleryID]?.all { it.isInfinite() } == true)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,9 +113,9 @@ class ExampleInstrumentedTest {
|
|||||||
val galleryID = 1561552
|
val galleryID = 1561552
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
Log.i("PUPILD", Cache(context).getReader(galleryID)?.title ?: "null")
|
Log.i("PUPILD", Cache(context).getReader(galleryID)?.galleryInfo?.title ?: "null")
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.title ?: "null")
|
Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.title ?: "null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
<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"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".Pupil"
|
android:name=".Pupil"
|
||||||
@@ -19,7 +21,8 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
tools:replace="android:theme"
|
tools:replace="android:theme"
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true"
|
||||||
|
tools:ignore="UnusedAttribute">
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
@@ -33,6 +36,12 @@
|
|||||||
|
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
|
<receiver android:name=".BroadcastReciever" android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<activity android:name=".ui.LockActivity" />
|
<activity android:name=".ui.LockActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ReaderActivity"
|
android:name=".ui.ReaderActivity"
|
||||||
@@ -44,6 +53,61 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/galleries"
|
||||||
|
android:scheme="http" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/manga"
|
||||||
|
android:scheme="http" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/doujinshi"
|
||||||
|
android:scheme="http" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/cg"
|
||||||
|
android:scheme="http" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/reader"
|
||||||
|
android:scheme="http" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="hitomi.la"
|
android:host="hitomi.la"
|
||||||
android:pathPrefix="/galleries"
|
android:pathPrefix="/galleries"
|
||||||
@@ -55,6 +119,61 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/manga"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/doujinshi"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/cg"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hitomi.la"
|
||||||
|
android:pathPrefix="/reader"
|
||||||
|
android:scheme="https" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data
|
||||||
|
android:host="hiyobi.me"
|
||||||
|
android:scheme="http"
|
||||||
|
android:pathPrefix="/reader" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="hiyobi.me"
|
android:host="hiyobi.me"
|
||||||
android:pathPrefix="/reader"
|
android:pathPrefix="/reader"
|
||||||
@@ -69,17 +188,6 @@
|
|||||||
<data
|
<data
|
||||||
android:host="e-hentai.org"
|
android:host="e-hentai.org"
|
||||||
android:pathPrefix="/g"
|
android:pathPrefix="/g"
|
||||||
android:scheme="https" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="hitomi.la"
|
|
||||||
android:pathPrefix="/galleries"
|
|
||||||
android:scheme="http" />
|
android:scheme="http" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -88,21 +196,10 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
|
||||||
android:host="hiyobi.me"
|
|
||||||
android:scheme="http"
|
|
||||||
android:pathPrefix="/reader" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data
|
<data
|
||||||
android:host="e-hentai.org"
|
android:host="e-hentai.org"
|
||||||
android:pathPrefix="/g"
|
android:pathPrefix="/g"
|
||||||
android:scheme="http" />
|
android:scheme="https" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.arlib.floatingsearchview
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
class FloatingSearchViewDayNight @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null)
|
||||||
|
: FloatingSearchView(context, attrs) {
|
||||||
|
|
||||||
|
// hack to remove color attributes which should not be reused
|
||||||
|
override fun onSaveInstanceState(): Parcelable? {
|
||||||
|
super.onSaveInstanceState()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
103
app/src/main/java/xyz/quaver/pupil/BroadcastReciever.kt
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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.app.DownloadManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import xyz.quaver.pupil.util.NOTIFICATION_ID_UPDATE
|
||||||
|
import xyz.quaver.pupil.util.cancelImport
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class BroadcastReciever : BroadcastReceiver() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ACTION_CANCEL_IMPORT = "ACTION_CANCEL_IMPORT"
|
||||||
|
|
||||||
|
const val EXTRA_IMPORT_NOTIFICATION_ID = "EXTRA_IMPORT_NOTIFICATION_ID"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
context ?: return
|
||||||
|
|
||||||
|
when (intent?.action) {
|
||||||
|
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
|
||||||
|
|
||||||
|
// Validate download
|
||||||
|
|
||||||
|
val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
val downloadID = preference.getLong("update_download_id", -1)
|
||||||
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
|
||||||
|
if (intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) != downloadID)
|
||||||
|
return
|
||||||
|
|
||||||
|
// Get target uri
|
||||||
|
|
||||||
|
val query = DownloadManager.Query()
|
||||||
|
.setFilterById(downloadID)
|
||||||
|
|
||||||
|
val uri = downloadManager.query(query).use { cursor ->
|
||||||
|
cursor.moveToFirst()
|
||||||
|
|
||||||
|
cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)).let {
|
||||||
|
val uri = Uri.parse(it)
|
||||||
|
|
||||||
|
when (uri.scheme) {
|
||||||
|
"file" ->
|
||||||
|
FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", File(uri.path!!))
|
||||||
|
"content" -> uri
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Notification
|
||||||
|
|
||||||
|
val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getActivity(context, 0, Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
setDataAndType(uri, MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(context, "update")
|
||||||
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
|
.setContentTitle(context.getText(R.string.update_download_completed))
|
||||||
|
.setContentText(context.getText(R.string.update_download_completed_description))
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
notificationManager.notify(NOTIFICATION_ID_UPDATE, notification)
|
||||||
|
}
|
||||||
|
ACTION_CANCEL_IMPORT -> {
|
||||||
|
cancelImport = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,10 +31,12 @@ 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 xyz.quaver.proxy
|
import xyz.quaver.proxy
|
||||||
import xyz.quaver.pupil.util.Histories
|
import xyz.quaver.pupil.util.Histories
|
||||||
import xyz.quaver.pupil.util.getProxy
|
import xyz.quaver.pupil.util.getProxy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class Pupil : MultiDexApplication() {
|
class Pupil : MultiDexApplication() {
|
||||||
|
|
||||||
@@ -48,10 +50,23 @@ class Pupil : MultiDexApplication() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
|
val userID =
|
||||||
|
if (preference.getString("user_id", "").isNullOrEmpty()) {
|
||||||
|
UUID.randomUUID().toString().also {
|
||||||
|
preference.edit().putString("user_id", it).apply()
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
preference.getString("user_id", "") ?: ""
|
||||||
|
|
||||||
|
FirebaseCrashlytics.getInstance().setUserId(userID)
|
||||||
|
|
||||||
proxy = getProxy(this)
|
proxy = getProxy(this)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
preference.getString("dl_location", null)
|
preference.getString("dl_location", null).also {
|
||||||
|
if (!File(it!!).canWrite())
|
||||||
|
throw Exception()
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
preference.edit().remove("dl_location").apply()
|
preference.edit().remove("dl_location").apply()
|
||||||
}
|
}
|
||||||
@@ -72,13 +87,27 @@ class Pupil : MultiDexApplication() {
|
|||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_MIN).apply {
|
|
||||||
|
manager.createNotificationChannel(NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
|
||||||
description = getString(R.string.channel_download_description)
|
description = getString(R.string.channel_download_description)
|
||||||
enableLights(false)
|
enableLights(false)
|
||||||
enableVibration(false)
|
enableVibration(false)
|
||||||
lockscreenVisibility = Notification.VISIBILITY_SECRET
|
lockscreenVisibility = Notification.VISIBILITY_SECRET
|
||||||
}
|
})
|
||||||
manager.createNotificationChannel(channel)
|
|
||||||
|
manager.createNotificationChannel(NotificationChannel("update", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply {
|
||||||
|
description = getString(R.string.channel_update_description)
|
||||||
|
enableLights(true)
|
||||||
|
enableVibration(true)
|
||||||
|
lockscreenVisibility = Notification.VISIBILITY_SECRET
|
||||||
|
})
|
||||||
|
|
||||||
|
manager.createNotificationChannel(NotificationChannel("import", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply {
|
||||||
|
description = getString(R.string.channel_update_description)
|
||||||
|
enableLights(false)
|
||||||
|
enableVibration(false)
|
||||||
|
lockscreenVisibility = Notification.VISIBILITY_SECRET
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ 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.core.content.ContextCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.RequestManager
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.daimajia.swipe.SwipeLayout
|
import com.daimajia.swipe.SwipeLayout
|
||||||
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
||||||
@@ -42,20 +43,21 @@ import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
|||||||
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.withContext
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
|
import xyz.quaver.hitomi.getReader
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.util.Histories
|
import xyz.quaver.pupil.util.Histories
|
||||||
import xyz.quaver.pupil.util.download.Cache
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
import xyz.quaver.pupil.util.download.DownloadWorker
|
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryBlock>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<GalleryBlock>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||||
|
|
||||||
enum class ViewType {
|
enum class ViewType {
|
||||||
NEXT,
|
NEXT,
|
||||||
@@ -63,20 +65,20 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
|||||||
PREV
|
PREV
|
||||||
}
|
}
|
||||||
|
|
||||||
private val glide = Glide.with(context)
|
|
||||||
private lateinit var favorites: Histories
|
private lateinit var favorites: Histories
|
||||||
|
|
||||||
val timer = Timer()
|
val timer = Timer()
|
||||||
|
|
||||||
|
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
|
var timerTask: TimerTask? = null
|
||||||
|
|
||||||
private fun updateProgress(context: Context, galleryID: Int) {
|
private fun updateProgress(context: Context, galleryID: Int) {
|
||||||
val cache = Cache(context).getCachedGallery(galleryID)
|
|
||||||
val reader = Cache(context).getReaderOrNull(galleryID)
|
val reader = Cache(context).getReaderOrNull(galleryID)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (reader == null) {
|
if (reader == null || PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false)) {
|
||||||
view.galleryblock_progressbar.visibility = View.GONE
|
view.galleryblock_progressbar.visibility = View.GONE
|
||||||
view.galleryblock_progress_complete.visibility = View.GONE
|
view.galleryblock_progress_complete.visibility = View.GONE
|
||||||
return@launch
|
return@launch
|
||||||
@@ -84,9 +86,7 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
|||||||
|
|
||||||
with(view.galleryblock_progressbar) {
|
with(view.galleryblock_progressbar) {
|
||||||
|
|
||||||
progress = cache.listFiles()?.count { file ->
|
progress = Cache(context).getImages(galleryID)?.size ?: 0
|
||||||
Regex("^[0-9]+.+\$").matches(file.name)
|
|
||||||
} ?: 0
|
|
||||||
|
|
||||||
if (visibility == View.GONE) {
|
if (visibility == View.GONE) {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
@@ -126,6 +126,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
|||||||
val artists = galleryBlock.artists
|
val artists = galleryBlock.artists
|
||||||
val series = galleryBlock.series
|
val series = galleryBlock.series
|
||||||
|
|
||||||
|
if (isThin)
|
||||||
|
galleryblock_thumbnail.layoutParams.width = context.resources.getDimensionPixelSize(
|
||||||
|
R.dimen.galleryblock_thumbnail_thin
|
||||||
|
)
|
||||||
galleryblock_thumbnail.setImageDrawable(CircularProgressDrawable(context).also {
|
galleryblock_thumbnail.setImageDrawable(CircularProgressDrawable(context).also {
|
||||||
it.start()
|
it.start()
|
||||||
})
|
})
|
||||||
@@ -138,16 +142,18 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
glide
|
galleryblock_thumbnail.post {
|
||||||
.load(thumbnail)
|
glide
|
||||||
.skipMemoryCache(true)
|
.load(thumbnail)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.skipMemoryCache(true)
|
||||||
.error(R.drawable.image_broken_variant)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.apply {
|
.error(R.drawable.image_broken_variant)
|
||||||
if (BuildConfig.CENSOR)
|
.apply {
|
||||||
override(5, 8)
|
if (BuildConfig.CENSOR)
|
||||||
}
|
override(5, 8)
|
||||||
.into(galleryblock_thumbnail)
|
}
|
||||||
|
.into(galleryblock_thumbnail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Check cache
|
//Check cache
|
||||||
@@ -215,12 +221,12 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
|||||||
"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.ic_gender_male_white)
|
ContextCompat.getDrawable(context, R.drawable.gender_male)
|
||||||
}
|
}
|
||||||
"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.ic_gender_female_white)
|
ContextCompat.getDrawable(context, R.drawable.gender_female)
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -234,6 +240,15 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
|||||||
}
|
}
|
||||||
|
|
||||||
galleryblock_id.text = galleryBlock.id.toString()
|
galleryblock_id.text = galleryBlock.id.toString()
|
||||||
|
galleryblock_pagecount.text = "-"
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val pageCount = kotlin.runCatching {
|
||||||
|
getReader(galleryBlock.id).galleryInfo.files.size
|
||||||
|
}.getOrNull() ?: return@launch
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
galleryblock_pagecount.text = context.getString(R.string.galleryblock_pagecount, pageCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!::favorites.isInitialized)
|
if (!::favorites.isInitialized)
|
||||||
favorites = (context.applicationContext as Pupil).favorites
|
favorites = (context.applicationContext as Pupil).favorites
|
||||||
@@ -264,6 +279,14 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Make some views invisible to make it thinner
|
||||||
|
if (isThin) {
|
||||||
|
galleryblock_language.visibility = View.GONE
|
||||||
|
galleryblock_type.visibility = View.GONE
|
||||||
|
galleryblock_tag_group.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,10 +364,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryB
|
|||||||
mItemManger.closeAllExcept(layout)
|
mItemManger.closeAllExcept(layout)
|
||||||
|
|
||||||
holder.view.galleryblock_download.text =
|
holder.view.galleryblock_download.text =
|
||||||
if (DownloadWorker.getInstance(holder.view.context).progress.indexOfKey(gallery.id) < 0)
|
if (Cache(holder.view.context).isDownloading(gallery.id))
|
||||||
holder.view.context.getString(R.string.main_download)
|
|
||||||
else
|
|
||||||
holder.view.context.getString(android.R.string.cancel)
|
holder.view.context.getString(android.R.string.cancel)
|
||||||
|
else
|
||||||
|
holder.view.context.getString(R.string.main_download)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClose(layout: SwipeLayout?) {}
|
override fun onClose(layout: SwipeLayout?) {}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
@@ -60,6 +61,7 @@ class MirrorAdapter(context: Context) : RecyclerView.Adapter<MirrorAdapter.ViewH
|
|||||||
var onStartDrag : ((ViewHolder) -> Unit)? = null
|
var onStartDrag : ((ViewHolder) -> Unit)? = null
|
||||||
var onItemMoved : ((List<String>) -> (Unit))? = null
|
var onItemMoved : ((List<String>) -> (Unit))? = null
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
with(holder.view) {
|
with(holder.view) {
|
||||||
mirror_name.text = mirrors[list.elementAt(position)]
|
mirror_name.text = mirrors[list.elementAt(position)]
|
||||||
|
|||||||
@@ -18,61 +18,38 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.app.Activity
|
||||||
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 androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.RequestManager
|
||||||
import com.bumptech.glide.ListPreloader
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.RequestBuilder
|
import com.bumptech.glide.load.model.GlideUrl
|
||||||
import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader
|
import com.bumptech.glide.load.model.LazyHeaders
|
||||||
import com.crashlytics.android.Crashlytics
|
|
||||||
import io.fabric.sdk.android.Fabric
|
|
||||||
import kotlinx.android.synthetic.main.item_reader.view.*
|
import kotlinx.android.synthetic.main.item_reader.view.*
|
||||||
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 xyz.quaver.Code
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.hitomi.getReferer
|
||||||
|
import xyz.quaver.hitomi.imageUrlFromImage
|
||||||
|
import xyz.quaver.hiyobi.cookie
|
||||||
|
import xyz.quaver.hiyobi.createImgList
|
||||||
|
import xyz.quaver.hiyobi.user_agent
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.util.download.Cache
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
import xyz.quaver.pupil.util.download.DownloadWorker
|
import xyz.quaver.pupil.util.download.DownloadWorker
|
||||||
import java.io.File
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class ReaderAdapter(private val context: Context,
|
class ReaderAdapter(private val glide: RequestManager,
|
||||||
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
private val galleryID: Int,
|
||||||
|
private val activity: Activity) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||||
val glide = Glide.with(context)
|
|
||||||
|
|
||||||
//region Glide.RecyclerView
|
|
||||||
val sizeProvider = ListPreloader.PreloadSizeProvider<File> { _, _, position ->
|
|
||||||
Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.files?.getOrNull(position)?.let {
|
|
||||||
arrayOf(it.width, it.height).toIntArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val modelProvider = object: ListPreloader.PreloadModelProvider<File> {
|
|
||||||
override fun getPreloadItems(position: Int): MutableList<File> {
|
|
||||||
return listOf(Cache(context).getImages(galleryID)?.getOrNull(position)).filterNotNullTo(mutableListOf())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPreloadRequestBuilder(item: File): RequestBuilder<*>? {
|
|
||||||
return glide
|
|
||||||
.load(item)
|
|
||||||
.fitCenter()
|
|
||||||
.error(R.drawable.image_broken_variant)
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val preloader = RecyclerViewPreloader<File>(glide, modelProvider, sizeProvider, 10)
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
var reader: Reader? = null
|
var reader: Reader? = null
|
||||||
val timer = Timer()
|
val timer = Timer()
|
||||||
@@ -83,6 +60,8 @@ class ReaderAdapter(private val context: Context,
|
|||||||
|
|
||||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
|
var downloadWorker: DownloadWorker? = 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
|
||||||
@@ -94,12 +73,18 @@ class ReaderAdapter(private val context: Context,
|
|||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
holder.view as ConstraintLayout
|
holder.view as ConstraintLayout
|
||||||
|
|
||||||
|
if (downloadWorker == null)
|
||||||
|
downloadWorker = DownloadWorker.getInstance(holder.view.context)
|
||||||
|
|
||||||
if (isFullScreen) {
|
if (isFullScreen) {
|
||||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
|
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
|
||||||
holder.view.container.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
holder.view.container.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||||
} else {
|
} else {
|
||||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
|
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
|
||||||
holder.view.container.layoutParams.height = 0
|
holder.view.container.layoutParams.height = 0
|
||||||
|
|
||||||
|
(holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
|
||||||
|
.dimensionRatio = "W,${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
||||||
@@ -110,41 +95,59 @@ class ReaderAdapter(private val context: Context,
|
|||||||
onItemClickListener?.invoke(position)
|
onItemClickListener?.invoke(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isFullScreen) {
|
|
||||||
(holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
|
|
||||||
.dimensionRatio = "${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.view.reader_index.text = (position+1).toString()
|
holder.view.reader_index.text = (position+1).toString()
|
||||||
|
|
||||||
val images = Cache(context).getImage(galleryID, position)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(holder.view.context)
|
||||||
val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)
|
if (preferences.getBoolean("cache_disable", false)) {
|
||||||
|
val lowQuality = preferences.getBoolean("low_quality", false)
|
||||||
if (progress?.isInfinite() == true && images != null) {
|
|
||||||
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
|
||||||
|
|
||||||
glide
|
|
||||||
.load(images)
|
|
||||||
.fitCenter()
|
|
||||||
.error(R.drawable.image_broken_variant)
|
|
||||||
.apply {
|
|
||||||
if (BuildConfig.CENSOR)
|
|
||||||
override(5, 8)
|
|
||||||
}
|
|
||||||
.into(holder.view.image)
|
|
||||||
} else {
|
|
||||||
holder.view.reader_item_progressbar.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
if (progress?.isNaN() == true) {
|
|
||||||
if (Fabric.isInitialized())
|
|
||||||
Crashlytics.logException(DownloadWorker.getInstance(context).exception[galleryID]?.get(position))
|
|
||||||
|
|
||||||
|
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, LazyHeaders.Builder()
|
||||||
|
.addHeader("User-Agent", user_agent)
|
||||||
|
.addHeader("Cookie", cookie)
|
||||||
|
.build())
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
holder.view.image.post {
|
||||||
glide
|
glide
|
||||||
.load(R.drawable.image_broken_variant)
|
.load(url!!)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.skipMemoryCache(false)
|
||||||
|
.fitCenter()
|
||||||
|
.error(R.drawable.image_broken_variant)
|
||||||
.into(holder.view.image)
|
.into(holder.view.image)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val image = Cache(holder.view.context).getImage(galleryID, position)
|
||||||
|
val progress = downloadWorker!!.progress[galleryID]?.get(position)
|
||||||
|
|
||||||
|
if (progress?.isInfinite() == true && image != null) {
|
||||||
|
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
||||||
|
|
||||||
|
holder.view.image.post {
|
||||||
|
glide
|
||||||
|
.load(image)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.fitCenter()
|
||||||
|
.error(R.drawable.image_broken_variant)
|
||||||
|
.into(holder.view.image)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
|
||||||
} else {
|
} else {
|
||||||
|
holder.view.reader_item_progressbar.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
|
||||||
@@ -152,11 +155,11 @@ class ReaderAdapter(private val context: Context,
|
|||||||
progress?.roundToInt() ?: 0
|
progress?.roundToInt() ?: 0
|
||||||
|
|
||||||
holder.view.image.setImageDrawable(null)
|
holder.view.image.setImageDrawable(null)
|
||||||
}
|
|
||||||
|
|
||||||
timer.schedule(1000) {
|
timer.schedule(1000) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,23 +21,157 @@ package xyz.quaver.pupil.ui
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationUtils
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.andrognito.patternlockview.PatternLockView
|
import com.andrognito.patternlockview.PatternLockView
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.android.synthetic.main.activity_lock.*
|
import kotlinx.android.synthetic.main.activity_lock.*
|
||||||
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
|
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
|
||||||
|
import kotlinx.android.synthetic.main.fragment_pin_lock.*
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.ui.fragment.PINLockFragment
|
||||||
import xyz.quaver.pupil.ui.fragment.PatternLockFragment
|
import xyz.quaver.pupil.ui.fragment.PatternLockFragment
|
||||||
import xyz.quaver.pupil.util.Lock
|
import xyz.quaver.pupil.util.Lock
|
||||||
import xyz.quaver.pupil.util.LockManager
|
import xyz.quaver.pupil.util.LockManager
|
||||||
|
|
||||||
class LockActivity : AppCompatActivity() {
|
class LockActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var lockManager: LockManager
|
||||||
|
private var mode: String? = null
|
||||||
|
|
||||||
|
private val patternLockFragment = PatternLockFragment().apply {
|
||||||
|
var lastPass = ""
|
||||||
|
onPatternDrawn = {
|
||||||
|
when(mode) {
|
||||||
|
null -> {
|
||||||
|
val result = lockManager.check(it)
|
||||||
|
|
||||||
|
if (result == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
finish()
|
||||||
|
} else
|
||||||
|
lock_pattern_view.setViewMode(PatternLockView.PatternViewMode.WRONG)
|
||||||
|
}
|
||||||
|
"add_lock" -> {
|
||||||
|
if (lastPass.isEmpty()) {
|
||||||
|
lastPass = it
|
||||||
|
|
||||||
|
Snackbar.make(view!!, R.string.settings_lock_confirm, Snackbar.LENGTH_LONG).show()
|
||||||
|
} else {
|
||||||
|
if (lastPass == it) {
|
||||||
|
LockManager(context!!).add(Lock.generate(Lock.Type.PATTERN, it))
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
lock_pattern_view.setViewMode(PatternLockView.PatternViewMode.WRONG)
|
||||||
|
lastPass = ""
|
||||||
|
|
||||||
|
Snackbar.make(view!!, R.string.settings_lock_wrong_confirm, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val pinLockFragment = PINLockFragment().apply {
|
||||||
|
var lastPass = ""
|
||||||
|
onPINEntered = {
|
||||||
|
when(mode) {
|
||||||
|
null -> {
|
||||||
|
val result = lockManager.check(it)
|
||||||
|
|
||||||
|
if (result == true) {
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
indicator_dots.startAnimation(AnimationUtils.loadAnimation(context, R.anim.shake).apply {
|
||||||
|
setAnimationListener(object: Animation.AnimationListener {
|
||||||
|
override fun onAnimationEnd(animation: Animation?) {
|
||||||
|
pin_lock_view.resetPinLockView()
|
||||||
|
pin_lock_view.isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationStart(animation: Animation?) {
|
||||||
|
pin_lock_view.isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationRepeat(animation: Animation?) {
|
||||||
|
// Do Nothing
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"add_lock" -> {
|
||||||
|
if (lastPass.isEmpty()) {
|
||||||
|
lastPass = it
|
||||||
|
|
||||||
|
pin_lock_view.resetPinLockView()
|
||||||
|
Snackbar.make(view!!, R.string.settings_lock_confirm, Snackbar.LENGTH_LONG).show()
|
||||||
|
} else {
|
||||||
|
if (lastPass == it) {
|
||||||
|
LockManager(context!!).add(Lock.generate(Lock.Type.PIN, it))
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
indicator_dots.startAnimation(AnimationUtils.loadAnimation(context, R.anim.shake).apply {
|
||||||
|
setAnimationListener(object: Animation.AnimationListener {
|
||||||
|
override fun onAnimationEnd(animation: Animation?) {
|
||||||
|
pin_lock_view.resetPinLockView()
|
||||||
|
pin_lock_view.isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationStart(animation: Animation?) {
|
||||||
|
pin_lock_view.isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationRepeat(animation: Animation?) {
|
||||||
|
// Do Nothing
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
lastPass = ""
|
||||||
|
|
||||||
|
Snackbar.make(view!!, R.string.settings_lock_wrong_confirm, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showBiometricPrompt() {
|
||||||
|
val promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(getText(R.string.settings_lock_fingerprint_prompt))
|
||||||
|
.setSubtitle(getText(R.string.settings_lock_fingerprint_prompt_subtitle))
|
||||||
|
.setNegativeButtonText(getText(android.R.string.cancel))
|
||||||
|
.setConfirmationRequired(false)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val biometricPrompt = BiometricPrompt(this, ContextCompat.getMainExecutor(this),
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationSucceeded(
|
||||||
|
result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Displays the "log in" prompt.
|
||||||
|
biometricPrompt.authenticate(promptInfo)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_lock)
|
setContentView(R.layout.activity_lock)
|
||||||
|
|
||||||
val lockManager = try {
|
lockManager = try {
|
||||||
LockManager(this)
|
LockManager(this)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
AlertDialog.Builder(this).apply {
|
AlertDialog.Builder(this).apply {
|
||||||
@@ -50,12 +184,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val mode = intent.getStringExtra("mode")
|
mode = intent.getStringExtra("mode")
|
||||||
|
|
||||||
lock_pattern.isEnabled = false
|
|
||||||
lock_pin.isEnabled = false
|
|
||||||
lock_fingerprint.isEnabled = false
|
|
||||||
lock_password.isEnabled = false
|
|
||||||
|
|
||||||
when(mode) {
|
when(mode) {
|
||||||
null -> {
|
null -> {
|
||||||
@@ -64,52 +193,75 @@ class LockActivity : AppCompatActivity() {
|
|||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lock_fingerprint", false)
|
||||||
|
&& BiometricManager.from(this).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|
) {
|
||||||
|
lock_fingerprint.apply {
|
||||||
|
isEnabled = true
|
||||||
|
setOnClickListener {
|
||||||
|
showBiometricPrompt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showBiometricPrompt()
|
||||||
|
}
|
||||||
|
|
||||||
|
lock_pattern.apply {
|
||||||
|
isEnabled = lockManager.contains(Lock.Type.PATTERN)
|
||||||
|
setOnClickListener {
|
||||||
|
supportFragmentManager.beginTransaction().replace(
|
||||||
|
R.id.lock_content, patternLockFragment
|
||||||
|
).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lock_pin.apply {
|
||||||
|
isEnabled = lockManager.contains(Lock.Type.PIN)
|
||||||
|
setOnClickListener {
|
||||||
|
supportFragmentManager.beginTransaction().replace(
|
||||||
|
R.id.lock_content, pinLockFragment
|
||||||
|
).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lock_password.isEnabled = false
|
||||||
|
|
||||||
|
when (lockManager.locks!!.first().type) {
|
||||||
|
Lock.Type.PIN -> {
|
||||||
|
|
||||||
|
supportFragmentManager.beginTransaction().add(
|
||||||
|
R.id.lock_content, pinLockFragment
|
||||||
|
).commit()
|
||||||
|
}
|
||||||
|
Lock.Type.PATTERN -> {
|
||||||
|
supportFragmentManager.beginTransaction().add(
|
||||||
|
R.id.lock_content, patternLockFragment
|
||||||
|
).commit()
|
||||||
|
}
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"add_lock" -> {
|
"add_lock" -> {
|
||||||
|
lock_pattern.isEnabled = false
|
||||||
|
lock_pin.isEnabled = false
|
||||||
|
lock_fingerprint.isEnabled = false
|
||||||
|
lock_password.isEnabled = false
|
||||||
|
|
||||||
when(intent.getStringExtra("type")!!) {
|
when(intent.getStringExtra("type")!!) {
|
||||||
"pattern" -> {
|
"pattern" -> {
|
||||||
|
lock_pattern.isEnabled = true
|
||||||
|
supportFragmentManager.beginTransaction().add(
|
||||||
|
R.id.lock_content, patternLockFragment
|
||||||
|
).commit()
|
||||||
|
}
|
||||||
|
"pin" -> {
|
||||||
|
lock_pin.isEnabled = true
|
||||||
|
supportFragmentManager.beginTransaction().add(
|
||||||
|
R.id.lock_content, pinLockFragment
|
||||||
|
).commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
supportFragmentManager.beginTransaction().add(
|
|
||||||
R.id.lock_content,
|
|
||||||
PatternLockFragment().apply {
|
|
||||||
var lastPass = ""
|
|
||||||
onPatternDrawn = {
|
|
||||||
when(mode) {
|
|
||||||
null -> {
|
|
||||||
val result = lockManager.check(it)
|
|
||||||
|
|
||||||
if (result == true) {
|
|
||||||
setResult(Activity.RESULT_OK)
|
|
||||||
finish()
|
|
||||||
} else
|
|
||||||
lock_pattern_view.setViewMode(PatternLockView.PatternViewMode.WRONG)
|
|
||||||
}
|
|
||||||
"add_lock" -> {
|
|
||||||
if (lastPass.isEmpty()) {
|
|
||||||
lastPass = it
|
|
||||||
|
|
||||||
Snackbar.make(view!!, R.string.settings_lock_confirm, Snackbar.LENGTH_LONG).show()
|
|
||||||
} else {
|
|
||||||
if (lastPass == it) {
|
|
||||||
LockManager(context!!).add(Lock.generate(Lock.Type.PATTERN, it))
|
|
||||||
finish()
|
|
||||||
} else {
|
|
||||||
lock_pattern_view.setViewMode(PatternLockView.PatternViewMode.WRONG)
|
|
||||||
lastPass = ""
|
|
||||||
|
|
||||||
Snackbar.make(view!!, R.string.settings_lock_wrong_confirm, Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).commit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
@@ -25,15 +26,13 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.*
|
import android.text.*
|
||||||
import android.text.style.AlignmentSpan
|
import android.text.style.AlignmentSpan
|
||||||
|
import android.util.TypedValue
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.EditText
|
import android.widget.*
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
@@ -43,15 +42,16 @@ import androidx.core.view.GravityCompat
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.arlib.floatingsearchview.FloatingSearchView
|
import com.arlib.floatingsearchview.FloatingSearchView
|
||||||
|
import com.arlib.floatingsearchview.FloatingSearchViewDayNight
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import com.arlib.floatingsearchview.util.view.SearchInputView
|
import com.arlib.floatingsearchview.util.view.SearchInputView
|
||||||
import com.crashlytics.android.Crashlytics
|
import com.bumptech.glide.Glide
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import io.fabric.sdk.android.Fabric
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.activity_main_content.*
|
import kotlinx.android.synthetic.main.activity_main_content.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.list
|
import kotlinx.serialization.builtins.list
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.doSearch
|
import xyz.quaver.hitomi.doSearch
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||||
@@ -195,7 +195,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val perPage = preference.getString("per_page", "25")!!.toInt()
|
val perPage = preference.getString("per_page", "25")!!.toIntOrNull() ?: 25
|
||||||
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
|
|
||||||
return when(keyCode) {
|
return when(keyCode) {
|
||||||
@@ -330,11 +330,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(main_fab_cancel) {
|
||||||
|
setImageResource(R.drawable.cancel)
|
||||||
|
setOnClickListener {
|
||||||
|
DownloadWorker.getInstance(context).stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with(main_fab_jump) {
|
with(main_fab_jump) {
|
||||||
setImageResource(R.drawable.ic_jump)
|
setImageResource(R.drawable.ic_jump)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val perPage = preference.getString("per_page", "25")!!.toInt()
|
val perPage = preference.getString("per_page", "25")!!.toIntOrNull() ?: 25
|
||||||
val editText = EditText(context)
|
val editText = EditText(context)
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
@@ -359,17 +366,42 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(main_fab_random) {
|
||||||
|
setImageResource(R.drawable.shuffle_variant)
|
||||||
|
setOnClickListener {
|
||||||
|
runBlocking {
|
||||||
|
withTimeoutOrNull(100) {
|
||||||
|
galleryIDs?.await()
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
if (it?.isEmpty() == false) {
|
||||||
|
val galleryID = it.random()
|
||||||
|
|
||||||
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
|
||||||
|
putExtra("galleryID", galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
|
||||||
|
histories.add(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with(main_fab_id) {
|
with(main_fab_id) {
|
||||||
setImageResource(R.drawable.numeric)
|
setImageResource(R.drawable.numeric)
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
val editText = EditText(context)
|
val editText = EditText(context).apply {
|
||||||
|
inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
|
}
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
setView(editText)
|
setView(editText)
|
||||||
setTitle(R.string.main_open_gallery_by_id)
|
setTitle(R.string.main_open_gallery_by_id)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
val galleryID = editText.text.toString().toInt()
|
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
|
||||||
putExtra("galleryID", galleryID)
|
putExtra("galleryID", galleryID)
|
||||||
}
|
}
|
||||||
@@ -388,9 +420,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
with(main_recyclerview) {
|
with(main_recyclerview) {
|
||||||
adapter = GalleryBlockAdapter(this@MainActivity, galleries).apply {
|
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
@@ -404,17 +437,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
onDownloadClickedHandler = { position ->
|
onDownloadClickedHandler = { position ->
|
||||||
val galleryID = galleries[position].id
|
val galleryID = galleries[position].id
|
||||||
|
val worker = DownloadWorker.getInstance(context)
|
||||||
if (!completeFlag.get(galleryID, false)) {
|
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
|
||||||
val worker = DownloadWorker.getInstance(context)
|
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
||||||
|
else {
|
||||||
if (worker.progress.indexOfKey(galleryID) >= 0) //download in progress
|
if (worker.progress.indexOfKey(galleryID) >= 0 && Cache(context).isDownloading(galleryID)) { //download in progress
|
||||||
|
Cache(context).setDownloading(galleryID, false)
|
||||||
worker.cancel(galleryID)
|
worker.cancel(galleryID)
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
Cache(context).setDownloading(galleryID, true)
|
Cache(context).setDownloading(galleryID, true)
|
||||||
|
|
||||||
if (!worker.queue.contains(galleryID))
|
worker.queue.add(galleryID)
|
||||||
worker.queue.add(galleryID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,6 +501,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
GalleryDialog(
|
GalleryDialog(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
|
Glide.with(this@MainActivity),
|
||||||
galleryID
|
galleryID
|
||||||
).apply {
|
).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
@@ -712,7 +747,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
})
|
})
|
||||||
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
||||||
|
|
||||||
with(main_searchview as FloatingSearchView) {
|
with(main_searchview as FloatingSearchViewDayNight) {
|
||||||
val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json")
|
val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json")
|
||||||
val serializer = Tag.serializer().list
|
val serializer = Tag.serializer().list
|
||||||
|
|
||||||
@@ -721,9 +756,28 @@ class MainActivity : AppCompatActivity() {
|
|||||||
favoritesFile.writeText(json.stringify(serializer, Tags(listOf())))
|
favoritesFile.writeText(json.stringify(serializer, Tags(listOf())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
|
||||||
|
override fun onMenuOpened() {
|
||||||
|
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuClosed() {
|
||||||
|
//Do Nothing
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
setOnMenuItemClickListener {
|
||||||
when(it.itemId) {
|
when(it.itemId) {
|
||||||
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
|
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS)
|
||||||
|
R.id.main_menu_thin -> {
|
||||||
|
main_recyclerview.apply {
|
||||||
|
(adapter as GalleryBlockAdapter).apply {
|
||||||
|
isThin = !isThin
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = adapter // Force to redraw
|
||||||
|
}
|
||||||
|
}
|
||||||
R.id.main_menu_sort_newest -> {
|
R.id.main_menu_sort_newest -> {
|
||||||
sortMode = SortMode.NEWEST
|
sortMode = SortMode.NEWEST
|
||||||
it.isChecked = true
|
it.isChecked = true
|
||||||
@@ -792,20 +846,23 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
|
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
|
||||||
|
|
||||||
|
val color = TypedValue()
|
||||||
|
theme.resolveAttribute(R.attr.colorControlNormal, color, true)
|
||||||
|
|
||||||
leftIcon.setImageDrawable(
|
leftIcon.setImageDrawable(
|
||||||
ResourcesCompat.getDrawable(
|
ResourcesCompat.getDrawable(
|
||||||
resources,
|
resources,
|
||||||
when(item.n) {
|
when(item.n) {
|
||||||
"female" -> R.drawable.ic_gender_female
|
"female" -> R.drawable.gender_female
|
||||||
"male" -> R.drawable.ic_gender_male
|
"male" -> R.drawable.gender_male
|
||||||
"language" -> R.drawable.ic_translate
|
"language" -> R.drawable.translate
|
||||||
"group" -> R.drawable.ic_account_group
|
"group" -> R.drawable.account_group
|
||||||
"character" -> R.drawable.ic_account_star
|
"character" -> R.drawable.account_star
|
||||||
"series" -> R.drawable.ic_book_open
|
"series" -> R.drawable.book_open
|
||||||
"artist" -> R.drawable.ic_brush
|
"artist" -> R.drawable.brush
|
||||||
else -> R.drawable.ic_tag
|
else -> R.drawable.tag
|
||||||
},
|
},
|
||||||
null)
|
context.theme)
|
||||||
)
|
)
|
||||||
|
|
||||||
with(suggestionView.findViewById<ImageView>(R.id.right_icon)) {
|
with(suggestionView.findViewById<ImageView>(R.id.right_icon)) {
|
||||||
@@ -1009,8 +1066,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
if (Fabric.isInitialized() && e.message != "No result")
|
if (e.message != "No result")
|
||||||
Crashlytics.logException(e)
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
main_noresult.visibility = View.VISIBLE
|
main_noresult.visibility = View.VISIBLE
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.graphics.drawable.Animatable
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@@ -32,12 +33,15 @@ 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.crashlytics.android.Crashlytics
|
import com.bumptech.glide.Glide
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import io.fabric.sdk.android.Fabric
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.android.synthetic.main.activity_reader.*
|
import kotlinx.android.synthetic.main.activity_reader.*
|
||||||
import kotlinx.android.synthetic.main.activity_reader.view.*
|
import kotlinx.android.synthetic.main.activity_reader.view.*
|
||||||
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import xyz.quaver.Code
|
import xyz.quaver.Code
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
@@ -90,8 +94,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
handleIntent(intent)
|
handleIntent(intent)
|
||||||
|
|
||||||
if (Fabric.isInitialized())
|
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
||||||
Crashlytics.setInt("GalleryID", galleryID)
|
|
||||||
|
|
||||||
if (galleryID == 0) {
|
if (galleryID == 0) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
@@ -99,7 +102,36 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
initDownloader()
|
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("cache_disable", false)) {
|
||||||
|
reader_download_progressbar.visibility = View.GONE
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val reader = Cache(this@ReaderActivity).getReader(galleryID)
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) initDownloader@{
|
||||||
|
if (reader == null) {
|
||||||
|
Snackbar
|
||||||
|
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||||
|
.show()
|
||||||
|
return@initDownloader
|
||||||
|
}
|
||||||
|
|
||||||
|
(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()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
@@ -112,14 +144,12 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
val uri = intent.data
|
val uri = intent.data
|
||||||
val lastPathSegment = uri?.lastPathSegment
|
val lastPathSegment = uri?.lastPathSegment
|
||||||
if (uri != null && lastPathSegment != null) {
|
if (uri != null && lastPathSegment != null) {
|
||||||
val nonNumber = Regex("[^-?0-9]+")
|
|
||||||
|
|
||||||
galleryID = when (uri.host) {
|
galleryID = when (uri.host) {
|
||||||
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
|
"hitomi.la" ->
|
||||||
"히요비.asia" -> lastPathSegment.toInt()
|
Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1)?.toIntOrNull() ?: 0
|
||||||
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
|
"hiyobi.me" -> lastPathSegment.toInt()
|
||||||
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
||||||
else -> return
|
else -> 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -157,10 +187,10 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||||
when(item?.itemId) {
|
when(item?.itemId) {
|
||||||
R.id.reader_menu_page_indicator -> {
|
R.id.reader_menu_page_indicator -> {
|
||||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
|
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
|
||||||
with(view.dialog_number_picker) {
|
with(view.dialog_number_picker) {
|
||||||
minValue=1
|
minValue=1
|
||||||
maxValue=reader_recyclerview?.adapter?.itemCount ?: 0
|
maxValue=Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.files?.size ?: 0
|
||||||
value=currentPage
|
value=currentPage
|
||||||
}
|
}
|
||||||
val dialog = AlertDialog.Builder(this).apply {
|
val dialog = AlertDialog.Builder(this).apply {
|
||||||
@@ -234,6 +264,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun initDownloader() {
|
private fun initDownloader() {
|
||||||
val worker = DownloadWorker.getInstance(this).apply {
|
val worker = DownloadWorker.getInstance(this).apply {
|
||||||
|
cancel(galleryID)
|
||||||
queue.add(galleryID)
|
queue.add(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +281,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||||
reader_download_progressbar.progress = worker.progress[galleryID]?.count { !it.isFinite() } ?: 0
|
reader_download_progressbar.progress = worker.progress[galleryID]?.count { it.isInfinite() } ?: 0
|
||||||
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
|
||||||
|
|
||||||
if (title == getString(R.string.reader_loading)) {
|
if (title == getString(R.string.reader_loading)) {
|
||||||
@@ -275,7 +306,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (worker.progress[galleryID]?.all { !it.isFinite() } == true) { //Download finished
|
if (worker.progress[galleryID]?.all { it.isInfinite() } == true) { //Download finished
|
||||||
reader_download_progressbar.visibility = View.GONE
|
reader_download_progressbar.visibility = View.GONE
|
||||||
|
|
||||||
animateDownloadFAB(false)
|
animateDownloadFAB(false)
|
||||||
@@ -286,7 +317,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
with(reader_recyclerview) {
|
with(reader_recyclerview) {
|
||||||
adapter = ReaderAdapter(this@ReaderActivity, galleryID).apply {
|
adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID, this@ReaderActivity).apply {
|
||||||
onItemClickListener = {
|
onItemClickListener = {
|
||||||
if (isScroll) {
|
if (isScroll) {
|
||||||
isScroll = false
|
isScroll = false
|
||||||
@@ -300,7 +331,6 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addOnScrollListener((adapter as ReaderAdapter).preloader)
|
|
||||||
addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
addOnScrollListener(object: RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
@@ -325,13 +355,27 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
animateDownloadFAB(Cache(context).isDownloading(galleryID)) //If download in progress, animate button
|
animateDownloadFAB(Cache(context).isDownloading(galleryID)) //If download in progress, animate button
|
||||||
|
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
if (Cache(context).isDownloading(galleryID)) {
|
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
|
||||||
Cache(context).setDownloading(galleryID, false)
|
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
||||||
|
else {
|
||||||
|
if (Cache(context).isDownloading(galleryID)) {
|
||||||
|
Cache(context).setDownloading(galleryID, false)
|
||||||
|
|
||||||
animateDownloadFAB(false)
|
animateDownloadFAB(false)
|
||||||
} else {
|
} else {
|
||||||
Cache(context).setDownloading(galleryID, true)
|
Cache(context).setDownloading(galleryID, true)
|
||||||
animateDownloadFAB(true)
|
animateDownloadFAB(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(reader_fab_retry) {
|
||||||
|
setImageResource(R.drawable.refresh)
|
||||||
|
setOnClickListener {
|
||||||
|
DownloadWorker.getInstance(context).let {
|
||||||
|
it.cancel(galleryID)
|
||||||
|
it.queue.add(galleryID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,6 +405,8 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
window.attributes = this
|
window.attributes = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader_recyclerview.adapter = reader_recyclerview.adapter // Force to redraw
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scrollMode(isScroll: Boolean) {
|
private fun scrollMode(isScroll: Boolean) {
|
||||||
@@ -383,10 +429,10 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
icon?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
|
icon?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
|
||||||
override fun onAnimationEnd(drawable: Drawable?) {
|
override fun onAnimationEnd(drawable: Drawable?) {
|
||||||
val worker = DownloadWorker.getInstance(context)
|
val worker = DownloadWorker.getInstance(context)
|
||||||
if (worker.progress[galleryID]?.all { !it.isFinite() } == true) // If download is finished, stop animating
|
if (worker.progress[galleryID]?.all { it.isInfinite() } == true) // If download is finished, stop animating
|
||||||
post {
|
post {
|
||||||
setImageResource(R.drawable.ic_download)
|
setImageResource(R.drawable.ic_download)
|
||||||
labelText = getString(R.string.reader_fab_download)
|
labelText = getString(R.string.reader_fab_download_cancel)
|
||||||
}
|
}
|
||||||
else // Or continue animate
|
else // Or continue animate
|
||||||
post {
|
post {
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.android.synthetic.main.settings_activity.*
|
import kotlinx.android.synthetic.main.settings_activity.*
|
||||||
import kotlinx.serialization.list
|
import kotlinx.serialization.builtins.list
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.ui.fragment.LockFragment
|
import xyz.quaver.pupil.ui.fragment.LockSettingsFragment
|
||||||
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -84,7 +84,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(R.id.settings, LockFragment())
|
.replace(R.id.settings, LockSettingsFragment())
|
||||||
.addToBackStack("Lock")
|
.addToBackStack("Lock")
|
||||||
.commitAllowingStateLoss()
|
.commitAllowingStateLoss()
|
||||||
}
|
}
|
||||||
@@ -156,6 +156,43 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
REQUEST_IMPORT_OLD_GALLERIES -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
data?.data?.also { uri ->
|
||||||
|
val takeFlags: Int =
|
||||||
|
intent.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||||
|
contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
|
|
||||||
|
val file = uri.toFile(this)
|
||||||
|
|
||||||
|
if (file?.canRead() != true)
|
||||||
|
Snackbar.make(
|
||||||
|
settings,
|
||||||
|
resources.getText(R.string.import_old_galleries_folder_not_readable),
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
else
|
||||||
|
importOldGalleries(this, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REQUEST_IMPORT_OLD_GALLERIES_OLD -> {
|
||||||
|
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
|
||||||
|
val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
|
||||||
|
|
||||||
|
if (!File(directory).canRead())
|
||||||
|
Snackbar.make(
|
||||||
|
settings,
|
||||||
|
resources.getText(R.string.import_old_galleries_folder_not_readable),
|
||||||
|
Snackbar.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
else {
|
||||||
|
importOldGalleries(this, File(directory))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
|
|||||||
|
|
||||||
private val excludeBL = "-male:yaoi"
|
private val excludeBL = "-male:yaoi"
|
||||||
private val excludeGuro = listOf("-female:guro", "-male:guro")
|
private val excludeGuro = listOf("-female:guro", "-male:guro")
|
||||||
|
private val excludeLoli = listOf("-female:loli", "-male:shota")
|
||||||
|
|
||||||
var onPositiveButtonClickListener : ((Tags) -> (Unit))? = null
|
var onPositiveButtonClickListener : ((Tags) -> (Unit))? = null
|
||||||
|
|
||||||
@@ -68,6 +69,11 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
|
|||||||
newTags.add(tag)
|
newTags.add(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (default_query_dialog_loli_checkbox.isChecked)
|
||||||
|
excludeLoli.forEach { tag ->
|
||||||
|
newTags.add(tag)
|
||||||
|
}
|
||||||
|
|
||||||
onPositiveButtonClickListener?.invoke(newTags)
|
onPositiveButtonClickListener?.invoke(newTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +126,14 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(view.default_query_dialog_loli_checkbox) {
|
||||||
|
isChecked = excludeLoli.all { tags.contains(it) }
|
||||||
|
if (excludeLoli.all { tags.contains(it) })
|
||||||
|
excludeLoli.forEach {
|
||||||
|
tags.remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with(view.default_query_dialog_edittext) {
|
with(view.default_query_dialog_edittext) {
|
||||||
setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE)
|
setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE)
|
||||||
addTextChangedListener(object : TextWatcher {
|
addTextChangedListener(object : TextWatcher {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.RequestManager
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
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.*
|
||||||
@@ -53,7 +53,7 @@ import xyz.quaver.pupil.util.ItemClickSupport
|
|||||||
import xyz.quaver.pupil.util.download.Cache
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
|
||||||
class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) {
|
class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : Dialog(context) {
|
||||||
|
|
||||||
private val languages = context.resources.getStringArray(R.array.languages).map {
|
private val languages = context.resources.getStringArray(R.array.languages).map {
|
||||||
it.split("|").let { split ->
|
it.split("|").let { split ->
|
||||||
@@ -61,8 +61,6 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
|
|||||||
}
|
}
|
||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
private val glide = Glide.with(context)
|
|
||||||
|
|
||||||
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -90,7 +88,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
|
|||||||
try {
|
try {
|
||||||
val gallery = getGallery(galleryID)
|
val gallery = getGallery(galleryID)
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
gallery_cover.post {
|
||||||
gallery_progressbar.visibility = View.GONE
|
gallery_progressbar.visibility = View.GONE
|
||||||
gallery_title.text = gallery.title
|
gallery_title.text = gallery.title
|
||||||
gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
|
gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
|
||||||
@@ -112,7 +110,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Glide.with(context)
|
glide
|
||||||
.load(gallery.cover)
|
.load(gallery.cover)
|
||||||
.apply {
|
.apply {
|
||||||
if (BuildConfig.CENSOR)
|
if (BuildConfig.CENSOR)
|
||||||
@@ -171,12 +169,12 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
|
|||||||
"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.ic_gender_male_white)
|
ContextCompat.getDrawable(context, R.drawable.gender_male)
|
||||||
}
|
}
|
||||||
"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.ic_gender_female_white)
|
ContextCompat.getDrawable(context, R.drawable.gender_female)
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -226,7 +224,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
|
|||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
val galleries = ArrayList<GalleryBlock>()
|
val galleries = ArrayList<GalleryBlock>()
|
||||||
|
|
||||||
val adapter = GalleryBlockAdapter(context, galleries).apply {
|
val adapter = GalleryBlockAdapter(glide, galleries).apply {
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||||
handler.invoke(tag)
|
handler.invoke(tag)
|
||||||
@@ -264,6 +262,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
|
|||||||
.setOnItemLongClickListener { _, position, _ ->
|
.setOnItemLongClickListener { _, position, _ ->
|
||||||
GalleryDialog(
|
GalleryDialog(
|
||||||
context,
|
context,
|
||||||
|
glide,
|
||||||
galleries[position].id
|
galleries[position].id
|
||||||
).apply {
|
).apply {
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
|
|||||||
@@ -1,81 +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.ui.fragment
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
|
||||||
import xyz.quaver.pupil.R
|
|
||||||
import xyz.quaver.pupil.ui.LockActivity
|
|
||||||
import xyz.quaver.pupil.util.Lock
|
|
||||||
import xyz.quaver.pupil.util.LockManager
|
|
||||||
|
|
||||||
class LockFragment : PreferenceFragmentCompat() {
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
val lockManager = LockManager(context!!)
|
|
||||||
|
|
||||||
findPreference<Preference>("lock_pattern")?.summary =
|
|
||||||
if (lockManager.contains(Lock.Type.PATTERN))
|
|
||||||
getString(R.string.settings_lock_enabled)
|
|
||||||
else
|
|
||||||
""
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
setPreferencesFromResource(R.xml.lock_preferences, rootKey)
|
|
||||||
|
|
||||||
with(findPreference<Preference>("lock_pattern")) {
|
|
||||||
this!!
|
|
||||||
|
|
||||||
if (LockManager(context!!).contains(Lock.Type.PATTERN))
|
|
||||||
summary = getString(R.string.settings_lock_enabled)
|
|
||||||
|
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
|
||||||
val lockManager = LockManager(context!!)
|
|
||||||
|
|
||||||
if (lockManager.contains(Lock.Type.PATTERN)) {
|
|
||||||
AlertDialog.Builder(context).apply {
|
|
||||||
setTitle(R.string.warning)
|
|
||||||
setMessage(R.string.settings_lock_remove_message)
|
|
||||||
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
|
||||||
lockManager.remove(Lock.Type.PATTERN)
|
|
||||||
onResume()
|
|
||||||
}
|
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
|
||||||
}.show()
|
|
||||||
} else {
|
|
||||||
val intent = Intent(context, LockActivity::class.java).apply {
|
|
||||||
putExtra("mode", "add_lock")
|
|
||||||
putExtra("type", "pattern")
|
|
||||||
}
|
|
||||||
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
|
import xyz.quaver.pupil.util.Lock
|
||||||
|
import xyz.quaver.pupil.util.LockManager
|
||||||
|
|
||||||
|
class LockSettingsFragment :
|
||||||
|
PreferenceFragmentCompat() {
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
val lockManager = LockManager(requireContext())
|
||||||
|
|
||||||
|
findPreference<Preference>("lock_pattern")?.summary =
|
||||||
|
if (lockManager.contains(Lock.Type.PATTERN))
|
||||||
|
getString(R.string.settings_lock_enabled)
|
||||||
|
else
|
||||||
|
""
|
||||||
|
|
||||||
|
findPreference<Preference>("lock_pin")?.summary =
|
||||||
|
if (lockManager.contains(Lock.Type.PIN))
|
||||||
|
getString(R.string.settings_lock_enabled)
|
||||||
|
else
|
||||||
|
""
|
||||||
|
|
||||||
|
if (lockManager.isEmpty()) {
|
||||||
|
(findPreference<Preference>("lock_fingerprint") as SwitchPreferenceCompat).isChecked = false
|
||||||
|
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("lock_fingerprint", false).apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
setPreferencesFromResource(R.xml.lock_preferences, rootKey)
|
||||||
|
|
||||||
|
with(findPreference<Preference>("lock_pattern")) {
|
||||||
|
this!!
|
||||||
|
|
||||||
|
if (LockManager(requireContext()).contains(Lock.Type.PATTERN))
|
||||||
|
summary = getString(R.string.settings_lock_enabled)
|
||||||
|
|
||||||
|
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
val lockManager = LockManager(requireContext())
|
||||||
|
|
||||||
|
if (lockManager.contains(Lock.Type.PATTERN)) {
|
||||||
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
|
setTitle(R.string.warning)
|
||||||
|
setMessage(R.string.settings_lock_remove_message)
|
||||||
|
|
||||||
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
lockManager.remove(Lock.Type.PATTERN)
|
||||||
|
onResume()
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||||
|
}.show()
|
||||||
|
} else {
|
||||||
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
|
putExtra("mode", "add_lock")
|
||||||
|
putExtra("type", "pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(findPreference<Preference>("lock_pin")) {
|
||||||
|
this!!
|
||||||
|
|
||||||
|
if (LockManager(requireContext()).contains(Lock.Type.PIN))
|
||||||
|
summary = getString(R.string.settings_lock_enabled)
|
||||||
|
|
||||||
|
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
|
val lockManager = LockManager(requireContext())
|
||||||
|
|
||||||
|
if (lockManager.contains(Lock.Type.PIN)) {
|
||||||
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
|
setTitle(R.string.warning)
|
||||||
|
setMessage(R.string.settings_lock_remove_message)
|
||||||
|
|
||||||
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
lockManager.remove(Lock.Type.PIN)
|
||||||
|
onResume()
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||||
|
}.show()
|
||||||
|
} else {
|
||||||
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
|
putExtra("mode", "add_lock")
|
||||||
|
putExtra("type", "pin")
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with(findPreference<Preference>("lock_fingerprint")) {
|
||||||
|
this!!
|
||||||
|
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
this as SwitchPreferenceCompat
|
||||||
|
|
||||||
|
if (newValue == true && LockManager(requireContext()).isEmpty()) {
|
||||||
|
isChecked = false
|
||||||
|
|
||||||
|
Toast.makeText(requireContext(), R.string.settings_lock_fingerprint_without_lock, Toast.LENGTH_SHORT).show()
|
||||||
|
} else
|
||||||
|
isChecked = newValue as Boolean
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.andrognito.pinlockview.PinLockListener
|
||||||
|
import kotlinx.android.synthetic.main.fragment_pin_lock.view.*
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
|
||||||
|
class PINLockFragment : Fragment(), PinLockListener {
|
||||||
|
|
||||||
|
var onPINEntered: ((String) -> Unit)? = null
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_pin_lock, container, false).apply {
|
||||||
|
pin_lock_view.attachIndicatorDots(indicator_dots)
|
||||||
|
pin_lock_view.setPinLockListener(this@PINLockFragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onComplete(pin: String?) {
|
||||||
|
onPINEntered?.invoke(pin!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEmpty() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPinChange(pinLength: Int, intermediatePin: String?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,17 +18,23 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.fragment
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
import android.content.Intent
|
import android.Manifest
|
||||||
import android.content.SharedPreferences
|
import android.content.*
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
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.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
||||||
|
import net.rdrei.android.dirchooser.DirectoryChooserConfig
|
||||||
import xyz.quaver.pupil.Pupil
|
import xyz.quaver.pupil.Pupil
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.ui.LockActivity
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
@@ -47,16 +53,12 @@ class SettingsFragment :
|
|||||||
Preference.OnPreferenceChangeListener,
|
Preference.OnPreferenceChangeListener,
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
lateinit var sharedPreference: SharedPreferences
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
val lockManager = LockManager(context!!)
|
val lockManager = LockManager(requireContext())
|
||||||
|
|
||||||
findPreference<Preference>("app_lock")?.summary = if (lockManager.locks.isNullOrEmpty()) {
|
findPreference<Preference>("app_lock")?.summary = if (lockManager.locks.isNullOrEmpty()) {
|
||||||
getString(R.string.settings_lock_none)
|
getString(R.string.settings_lock_none)
|
||||||
@@ -86,9 +88,9 @@ class SettingsFragment :
|
|||||||
checkUpdate(activity as SettingsActivity, true)
|
checkUpdate(activity as SettingsActivity, true)
|
||||||
}
|
}
|
||||||
"delete_cache" -> {
|
"delete_cache" -> {
|
||||||
val dir = File(context.cacheDir, "imageCache")
|
val dir = File(requireContext().cacheDir, "imageCache")
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_cache_alert_message)
|
setMessage(R.string.settings_clear_cache_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
@@ -101,9 +103,9 @@ class SettingsFragment :
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"delete_downloads" -> {
|
"delete_downloads" -> {
|
||||||
val dir = getDownloadDirectory(context)
|
val dir = getDownloadDirectory(requireContext())
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_downloads_alert_message)
|
setMessage(R.string.settings_clear_downloads_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
@@ -116,9 +118,9 @@ class SettingsFragment :
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"clear_history" -> {
|
"clear_history" -> {
|
||||||
val histories = (context.applicationContext as Pupil).histories
|
val histories = (requireContext().applicationContext as Pupil).histories
|
||||||
|
|
||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_history_alert_message)
|
setMessage(R.string.settings_clear_history_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
@@ -129,10 +131,10 @@ class SettingsFragment :
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"dl_location" -> {
|
"dl_location" -> {
|
||||||
DownloadLocationDialog(activity!!).show()
|
DownloadLocationDialog(requireActivity()).show()
|
||||||
}
|
}
|
||||||
"default_query" -> {
|
"default_query" -> {
|
||||||
DefaultQueryDialog(context).apply {
|
DefaultQueryDialog(requireContext()).apply {
|
||||||
onPositiveButtonClickListener = { newTags ->
|
onPositiveButtonClickListener = { newTags ->
|
||||||
sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
|
||||||
summary = newTags.toString()
|
summary = newTags.toString()
|
||||||
@@ -140,20 +142,23 @@ class SettingsFragment :
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"app_lock" -> {
|
"app_lock" -> {
|
||||||
val intent = Intent(context, LockActivity::class.java)
|
val intent = Intent(requireContext(), LockActivity::class.java)
|
||||||
activity?.startActivityForResult(intent, REQUEST_LOCK)
|
activity?.startActivityForResult(intent, REQUEST_LOCK)
|
||||||
}
|
}
|
||||||
"mirrors" -> {
|
"mirrors" -> {
|
||||||
MirrorDialog(context)
|
MirrorDialog(requireContext())
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
"proxy" -> {
|
"proxy" -> {
|
||||||
ProxyDialog(context)
|
ProxyDialog(requireContext())
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
"nomedia" -> {
|
||||||
|
File(getDownloadDirectory(context), ".nomedia").createNewFile()
|
||||||
|
}
|
||||||
"backup" -> {
|
"backup" -> {
|
||||||
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
|
File(ContextCompat.getDataDir(requireContext()), "favorites.json").copyTo(
|
||||||
File(getDownloadDirectory(context), "favorites.json"),
|
File(getDownloadDirectory(requireContext()), "favorites.json"),
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,6 +173,37 @@ class SettingsFragment :
|
|||||||
|
|
||||||
activity?.startActivityForResult(intent, REQUEST_RESTORE)
|
activity?.startActivityForResult(intent, REQUEST_RESTORE)
|
||||||
}
|
}
|
||||||
|
"old_import_galleries" -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
|
||||||
|
ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_PERMISSION_AND_SAF)
|
||||||
|
else {
|
||||||
|
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||||
|
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
activity?.startActivityForResult(intent, REQUEST_IMPORT_OLD_GALLERIES)
|
||||||
|
}
|
||||||
|
} else { // Can't use SAF on old Androids!
|
||||||
|
val config = DirectoryChooserConfig.builder()
|
||||||
|
.newDirectoryName("Pupil")
|
||||||
|
.allowNewDirectoryNameModification(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val intent = Intent(requireContext(), DirectoryChooserActivity::class.java).apply {
|
||||||
|
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
activity?.startActivityForResult(intent, REQUEST_IMPORT_OLD_GALLERIES_OLD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"user_id" -> {
|
||||||
|
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
|
||||||
|
ClipData.newPlainText("user_id", sharedPreference.getString("user_id", ""))
|
||||||
|
)
|
||||||
|
Toast.makeText(context, R.string.settings_user_id_toast, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,10 +237,10 @@ class SettingsFragment :
|
|||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
"proxy" -> {
|
"proxy" -> {
|
||||||
summary = getProxyInfo(context).type.name
|
summary = getProxyInfo(requireContext()).type.name
|
||||||
}
|
}
|
||||||
"dl_location" -> {
|
"dl_location" -> {
|
||||||
summary = getDownloadDirectory(context!!).canonicalPath
|
summary = getDownloadDirectory(requireContext()).canonicalPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,6 +249,9 @@ class SettingsFragment :
|
|||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||||
|
|
||||||
|
sharedPreference = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
sharedPreference.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
|
||||||
initPreferences()
|
initPreferences()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,42 +268,42 @@ class SettingsFragment :
|
|||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
"app_version" -> {
|
"app_version" -> {
|
||||||
val manager = context.packageManager
|
val manager = requireContext().packageManager
|
||||||
val info = manager.getPackageInfo(context.packageName, 0)
|
val info = manager.getPackageInfo(requireContext().packageName, 0)
|
||||||
summary = context.getString(R.string.settings_app_version_description, info.versionName)
|
summary = requireContext().getString(R.string.settings_app_version_description, info.versionName)
|
||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"delete_cache" -> {
|
"delete_cache" -> {
|
||||||
val dir = File(context.cacheDir, "imageCache")
|
val dir = File(requireContext().cacheDir, "imageCache")
|
||||||
summary = getDirSize(dir)
|
summary = getDirSize(dir)
|
||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"delete_downloads" -> {
|
"delete_downloads" -> {
|
||||||
val dir = getDownloadDirectory(context)
|
val dir = getDownloadDirectory(requireContext())
|
||||||
summary = getDirSize(dir)
|
summary = getDirSize(dir)
|
||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"clear_history" -> {
|
"clear_history" -> {
|
||||||
val histories = (activity!!.application as Pupil).histories
|
val histories = (requireActivity().application as Pupil).histories
|
||||||
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
summary = getString(R.string.settings_clear_history_summary, histories.size)
|
||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"dl_location" -> {
|
"dl_location" -> {
|
||||||
summary = getDownloadDirectory(context).canonicalPath
|
summary = getDownloadDirectory(requireContext()).canonicalPath
|
||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"default_query" -> {
|
"default_query" -> {
|
||||||
summary = PreferenceManager.getDefaultSharedPreferences(context).getString("default_query", "") ?: ""
|
summary = sharedPreference.getString("default_query", "") ?: ""
|
||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"app_lock" -> {
|
"app_lock" -> {
|
||||||
val lockManager = LockManager(context)
|
val lockManager = LockManager(requireContext())
|
||||||
summary =
|
summary =
|
||||||
if (lockManager.locks.isNullOrEmpty()) {
|
if (lockManager.locks.isNullOrEmpty()) {
|
||||||
getString(R.string.settings_lock_none)
|
getString(R.string.settings_lock_none)
|
||||||
@@ -284,19 +323,29 @@ class SettingsFragment :
|
|||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"proxy" -> {
|
"proxy" -> {
|
||||||
summary = getProxyInfo(context).type.name
|
summary = getProxyInfo(requireContext()).type.name
|
||||||
|
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"dark_mode" -> {
|
"dark_mode" -> {
|
||||||
onPreferenceChangeListener = this@SettingsFragment
|
onPreferenceChangeListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
|
"nomedia" -> {
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
"backup" -> {
|
"backup" -> {
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
"restore" -> {
|
"restore" -> {
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
|
"old_import_galleries" -> {
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
|
"user_id" -> {
|
||||||
|
summary = sharedPreference.getString("user_id", "")
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,20 @@ package xyz.quaver.pupil.util
|
|||||||
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
|
import okhttp3.Dispatcher
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import xyz.quaver.proxy
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
const val REQUEST_LOCK = 38238
|
const val REQUEST_LOCK = 38238
|
||||||
const val REQUEST_RESTORE = 16546
|
const val REQUEST_RESTORE = 16546
|
||||||
|
const val REQUEST_IMPORT_OLD_GALLERIES = 6458
|
||||||
|
const val REQUEST_IMPORT_OLD_GALLERIES_OLD = 5946
|
||||||
const val REQUEST_DOWNLOAD_FOLDER = 3874
|
const val REQUEST_DOWNLOAD_FOLDER = 3874
|
||||||
const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425
|
const val REQUEST_DOWNLOAD_FOLDER_OLD = 3425
|
||||||
const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900
|
const val REQUEST_WRITE_PERMISSION_AND_SAF = 13900
|
||||||
|
|
||||||
|
const val NOTIFICATION_ID_UPDATE = 2345
|
||||||
|
|
||||||
val json = Json(JsonConfiguration.Stable)
|
val json = Json(JsonConfiguration.Stable)
|
||||||
@@ -23,27 +23,32 @@ import android.content.ContextWrapper
|
|||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.SparseArray
|
import android.util.SparseArray
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.crashlytics.android.Crashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.io.InputStream
|
|
||||||
import xyz.quaver.Code
|
import xyz.quaver.Code
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.proxy
|
import xyz.quaver.proxy
|
||||||
import xyz.quaver.pupil.util.getCachedGallery
|
import xyz.quaver.pupil.util.getCachedGallery
|
||||||
import xyz.quaver.pupil.util.getDownloadDirectory
|
import xyz.quaver.pupil.util.getDownloadDirectory
|
||||||
|
import xyz.quaver.pupil.util.isParentOf
|
||||||
import xyz.quaver.pupil.util.json
|
import xyz.quaver.pupil.util.json
|
||||||
|
import java.io.BufferedInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.util.*
|
||||||
import java.util.concurrent.locks.Lock
|
import java.util.concurrent.locks.Lock
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
class Cache(context: Context) : ContextWrapper(context) {
|
class Cache(context: Context) : ContextWrapper(context) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val moving = mutableListOf<Int>()
|
||||||
|
private val readers = SparseArray<Reader?>()
|
||||||
|
}
|
||||||
|
|
||||||
private val locks = SparseArray<Lock>()
|
private val locks = SparseArray<Lock>()
|
||||||
private fun lock(galleryID: Int) {
|
private fun lock(galleryID: Int) {
|
||||||
synchronized(locks) {
|
synchronized(locks) {
|
||||||
@@ -83,6 +88,9 @@ class Cache(context: Context) : ContextWrapper(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setCachedMetadata(galleryID: Int, metadata: Metadata) {
|
fun setCachedMetadata(galleryID: Int, metadata: Metadata) {
|
||||||
|
if (preference.getBoolean("cache_disable", false))
|
||||||
|
return
|
||||||
|
|
||||||
val file = File(getCachedGallery(galleryID), ".metadata").also {
|
val file = File(getCachedGallery(galleryID), ".metadata").also {
|
||||||
if (!it.exists())
|
if (!it.exists())
|
||||||
it.createNewFile()
|
it.createNewFile()
|
||||||
@@ -94,6 +102,7 @@ class Cache(context: Context) : ContextWrapper(context) {
|
|||||||
suspend fun getThumbnail(galleryID: Int): String? {
|
suspend fun getThumbnail(galleryID: Int): String? {
|
||||||
val metadata = Cache(this).getCachedMetadata(galleryID)
|
val metadata = Cache(this).getCachedMetadata(galleryID)
|
||||||
|
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
val thumbnail = if (metadata?.thumbnail == null)
|
val thumbnail = if (metadata?.thumbnail == null)
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val thumbnails = getGalleryBlock(galleryID)?.thumbnails
|
val thumbnails = getGalleryBlock(galleryID)?.thumbnails
|
||||||
@@ -154,7 +163,7 @@ class Cache(context: Context) : ContextWrapper(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getReaderOrNull(galleryID: Int): Reader? {
|
fun getReaderOrNull(galleryID: Int): Reader? {
|
||||||
return getCachedMetadata(galleryID)?.reader
|
return readers[galleryID] ?: getCachedMetadata(galleryID)?.reader
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getReader(galleryID: Int): Reader? {
|
suspend fun getReader(galleryID: Int): Reader? {
|
||||||
@@ -175,15 +184,21 @@ class Cache(context: Context) : ContextWrapper(context) {
|
|||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
val reader = if (metadata?.reader == null) {
|
val reader =
|
||||||
CoroutineScope(Dispatchers.IO).async {
|
if (readers[galleryID] != null)
|
||||||
|
return readers[galleryID]
|
||||||
|
else if (metadata?.reader == null) {
|
||||||
var retval: Reader? = null
|
var retval: Reader? = null
|
||||||
|
|
||||||
for (source in sources) {
|
for (source in sources) {
|
||||||
retval = try {
|
retval = try {
|
||||||
source.value.invoke()
|
withContext(Dispatchers.IO) {
|
||||||
|
withTimeoutOrNull(1000) {
|
||||||
|
source.value.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Crashlytics.logException(e)
|
FirebaseCrashlytics.getInstance().recordException(e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,9 +207,10 @@ class Cache(context: Context) : ContextWrapper(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
retval
|
retval
|
||||||
}.await() ?: return null
|
} else
|
||||||
} else
|
metadata.reader
|
||||||
metadata.reader
|
|
||||||
|
readers.put(galleryID, reader)
|
||||||
|
|
||||||
setCachedMetadata(
|
setCachedMetadata(
|
||||||
galleryID,
|
galleryID,
|
||||||
@@ -236,25 +252,54 @@ class Cache(context: Context) : ContextWrapper(context) {
|
|||||||
|
|
||||||
|
|
||||||
fun putImage(galleryID: Int, index: Int, ext: String, data: InputStream) {
|
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 {
|
val cache = File(getCachedGallery(galleryID), "%05d.$ext".format(index)).also {
|
||||||
if (!it.exists())
|
if (!it.exists())
|
||||||
it.createNewFile()
|
it.createNewFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
data.use {
|
try {
|
||||||
it.copyTo(FileOutputStream(cache))
|
BufferedInputStream(data).use { inputStream ->
|
||||||
|
FileOutputStream(cache).use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
cache.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun moveToDownload(galleryID: Int) {
|
fun moveToDownload(galleryID: Int) {
|
||||||
val cache = getCachedGallery(galleryID).also {
|
if (preference.getBoolean("cache_disable", false))
|
||||||
if (!it.exists())
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
val download = File(getDownloadDirectory(this), galleryID.toString())
|
|
||||||
|
|
||||||
cache.copyRecursively(download, true) { _, _ -> OnErrorAction.SKIP }
|
if (moving.contains(galleryID))
|
||||||
cache.deleteRecursively()
|
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 isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.crashlytics.android.Crashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import io.fabric.sdk.android.Fabric
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okio.*
|
import okio.*
|
||||||
@@ -46,11 +45,10 @@ import xyz.quaver.pupil.R
|
|||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class DownloadWorker private constructor(context: Context) : ContextWrapper(context) {
|
class DownloadWorker private constructor(context: Context) : ContextWrapper(context) {
|
||||||
|
|
||||||
private val preferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
private val preferences : SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
@@ -78,7 +76,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
private var bufferedSource : BufferedSource? = null
|
private var bufferedSource : BufferedSource? = null
|
||||||
|
|
||||||
override fun contentLength() = responseBody.contentLength()
|
override fun contentLength() = responseBody.contentLength()
|
||||||
override fun contentType() = responseBody.contentType() ?: null
|
override fun contentType() = responseBody.contentType()
|
||||||
|
|
||||||
override fun source(): BufferedSource {
|
override fun source(): BufferedSource {
|
||||||
if (bufferedSource == null)
|
if (bufferedSource == null)
|
||||||
@@ -130,41 +128,38 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
* SECONDARY VALUE
|
* SECONDARY VALUE
|
||||||
* 0 <= value < 100 -> Download in progress
|
* 0 <= value < 100 -> Download in progress
|
||||||
* Float.POSITIVE_INFINITY -> Download completed
|
* Float.POSITIVE_INFINITY -> Download completed
|
||||||
* Float.NaN -> Exception
|
|
||||||
*/
|
*/
|
||||||
val progress = SparseArray<MutableList<Float>?>()
|
val progress = SparseArray<MutableList<Float>?>()
|
||||||
/*
|
|
||||||
* KEY
|
|
||||||
* primary galleryID
|
|
||||||
* secondary index
|
|
||||||
* PRIMARY VALUE
|
|
||||||
* MutableList -> Download in progress / Loading
|
|
||||||
* null -> Gallery doesn't exist
|
|
||||||
* SECONDARY VALUE
|
|
||||||
* Throwable -> Exception
|
|
||||||
* null -> Download in progress / Loading
|
|
||||||
*/
|
|
||||||
val exception = SparseArray<MutableList<Throwable?>?>()
|
|
||||||
val notification = SparseArray<NotificationCompat.Builder>()
|
val notification = SparseArray<NotificationCompat.Builder>()
|
||||||
|
|
||||||
private val loop = loop()
|
private val loop = loop()
|
||||||
private val worker = SparseArray<Job?>()
|
private val worker = SparseArray<Job?>()
|
||||||
val clients = SparseArray<OkHttpClient>()
|
|
||||||
|
|
||||||
val interceptor = Interceptor { chain ->
|
val interceptor = Interceptor { chain ->
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
val response = chain.proceed(request)
|
var response = chain.proceed(request)
|
||||||
|
|
||||||
|
var retry = 5
|
||||||
|
while (!response.isSuccessful && retry > 0) {
|
||||||
|
response = chain.proceed(request)
|
||||||
|
retry--
|
||||||
|
}
|
||||||
|
|
||||||
response.newBuilder()
|
response.newBuilder()
|
||||||
.body(ProgressResponseBody(request.tag(), response.body(), progressListener))
|
.body(response.body()?.let {
|
||||||
.build()
|
ProgressResponseBody(request.tag(), it, progressListener)
|
||||||
|
}).build()
|
||||||
}
|
}
|
||||||
fun buildClient() =
|
|
||||||
|
val client =
|
||||||
OkHttpClient.Builder()
|
OkHttpClient.Builder()
|
||||||
.addInterceptor(interceptor)
|
|
||||||
.connectTimeout(0, TimeUnit.SECONDS)
|
.connectTimeout(0, TimeUnit.SECONDS)
|
||||||
|
.addInterceptor(interceptor)
|
||||||
.readTimeout(0, TimeUnit.SECONDS)
|
.readTimeout(0, TimeUnit.SECONDS)
|
||||||
.dispatcher(Dispatcher(Executors.newFixedThreadPool(4)))
|
.dispatcher(Dispatcher().apply {
|
||||||
|
maxRequests = 4
|
||||||
|
maxRequestsPerHost = 4
|
||||||
|
})
|
||||||
.proxy(proxy)
|
.proxy(proxy)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@@ -172,20 +167,20 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
queue.clear()
|
queue.clear()
|
||||||
|
|
||||||
loop.cancel()
|
loop.cancel()
|
||||||
for (i in 0..worker.size()) {
|
for (i in 0 until worker.size()) {
|
||||||
val galleryID = worker.keyAt(i)
|
val galleryID = worker.keyAt(i)
|
||||||
|
|
||||||
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
||||||
worker[galleryID]?.cancel()
|
worker[galleryID]?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i in 0 until clients.size()) {
|
client.dispatcher().queuedCalls().filter {
|
||||||
clients.valueAt(i).dispatcher().cancelAll()
|
it.request().tag() is Pair<*, *>
|
||||||
|
}.forEach {
|
||||||
|
it.cancel()
|
||||||
}
|
}
|
||||||
clients.clear()
|
|
||||||
|
|
||||||
progress.clear()
|
progress.clear()
|
||||||
exception.clear()
|
|
||||||
notification.clear()
|
notification.clear()
|
||||||
notificationManager.cancelAll()
|
notificationManager.cancelAll()
|
||||||
}
|
}
|
||||||
@@ -194,20 +189,18 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
queue.remove(galleryID)
|
queue.remove(galleryID)
|
||||||
worker[galleryID]?.cancel()
|
worker[galleryID]?.cancel()
|
||||||
|
|
||||||
clients[galleryID]?.dispatcher()?.cancelAll()
|
client.dispatcher().queuedCalls().filter {
|
||||||
clients.remove(galleryID)
|
((it.request().tag() as Pair<*, *>).first as Int) == galleryID
|
||||||
|
}.forEach {
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
progress.remove(galleryID)
|
progress.remove(galleryID)
|
||||||
exception.remove(galleryID)
|
|
||||||
notification.remove(galleryID)
|
notification.remove(galleryID)
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID)
|
||||||
|
|
||||||
if (progress.indexOfKey(galleryID) >= 0) {
|
|
||||||
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isCompleted(galleryID: Int) = progress[galleryID]?.all { !it.isFinite() } == true
|
fun isCompleted(galleryID: Int) = progress[galleryID]?.all { it.isInfinite() } == true
|
||||||
|
|
||||||
private fun queueDownload(galleryID: Int, reader: Reader, index: Int, callback: Callback) {
|
private fun queueDownload(galleryID: Int, reader: Reader, index: Int, callback: Callback) {
|
||||||
val lowQuality = preferences.getBoolean("low_quality", false)
|
val lowQuality = preferences.getBoolean("low_quality", false)
|
||||||
@@ -219,7 +212,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
imageUrlFromImage(
|
imageUrlFromImage(
|
||||||
galleryID,
|
galleryID,
|
||||||
reader.galleryInfo.files[index],
|
reader.galleryInfo.files[index],
|
||||||
lowQuality
|
!lowQuality
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
addHeader("Referer", getReferer(galleryID))
|
addHeader("Referer", getReferer(galleryID))
|
||||||
@@ -236,10 +229,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
tag(galleryID to index)
|
tag(galleryID to index)
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
if (clients.get(galleryID) == null)
|
client.newCall(request).enqueue(callback)
|
||||||
clients.put(galleryID, buildClient())
|
|
||||||
|
|
||||||
clients[galleryID]?.newCall(request)?.enqueue(callback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
|
private fun download(galleryID: Int) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
@@ -248,7 +238,6 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
//gallery doesn't exist
|
//gallery doesn't exist
|
||||||
if (reader == null) {
|
if (reader == null) {
|
||||||
progress.put(galleryID, null)
|
progress.put(galleryID, null)
|
||||||
exception.put(galleryID, null)
|
|
||||||
|
|
||||||
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
Cache(this@DownloadWorker).setDownloading(galleryID, false)
|
||||||
return@launch
|
return@launch
|
||||||
@@ -262,7 +251,6 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
else
|
else
|
||||||
0F
|
0F
|
||||||
}.toMutableList())
|
}.toMutableList())
|
||||||
exception.put(galleryID, reader.galleryInfo.files.map { null }.toMutableList())
|
|
||||||
|
|
||||||
if (notification[galleryID] == null)
|
if (notification[galleryID] == null)
|
||||||
initNotification(galleryID)
|
initNotification(galleryID)
|
||||||
@@ -284,44 +272,33 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
for (i in reader.galleryInfo.files.indices) {
|
for (i in reader.galleryInfo.files.indices) {
|
||||||
val callback = object : Callback {
|
val callback = object : Callback {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
if (e.message?.contains("cancel", true) != false)
|
||||||
|
return
|
||||||
|
|
||||||
Log.i("PUPILD", "FAIL ${call.request().tag()} (${e.message})")
|
Log.i("PUPILD", "FAIL ${call.request().tag()} (${e.message})")
|
||||||
if (Fabric.isInitialized() && e.message != "Canceled")
|
FirebaseCrashlytics.getInstance().apply {
|
||||||
Crashlytics.logException(e)
|
log("FAIL ${call.request().tag()} (${e.message})")
|
||||||
|
setCustomKey("POS", "FAIL")
|
||||||
progress[galleryID]?.set(i, Float.NaN)
|
recordException(e)
|
||||||
exception[galleryID]?.set(i, e)
|
|
||||||
|
|
||||||
notify(galleryID)
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
if (isCompleted(galleryID) && clients.indexOfKey(galleryID) >= 0) {
|
|
||||||
clients.remove(galleryID)
|
|
||||||
with(Cache(this@DownloadWorker)) {
|
|
||||||
if (isDownloading(galleryID)) {
|
|
||||||
moveToDownload(galleryID)
|
|
||||||
setDownloading(galleryID, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cancel(galleryID)
|
||||||
|
queue.add(galleryID)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
Log.i("PUPILD", "OK ${call.request().tag()}")
|
|
||||||
|
|
||||||
val ext = call.request().url().encodedPath().split('.').last()
|
val ext = call.request().url().encodedPath().split('.').last()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response.body().use {
|
response.body().use {
|
||||||
Cache(this@DownloadWorker).putImage(galleryID, i, ext, it.byteStream())
|
Cache(this@DownloadWorker).putImage(galleryID, i, ext, it!!.byteStream())
|
||||||
}
|
}
|
||||||
progress[galleryID]?.set(i, Float.POSITIVE_INFINITY)
|
progress[galleryID]?.set(i, Float.POSITIVE_INFINITY)
|
||||||
|
|
||||||
notify(galleryID)
|
notify(galleryID)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (isCompleted(galleryID) && clients.indexOfKey(galleryID) >= 0) {
|
if (isCompleted(galleryID)) {
|
||||||
clients.remove(galleryID)
|
|
||||||
with(Cache(this@DownloadWorker)) {
|
with(Cache(this@DownloadWorker)) {
|
||||||
if (isDownloading(galleryID)) {
|
if (isDownloading(galleryID)) {
|
||||||
moveToDownload(galleryID)
|
moveToDownload(galleryID)
|
||||||
@@ -330,48 +307,29 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i("PUPILD", "SUCCESS ${call.request().tag()}")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
FirebaseCrashlytics.getInstance().apply {
|
||||||
progress[galleryID]?.set(i, Float.NaN)
|
log("FAIL ON OK ${call.request().tag()} (${e.message})")
|
||||||
exception[galleryID]?.set(i, e)
|
setCustomKey("POS", "FAIL ON OK")
|
||||||
|
recordException(e)
|
||||||
notify(galleryID)
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
if (isCompleted(galleryID) && clients.indexOfKey(galleryID) >= 0) {
|
|
||||||
clients.remove(galleryID)
|
|
||||||
with(Cache(this@DownloadWorker)) {
|
|
||||||
if (isDownloading(galleryID)) {
|
|
||||||
moveToDownload(galleryID)
|
|
||||||
setDownloading(galleryID, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
File(Cache(this@DownloadWorker).getCachedGallery(galleryID), "%05d.$ext".format(i)).delete()
|
File(Cache(this@DownloadWorker).getCachedGallery(galleryID), "%05d.$ext".format(i)).delete()
|
||||||
|
|
||||||
Log.i("PUPILD", "FAIL ON OK ${call.request().tag()} (${e.message})")
|
cancel(galleryID)
|
||||||
|
queue.add(galleryID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress[galleryID]?.get(i)?.isFinite() == true) {
|
if (progress[galleryID]?.get(i)?.isFinite() == true)
|
||||||
queueDownload(galleryID, reader, i, callback)
|
queueDownload(galleryID, reader, i, callback)
|
||||||
Log.i("PUPILD", "$galleryID QUEUED $i")
|
|
||||||
} else {
|
|
||||||
Log.i("PUPILD", "$galleryID SKIPPED $i (${progress[galleryID]?.get(i)})")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notify(galleryID: Int) {
|
private fun notify(galleryID: Int) {
|
||||||
val max = progress[galleryID]?.size ?: 0
|
val max = progress[galleryID]?.size ?: 0
|
||||||
val progress = progress[galleryID]?.count { !it.isFinite() } ?: 0
|
val progress = progress[galleryID]?.count { it.isInfinite() } ?: 0
|
||||||
|
|
||||||
Log.i("PUPILD", "NOTIFY $galleryID ${isCompleted(galleryID)} $progress/$max")
|
|
||||||
|
|
||||||
if (isCompleted(galleryID)) {
|
if (isCompleted(galleryID)) {
|
||||||
notification[galleryID]
|
notification[galleryID]
|
||||||
@@ -418,8 +376,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
|
|
||||||
val galleryID = queue.peek() ?: continue
|
val galleryID = queue.peek() ?: continue
|
||||||
|
|
||||||
if (clients.indexOfKey(galleryID) >= 0) // Gallery already downloading!
|
if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading!
|
||||||
continue
|
cancel(galleryID)
|
||||||
|
|
||||||
if (notification[galleryID] == null)
|
if (notification[galleryID] == null)
|
||||||
initNotification(galleryID)
|
initNotification(galleryID)
|
||||||
@@ -427,11 +385,6 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
if (Cache(this@DownloadWorker).isDownloading(galleryID))
|
if (Cache(this@DownloadWorker).isDownloading(galleryID))
|
||||||
notificationManager.notify(galleryID, notification[galleryID].build())
|
notificationManager.notify(galleryID, notification[galleryID].build())
|
||||||
|
|
||||||
if (clients.size() >= preferences.getInt("max_download", 4))
|
|
||||||
continue
|
|
||||||
|
|
||||||
Log.i("PUPILD", "QUEUED $galleryID #${clients.size()+1}")
|
|
||||||
|
|
||||||
worker.put(galleryID, download(galleryID))
|
worker.put(galleryID, download(galleryID))
|
||||||
queue.poll()
|
queue.poll()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,4 +211,7 @@ fun Uri.toFile(context: Context): File? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return File(context.getExternalFilesDir(null)?.canonicalPath?.substringBeforeLast("/Android/data") ?: return null, folderName)
|
return File(context.getExternalFilesDir(null)?.canonicalPath?.substringBeforeLast("/Android/data") ?: return null, folderName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun File.isParentOf(another: File) =
|
||||||
|
another.absolutePath.startsWith(this.absolutePath)
|
||||||
@@ -18,13 +18,14 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import kotlinx.serialization.list
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.builtins.list
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class Histories(private val file: File) : ArrayList<Int>() {
|
class Histories(private val file: File) : ArrayList<Int>() {
|
||||||
|
|
||||||
val serializer = Int.serializer().list
|
val serializer: KSerializer<List<Int>> = Int.serializer().list
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
@@ -40,7 +41,7 @@ class Histories(private val file: File) : ArrayList<Int>() {
|
|||||||
fun load() : Histories {
|
fun load() : Histories {
|
||||||
return apply {
|
return apply {
|
||||||
super.clear()
|
super.clear()
|
||||||
addAll(
|
super.addAll(
|
||||||
json.parse(
|
json.parse(
|
||||||
serializer,
|
serializer,
|
||||||
file.bufferedReader().use { it.readText() }
|
file.bufferedReader().use { it.readText() }
|
||||||
@@ -66,6 +67,20 @@ class Histories(private val file: File) : ArrayList<Int>() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun addAll(elements: Collection<Int>): Boolean {
|
||||||
|
load()
|
||||||
|
|
||||||
|
for (e in elements) {
|
||||||
|
if (contains(e))
|
||||||
|
super.remove(e)
|
||||||
|
super.add(0, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
save()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun remove(element: Int): Boolean {
|
override fun remove(element: Int): Boolean {
|
||||||
load()
|
load()
|
||||||
val retval = super.remove(element)
|
val retval = super.remove(element)
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ import android.content.Context
|
|||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.builtins.list
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
|
||||||
import kotlinx.serialization.list
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import android.annotation.SuppressLint
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@UseExperimental(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
fun String.wordCapitalize() : String {
|
fun String.wordCapitalize() : String {
|
||||||
val result = ArrayList<String>()
|
val result = ArrayList<String>()
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ data class ProxyInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun authenticator() = Authenticator { _, response ->
|
fun authenticator() = Authenticator { _, response ->
|
||||||
val credential = Credentials.basic(username, password)
|
val credential = Credentials.basic(username ?: "", password ?: "")
|
||||||
|
|
||||||
response.request().newBuilder()
|
response.request().newBuilder()
|
||||||
.header("Proxy-Authorization", credential)
|
.header("Proxy-Authorization", credential)
|
||||||
|
|||||||
@@ -18,30 +18,39 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.DownloadManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.webkit.MimeTypeMap
|
import android.net.Uri
|
||||||
|
import android.util.Base64
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.FileProvider
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.boolean
|
import kotlinx.serialization.json.boolean
|
||||||
import kotlinx.serialization.json.content
|
import kotlinx.serialization.json.content
|
||||||
|
import okhttp3.*
|
||||||
import ru.noties.markwon.Markwon
|
import ru.noties.markwon.Markwon
|
||||||
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
|
import xyz.quaver.hitomi.Reader
|
||||||
|
import xyz.quaver.hitomi.getGalleryBlock
|
||||||
|
import xyz.quaver.hitomi.getReader
|
||||||
|
import xyz.quaver.proxy
|
||||||
|
import xyz.quaver.pupil.BroadcastReciever
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.util.download.Cache
|
||||||
|
import xyz.quaver.pupil.util.download.Metadata
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
fun getReleases(url: String) : JsonArray {
|
fun getReleases(url: String) : JsonArray {
|
||||||
return try {
|
return try {
|
||||||
@@ -81,7 +90,7 @@ fun getApkUrl(releases: JsonObject) : String? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const val UPDATE_NOTIFICATION_ID = 384823
|
const val UPDATE_NOTIFICATION_ID = 384823
|
||||||
fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
|
fun checkUpdate(context: Context, force: Boolean = false) {
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0)
|
val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0)
|
||||||
@@ -143,56 +152,27 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
|
|||||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||||
|
|
||||||
val notificationManager = NotificationManagerCompat.from(context)
|
val preference = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val builder = NotificationCompat.Builder(context, "download").apply {
|
|
||||||
setContentTitle(context.getString(R.string.update_notification_description))
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
|
||||||
priority = NotificationCompat.PRIORITY_LOW
|
//Cancel any download queued before
|
||||||
setOngoing(true)
|
|
||||||
|
val id = preference.getLong("update_download_id", -1)
|
||||||
|
|
||||||
|
if (id != -1L)
|
||||||
|
downloadManager.remove(id)
|
||||||
|
|
||||||
|
val target = File(context.getExternalFilesDir(null), "Pupil.apk").also {
|
||||||
|
it.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch io@{
|
val request = DownloadManager.Request(Uri.parse(url))
|
||||||
val target = File(getDownloadDirectory(context), "Pupil.apk")
|
.setTitle(context.getText(R.string.update_notification_description))
|
||||||
|
.setDestinationUri(Uri.fromFile(target))
|
||||||
|
|
||||||
try {
|
downloadManager.enqueue(request).also {
|
||||||
URL(url).download(target) { progress, fileSize ->
|
preference.edit().putLong("update_download_id", it).apply()
|
||||||
builder.setProgress(fileSize.toInt(), progress.toInt(), false)
|
|
||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
builder.apply {
|
|
||||||
setContentText(context.getString(R.string.update_failed))
|
|
||||||
setMessage(context.getString(R.string.update_failed_message))
|
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
|
||||||
setOngoing(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
|
||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
|
|
||||||
|
|
||||||
return@io
|
|
||||||
}
|
|
||||||
|
|
||||||
val install = Intent(Intent.ACTION_VIEW).apply {
|
|
||||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
setDataAndType(FileProvider.getUriForFile(context, context.applicationContext.packageName + ".provider", target), MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.apply {
|
|
||||||
setContentIntent(PendingIntent.getActivity(context, 0, install, 0))
|
|
||||||
setProgress(0, 0, false)
|
|
||||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
|
||||||
setContentTitle(context.getString(R.string.update_download_completed))
|
|
||||||
setContentText(context.getString(R.string.update_download_completed_description))
|
|
||||||
setOngoing(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
|
||||||
|
|
||||||
if (context.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED))
|
|
||||||
context.startActivity(install)
|
|
||||||
else
|
|
||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNegativeButton(if (force) android.R.string.no else R.string.ignore_update) { _, _ ->
|
setNegativeButton(if (force) android.R.string.no else R.string.ignore_update) { _, _ ->
|
||||||
@@ -207,4 +187,149 @@ fun checkUpdate(context: AppCompatActivity, force: Boolean = false) {
|
|||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cancelImport = false
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
fun importOldGalleries(context: Context, folder: File) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val client = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(0, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(0, TimeUnit.SECONDS)
|
||||||
|
.proxy(proxy)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val cancelIntent = Intent(context, BroadcastReciever::class.java).apply {
|
||||||
|
action = BroadcastReciever.ACTION_CANCEL_IMPORT
|
||||||
|
putExtra(BroadcastReciever.EXTRA_IMPORT_NOTIFICATION_ID, 0)
|
||||||
|
}
|
||||||
|
val pendingIntent = PendingIntent.getBroadcast(context, 0, cancelIntent, 0)
|
||||||
|
|
||||||
|
val notificationManager = NotificationManagerCompat.from(context)
|
||||||
|
val notificationBuilder = NotificationCompat.Builder(context, "import").apply {
|
||||||
|
setContentTitle(context.getText(R.string.import_old_galleries_notification))
|
||||||
|
setProgress(0, 0, true)
|
||||||
|
setSmallIcon(R.drawable.ic_notification)
|
||||||
|
addAction(0, context.getText(android.R.string.cancel), pendingIntent)
|
||||||
|
setOngoing(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.notify(0, notificationBuilder.build())
|
||||||
|
|
||||||
|
if (!folder.isDirectory)
|
||||||
|
return@launch
|
||||||
|
|
||||||
|
val galleryRegex = Regex("""[0-9]+$""")
|
||||||
|
val imageRegex = Regex("""^[0-9]+\..+$""")
|
||||||
|
var size = 0
|
||||||
|
fun setProgress(progress: Int) {
|
||||||
|
notificationBuilder.apply {
|
||||||
|
setContentText(
|
||||||
|
context.getString(
|
||||||
|
R.string.import_old_galleries_notification_text,
|
||||||
|
progress,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setProgress(size, progress, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager.notify(0, notificationBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
folder.listFiles { _, name ->
|
||||||
|
galleryRegex.matches(name)
|
||||||
|
}?.also {
|
||||||
|
size = it.size
|
||||||
|
setProgress(0)
|
||||||
|
}?.forEachIndexed { index, gallery ->
|
||||||
|
if (cancelImport)
|
||||||
|
return@forEachIndexed
|
||||||
|
|
||||||
|
setProgress(index)
|
||||||
|
|
||||||
|
val galleryID = gallery.name.toIntOrNull() ?: return@forEachIndexed
|
||||||
|
|
||||||
|
File(getDownloadDirectory(context), galleryID.toString()).mkdirs()
|
||||||
|
|
||||||
|
val reader = async {
|
||||||
|
kotlin.runCatching {
|
||||||
|
json.parse(Reader.serializer(), File(gallery, "reader.json").readText())
|
||||||
|
}.getOrElse {
|
||||||
|
getReader(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val galleryBlock = async {
|
||||||
|
kotlin.runCatching {
|
||||||
|
json.parse(GalleryBlock.serializer(), File(gallery, "galleryBlock.json").readText())
|
||||||
|
}.getOrElse {
|
||||||
|
getGalleryBlock(galleryID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
val thumbnail = async thumbnail@{
|
||||||
|
val galleryBlock = galleryBlock.await()
|
||||||
|
|
||||||
|
Base64.encodeToString(try {
|
||||||
|
File(gallery, "thumbnail.jpg").readBytes()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val url = galleryBlock?.thumbnails?.firstOrNull()
|
||||||
|
|
||||||
|
if (url == null)
|
||||||
|
null
|
||||||
|
else {
|
||||||
|
val request = Request.Builder().url(url).build()
|
||||||
|
|
||||||
|
var done = false
|
||||||
|
var result: ByteArray? = null
|
||||||
|
client.newCall(request).enqueue(object : Callback {
|
||||||
|
override fun onFailure(call: Call?, e: IOException?) {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call?, response: Response?) {
|
||||||
|
result = response?.body()?.use {
|
||||||
|
it.bytes()
|
||||||
|
}
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!done)
|
||||||
|
yield()
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
} ?: return@thumbnail null, Base64.DEFAULT)
|
||||||
|
}
|
||||||
|
|
||||||
|
Cache(context).setCachedMetadata(galleryID,
|
||||||
|
Metadata(
|
||||||
|
thumbnail.await(),
|
||||||
|
galleryBlock.await(),
|
||||||
|
reader.await()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
File(gallery, "images").listFiles { _, name ->
|
||||||
|
imageRegex.matches(name)
|
||||||
|
}?.forEach {
|
||||||
|
if (cancelImport)
|
||||||
|
return@forEach
|
||||||
|
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
val index = it.nameWithoutExtension.toIntOrNull() ?: return@forEach
|
||||||
|
|
||||||
|
Cache(context).putImage(galleryID, index, it.extension, it.inputStream())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationBuilder.apply {
|
||||||
|
setContentText(context.getText(R.string.import_old_galleries_notification_done))
|
||||||
|
setProgress(0, 0, false)
|
||||||
|
setOngoing(false)
|
||||||
|
mActions.clear()
|
||||||
|
}
|
||||||
|
notificationManager.notify(0, notificationBuilder.build())
|
||||||
|
|
||||||
|
cancelImport = false
|
||||||
}
|
}
|
||||||
24
app/src/main/res/anim/shake.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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:interpolator="@anim/shake_cycle"
|
||||||
|
android:toXDelta="10" />
|
||||||
21
app/src/main/res/anim/shake_cycle.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:cycles="3" />
|
||||||
23
app/src/main/res/color/lock_fab.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_enabled="false" android:color="@android:color/darker_gray"/>
|
||||||
|
<item android:color="@color/colorPrimary"/>
|
||||||
|
</selector>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 620 B |
|
Before Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-hdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 255 B |
|
Before Width: | Height: | Size: 965 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 802 B |
|
Before Width: | Height: | Size: 495 B |
|
Before Width: | Height: | Size: 639 B |
|
Before Width: | Height: | Size: 733 B |
|
Before Width: | Height: | Size: 817 B |
|
Before Width: | Height: | Size: 670 B |
|
Before Width: | Height: | Size: 934 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 979 B |
|
Before Width: | Height: | Size: 636 B |
|
Before Width: | Height: | Size: 760 B |
|
Before Width: | Height: | Size: 947 B |
|
Before Width: | Height: | Size: 1001 B |
BIN
app/src/main/res/drawable-mdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 848 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 892 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 948 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_notification.png
Normal file
|
After Width: | Height: | Size: 605 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
8
app/src/main/res/drawable/account_group.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/account_group.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M12,5.5A3.5,3.5 0 0,1 15.5,9A3.5,3.5 0 0,1 12,12.5A3.5,3.5 0 0,1 8.5,9A3.5,3.5 0 0,1 12,5.5M5,8C5.56,8 6.08,8.15 6.53,8.42C6.38,9.85 6.8,11.27 7.66,12.38C7.16,13.34 6.16,14 5,14A3,3 0 0,1 2,11A3,3 0 0,1 5,8M19,8A3,3 0 0,1 22,11A3,3 0 0,1 19,14C17.84,14 16.84,13.34 16.34,12.38C17.2,11.27 17.62,9.85 17.47,8.42C17.92,8.15 18.44,8 19,8M5.5,18.25C5.5,16.18 8.41,14.5 12,14.5C15.59,14.5 18.5,16.18 18.5,18.25V20H5.5V18.25M0,20V18.5C0,17.11 1.89,15.94 4.45,15.6C3.86,16.28 3.5,17.22 3.5,18.25V20H0M24,20H20.5V18.25C20.5,17.22 20.14,16.28 19.55,15.6C22.11,15.94 24,17.11 24,18.5V20Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/account_star.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/account_star.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12M5,13.28L7.45,14.77L6.8,11.96L9,10.08L6.11,9.83L5,7.19L3.87,9.83L1,10.08L3.18,11.96L2.5,14.77L5,13.28Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/backspace_outline.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/backspace_outline.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#000" android:pathData="M19,15.59L17.59,17L14,13.41L10.41,17L9,15.59L12.59,12L9,8.41L10.41,7L14,10.59L17.59,7L19,8.41L15.41,12L19,15.59M22,3A2,2 0 0,1 24,5V19A2,2 0 0,1 22,21H7C6.31,21 5.77,20.64 5.41,20.11L0,12L5.41,3.88C5.77,3.35 6.31,3 7,3H22M22,5H7L2.28,12L7,19H22V5Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/book_open.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/book_open.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M13,12H20V13.5H13M13,9.5H20V11H13M13,14.5H20V16H13M21,4H3A2,2 0 0,0 1,6V19A2,2 0 0,0 3,21H21A2,2 0 0,0 23,19V6A2,2 0 0,0 21,4M21,19H12V6H21" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/brush.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/brush.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M20.71,4.63L19.37,3.29C19,2.9 18.35,2.9 17.96,3.29L9,12.25L11.75,15L20.71,6.04C21.1,5.65 21.1,5 20.71,4.63M7,14A3,3 0 0,0 4,17C4,18.31 2.84,19 2,19C2.92,20.22 4.5,21 6,21A4,4 0 0,0 10,17A3,3 0 0,0 7,14Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/cancel.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/cancel.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#fff" android:pathData="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,13.85 4.63,15.55 5.68,16.91L16.91,5.68C15.55,4.63 13.85,4 12,4M12,20A8,8 0 0,0 20,12C20,10.15 19.37,8.45 18.32,7.09L7.09,18.32C8.45,19.37 10.15,20 12,20Z" />
|
||||||
|
</vector>
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
<!-- drawable/fingerprint.xml -->
|
<!-- drawable/fingerprint.xml -->
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path android:fillColor="#000" android:pathData="M17.81,4.47C17.73,4.47 17.65,4.45 17.58,4.41C15.66,3.42 14,3 12,3C10.03,3 8.15,3.47 6.44,4.41C6.2,4.54 5.9,4.45 5.76,4.21C5.63,3.97 5.72,3.66 5.96,3.53C7.82,2.5 9.86,2 12,2C14.14,2 16,2.47 18.04,3.5C18.29,3.65 18.38,3.95 18.25,4.19C18.16,4.37 18,4.47 17.81,4.47M3.5,9.72C3.4,9.72 3.3,9.69 3.21,9.63C3,9.47 2.93,9.16 3.09,8.93C4.08,7.53 5.34,6.43 6.84,5.66C10,4.04 14,4.03 17.15,5.65C18.65,6.42 19.91,7.5 20.9,8.9C21.06,9.12 21,9.44 20.78,9.6C20.55,9.76 20.24,9.71 20.08,9.5C19.18,8.22 18.04,7.23 16.69,6.54C13.82,5.07 10.15,5.07 7.29,6.55C5.93,7.25 4.79,8.25 3.89,9.5C3.81,9.65 3.66,9.72 3.5,9.72M9.75,21.79C9.62,21.79 9.5,21.74 9.4,21.64C8.53,20.77 8.06,20.21 7.39,19C6.7,17.77 6.34,16.27 6.34,14.66C6.34,11.69 8.88,9.27 12,9.27C15.12,9.27 17.66,11.69 17.66,14.66A0.5,0.5 0 0,1 17.16,15.16A0.5,0.5 0 0,1 16.66,14.66C16.66,12.24 14.57,10.27 12,10.27C9.43,10.27 7.34,12.24 7.34,14.66C7.34,16.1 7.66,17.43 8.27,18.5C8.91,19.66 9.35,20.15 10.12,20.93C10.31,21.13 10.31,21.44 10.12,21.64C10,21.74 9.88,21.79 9.75,21.79M16.92,19.94C15.73,19.94 14.68,19.64 13.82,19.05C12.33,18.04 11.44,16.4 11.44,14.66A0.5,0.5 0 0,1 11.94,14.16A0.5,0.5 0 0,1 12.44,14.66C12.44,16.07 13.16,17.4 14.38,18.22C15.09,18.7 15.92,18.93 16.92,18.93C17.16,18.93 17.56,18.9 17.96,18.83C18.23,18.78 18.5,18.96 18.54,19.24C18.59,19.5 18.41,19.77 18.13,19.82C17.56,19.93 17.06,19.94 16.92,19.94M14.91,22C14.87,22 14.82,22 14.78,22C13.19,21.54 12.15,20.95 11.06,19.88C9.66,18.5 8.89,16.64 8.89,14.66C8.89,13.04 10.27,11.72 11.97,11.72C13.67,11.72 15.05,13.04 15.05,14.66C15.05,15.73 16,16.6 17.13,16.6C18.28,16.6 19.21,15.73 19.21,14.66C19.21,10.89 15.96,7.83 11.96,7.83C9.12,7.83 6.5,9.41 5.35,11.86C4.96,12.67 4.76,13.62 4.76,14.66C4.76,15.44 4.83,16.67 5.43,18.27C5.53,18.53 5.4,18.82 5.14,18.91C4.88,19 4.59,18.87 4.5,18.62C4,17.31 3.77,16 3.77,14.66C3.77,13.46 4,12.37 4.45,11.42C5.78,8.63 8.73,6.82 11.96,6.82C16.5,6.82 20.21,10.33 20.21,14.65C20.21,16.27 18.83,17.59 17.13,17.59C15.43,17.59 14.05,16.27 14.05,14.65C14.05,13.58 13.12,12.71 11.97,12.71C10.82,12.71 9.89,13.58 9.89,14.65C9.89,16.36 10.55,17.96 11.76,19.16C12.71,20.1 13.62,20.62 15.03,21C15.3,21.08 15.45,21.36 15.38,21.62C15.33,21.85 15.12,22 14.91,22Z" />
|
<path android:fillColor="#fff" android:pathData="M17.81,4.47C17.73,4.47 17.65,4.45 17.58,4.41C15.66,3.42 14,3 12,3C10.03,3 8.15,3.47 6.44,4.41C6.2,4.54 5.9,4.45 5.76,4.21C5.63,3.97 5.72,3.66 5.96,3.53C7.82,2.5 9.86,2 12,2C14.14,2 16,2.47 18.04,3.5C18.29,3.65 18.38,3.95 18.25,4.19C18.16,4.37 18,4.47 17.81,4.47M3.5,9.72C3.4,9.72 3.3,9.69 3.21,9.63C3,9.47 2.93,9.16 3.09,8.93C4.08,7.53 5.34,6.43 6.84,5.66C10,4.04 14,4.03 17.15,5.65C18.65,6.42 19.91,7.5 20.9,8.9C21.06,9.12 21,9.44 20.78,9.6C20.55,9.76 20.24,9.71 20.08,9.5C19.18,8.22 18.04,7.23 16.69,6.54C13.82,5.07 10.15,5.07 7.29,6.55C5.93,7.25 4.79,8.25 3.89,9.5C3.81,9.65 3.66,9.72 3.5,9.72M9.75,21.79C9.62,21.79 9.5,21.74 9.4,21.64C8.53,20.77 8.06,20.21 7.39,19C6.7,17.77 6.34,16.27 6.34,14.66C6.34,11.69 8.88,9.27 12,9.27C15.12,9.27 17.66,11.69 17.66,14.66A0.5,0.5 0 0,1 17.16,15.16A0.5,0.5 0 0,1 16.66,14.66C16.66,12.24 14.57,10.27 12,10.27C9.43,10.27 7.34,12.24 7.34,14.66C7.34,16.1 7.66,17.43 8.27,18.5C8.91,19.66 9.35,20.15 10.12,20.93C10.31,21.13 10.31,21.44 10.12,21.64C10,21.74 9.88,21.79 9.75,21.79M16.92,19.94C15.73,19.94 14.68,19.64 13.82,19.05C12.33,18.04 11.44,16.4 11.44,14.66A0.5,0.5 0 0,1 11.94,14.16A0.5,0.5 0 0,1 12.44,14.66C12.44,16.07 13.16,17.4 14.38,18.22C15.09,18.7 15.92,18.93 16.92,18.93C17.16,18.93 17.56,18.9 17.96,18.83C18.23,18.78 18.5,18.96 18.54,19.24C18.59,19.5 18.41,19.77 18.13,19.82C17.56,19.93 17.06,19.94 16.92,19.94M14.91,22C14.87,22 14.82,22 14.78,22C13.19,21.54 12.15,20.95 11.06,19.88C9.66,18.5 8.89,16.64 8.89,14.66C8.89,13.04 10.27,11.72 11.97,11.72C13.67,11.72 15.05,13.04 15.05,14.66C15.05,15.73 16,16.6 17.13,16.6C18.28,16.6 19.21,15.73 19.21,14.66C19.21,10.89 15.96,7.83 11.96,7.83C9.12,7.83 6.5,9.41 5.35,11.86C4.96,12.67 4.76,13.62 4.76,14.66C4.76,15.44 4.83,16.67 5.43,18.27C5.53,18.53 5.4,18.82 5.14,18.91C4.88,19 4.59,18.87 4.5,18.62C4,17.31 3.77,16 3.77,14.66C3.77,13.46 4,12.37 4.45,11.42C5.78,8.63 8.73,6.82 11.96,6.82C16.5,6.82 20.21,10.33 20.21,14.65C20.21,16.27 18.83,17.59 17.13,17.59C15.43,17.59 14.05,16.27 14.05,14.65C14.05,13.58 13.12,12.71 11.97,12.71C10.82,12.71 9.89,13.58 9.89,14.65C9.89,16.36 10.55,17.96 11.76,19.16C12.71,20.1 13.62,20.62 15.03,21C15.3,21.08 15.45,21.36 15.38,21.62C15.33,21.85 15.12,22 14.91,22Z"
|
||||||
|
tools:ignore="VectorPath" />
|
||||||
</vector>
|
</vector>
|
||||||
8
app/src/main/res/drawable/gender_female.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/gender_female.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M12,4A6,6 0 0,1 18,10C18,12.97 15.84,15.44 13,15.92V18H15V20H13V22H11V20H9V18H11V15.92C8.16,15.44 6,12.97 6,10A6,6 0 0,1 12,4M12,6A4,4 0 0,0 8,10A4,4 0 0,0 12,14A4,4 0 0,0 16,10A4,4 0 0,0 12,6Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/gender_male.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/gender_male.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M9,9C10.29,9 11.5,9.41 12.47,10.11L17.58,5H13V3H21V11H19V6.41L13.89,11.5C14.59,12.5 15,13.7 15,15A6,6 0 0,1 9,21A6,6 0 0,1 3,15A6,6 0 0,1 9,9M9,11A4,4 0 0,0 5,15A4,4 0 0,0 9,19A4,4 0 0,0 13,15A4,4 0 0,0 9,11Z" />
|
||||||
|
</vector>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<!-- drawable/gender_female.xml -->
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path android:fillColor="#fff" android:pathData="M12,4A6,6 0 0,1 18,10C18,12.97 15.84,15.44 13,15.92V18H15V20H13V22H11V20H9V18H11V15.92C8.16,15.44 6,12.97 6,10A6,6 0 0,1 12,4M12,6A4,4 0 0,0 8,10A4,4 0 0,0 12,14A4,4 0 0,0 16,10A4,4 0 0,0 12,6Z" />
|
|
||||||
</vector>
|
|
||||||