Compare commits

...

80 Commits

Author SHA1 Message Date
tom5079
48ff2f328f search bug fix 2020-10-24 23:55:50 +09:00
tom5079
9ae2423a40 search bug fix 2020-10-24 23:05:11 +09:00
tom5079
2bc3c78c75 search bug fix 2020-10-24 23:04:49 +09:00
tom5079
18e9fe75fb hiyobi.me fix 2020-10-24 11:48:14 +09:00
tom5079
880a741a44 hiyobi.me fix 2020-10-24 11:25:16 +09:00
tom5079
2c6ddcc64b hitomi.la image not loading fix 2020-10-21 14:42:07 +09:00
tom5079
8f2e757b77 Update README.md 2020-10-15 15:53:30 +09:00
tom5079
ff177955b3 Update README.md 2020-10-15 15:49:07 +09:00
tom5079
8bb8066a98 Apk built 2020-10-15 14:37:13 +09:00
tom5079
2747ddbf65 Adjust gallery_id margin 2020-10-15 14:32:36 +09:00
tom5079
b939e9424d Translate tag by default 2020-10-15 14:29:22 +09:00
tom5079
fb9dea5d1e Copy galleryID by clicking galleryblock_id 2020-10-15 12:52:38 +09:00
tom5079
da4d5d711b Prefetch
Resolves #109
2020-10-15 10:20:36 +09:00
tom5079
331cbec5f1 Bug fix 2020-10-14 18:36:36 +09:00
tom5079
7f02284285 Update README.md 2020-10-14 00:26:06 +09:00
tom5079
ac2c3a6d97 Merge remote-tracking branch 'origin/master' into master 2020-10-14 00:25:44 +09:00
tom5079
c3bc80fec6 Bug fix 2020-10-14 00:24:38 +09:00
tom5079
09779a0710 Update README.md 2020-10-13 23:47:56 +09:00
tom5079
e82c6ef866 App built
Possible build time optimization
2020-10-13 23:40:53 +09:00
tom5079
861ae9be64 Merge remote-tracking branch 'origin/dev' into dev 2020-10-13 23:34:28 +09:00
tom5079
96108bc1ec Improves Scroll Jitter 2020-10-13 23:34:16 +09:00
tom5079
016f217db0 Merge pull request #108 from klx7007/patch-1
Fix FloatingSearchView imeOptions to only affect keyboard visibility
2020-10-13 23:05:34 +09:00
tom5079
0688294f18 Dependency update
Support non external storage document Uris

Support non external storage document Uris
2020-10-13 22:59:29 +09:00
klx7007
9ad008255d FloatingSearchView imeOptions 수정
imeOption을 덮어씌워서 search할때 키보드만 숨겨짐
2020-10-13 22:58:13 +09:00
tom5079
4c5a862dd6 Update README.md 2020-10-13 17:57:19 +09:00
tom5079
b165a2308f Merge remote-tracking branch 'origin/master' into master 2020-10-13 17:13:10 +09:00
tom5079
8757b08cd2 Fixed pagecount not showing up 2020-10-13 17:12:53 +09:00
tom5079
3800543fba Update README.md 2020-10-13 13:59:02 +09:00
tom5079
02ef60c818 Update README.md 2020-10-13 13:57:23 +09:00
tom5079
88f3b30266 Merge branch 'dev' into master 2020-10-13 13:52:34 +09:00
tom5079
9203dc0112 Tag translation 2020-10-13 13:51:53 +09:00
tom5079
4c683bec68 Dependency update
Fixes concurrentmodificationexception
2020-10-13 08:34:04 +09:00
tom5079
0cfd1eb453 Update README.md 2020-10-04 23:05:30 +09:00
tom5079
19744dab37 Merge remote-tracking branch 'origin/master' into master 2020-10-04 23:04:30 +09:00
tom5079
12d58e5aa7 Don't cancel download onPause 2020-10-04 23:04:12 +09:00
tom5079
e46dd37a26 Update README.md 2020-10-04 22:47:45 +09:00
tom5079
49c3ebc36b Concurrency issue fixed
Don't cancel download onPause
Limit folder length to 127 characters
2020-10-04 22:44:31 +09:00
tom5079
11e9bc2235 Added five entries per page option 2020-10-04 20:39:33 +09:00
tom5079
3029b3bf0e Update README.md 2020-10-03 20:20:41 +09:00
tom5079
9a6c6f67ce Merge branch 'dev' into master 2020-10-03 20:18:53 +09:00
tom5079
a6ed0baef2 Fix auto cache cleanup 2020-10-03 20:18:20 +09:00
tom5079
d3b43d80da Update README.md 2020-10-03 10:06:17 +09:00
tom5079
46d4316d49 Merge remote-tracking branch 'origin/master' into master 2020-10-03 10:05:30 +09:00
tom5079
ade2864351 Fix auto cache cleanup 2020-10-03 10:03:57 +09:00
tom5079
365fc56e9d Update README.md 2020-10-02 13:09:46 +09:00
tom5079
54a5cd21ea Merge branch 'dev' into master 2020-10-02 13:00:02 +09:00
tom5079
38c0399b09 App built 2020-10-02 12:59:32 +09:00
tom5079
2b67858453 Auto cache clean 2020-10-02 12:51:59 +09:00
tom5079
87fdbdbb6e Open GalleryDialog first instead of opening Reader directly 2020-10-02 00:19:56 +09:00
tom5079
bab77a4116 (KO) Added support link 2020-10-02 00:13:34 +09:00
tom5079
d20756ab96 Reset security mode 2020-10-01 22:51:52 +09:00
tom5079
dc75a753c3 Minimum thumbnail height 2020-10-01 22:46:03 +09:00
tom5079
4712d47903 Show 10 tags maximum 2020-10-01 22:20:49 +09:00
tom5079
c5561801e1 Add group name to GalleryBlock 2020-10-01 21:32:42 +09:00
tom5079
5c259fa07a Dependency update
Fixed duplicated download file
Better download progress update handling

TODO: Add group name to GalleryBlock
2020-10-01 21:24:32 +09:00
tom5079
60e8b18702 Update README.md 2020-09-29 16:53:15 +09:00
tom5079
a8317824a9 Merge remote-tracking branch 'origin/master' into master 2020-09-27 21:40:32 +09:00
tom5079
0c3c78cc72 Fixed app crashing when loading thumbnail 2020-09-27 21:40:22 +09:00
tom5079
cfd4a8faac Update README.md 2020-09-27 21:39:05 +09:00
tom5079
7f3fb0db0d Update README.md 2020-09-27 20:30:47 +09:00
tom5079
385d3f0d1b Update README.md 2020-09-27 20:29:50 +09:00
tom5079
8fa6bd12a2 Update README.md 2020-09-27 20:27:29 +09:00
tom5079
57c2004e46 Update README.md 2020-09-27 20:21:45 +09:00
tom5079
c6b069bbfb Update README.md 2020-09-27 20:19:53 +09:00
tom5079
c18bffd08f Fixed app crashing when thumbnail is null 2020-09-27 20:18:04 +09:00
tom5079
47ec181439 Fixed app crashing when thumbnail is null 2020-09-27 20:15:43 +09:00
tom5079
90ad40b1b7 Update README.md 2020-09-27 19:32:18 +09:00
tom5079
4d3f20cf98 Update README.md 2020-09-27 19:31:30 +09:00
tom5079
86df9d52bc Update README.md 2020-09-27 19:15:46 +09:00
tom5079
1bd025e070 Fixed ProxyDialog not showing up 2020-09-27 15:09:19 +09:00
tom5079
86ee239c71 App built 2020-09-27 14:39:47 +09:00
tom5079
27d0c01e1f Don't refresh onResume 2020-09-27 14:37:16 +09:00
tom5079
7a9507be01 Somewhat working 2020-09-27 14:29:02 +09:00
tom5079
1490035893 Does not work 2020-09-27 10:04:26 +09:00
tom5079
a6afcb0ed0 Consistent usage of quotation marks 2020-09-26 22:41:51 +09:00
tom5079
ea7e8584cb Consistent usage of quotation marks 2020-09-26 22:36:48 +09:00
tom5079
608c6e6a1d App built 2020-09-26 21:01:36 +09:00
tom5079
bb2c91145f Dependency update 2020-09-26 20:58:46 +09:00
tom5079
db074df0f7 Fixed Download Concurrency issue
Fixed image not showing up after reader is paused and resumed
2020-09-26 11:07:35 +09:00
tom5079
f7c45df9a6 Tag favorite bug fix 2020-09-26 09:36:20 +09:00
54 changed files with 1159 additions and 6454 deletions

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" />
</component>
</project>

1
.idea/gradle.xml generated
View File

@@ -14,6 +14,7 @@
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintNewerVersionAvailable" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -66,5 +66,10 @@
<option name="name" value="maven3" /> <option name="name" value="maven3" />
<option name="url" value="https://dl.bintray.com/tom5079/maven" /> <option name="url" value="https://dl.bintray.com/tom5079/maven" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="maven3" />
<option name="name" value="maven3" />
<option name="url" value="http://dl.bintray.com/piasy/maven" />
</remote-repository>
</component> </component>
</project> </project>

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -1,18 +1,12 @@
# Pupil
![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true) ![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true)
*Pupil, Hitomi.la viewer for Android* *Pupil, Hitomi.la viewer for Android*
![](https://img.shields.io/github/downloads/tom5079/Pupil/total)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/5.1.6-hotfix6/Pupil-v5.1.6-hotfix6.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.1.6-hotfix6/Pupil-v5.1.6-hotfix6.apk)
[![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v) [![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v)
# Screenshot # Features
![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.png?raw=true) ![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.jpg?raw=true)
*Main Screen*
![Reader Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/reader-screenshot.png?raw=true)
*Reader Screen*
Images are censored to be SFW
# Installation # Installation
@@ -27,3 +21,6 @@ or Build app yourself
# Contribution # Contribution
Any kind of contribution is appriciated. Feel free to leave PR! Any kind of contribution is appriciated. Feel free to leave PR!
## Tag Translation
Head over to [tags branch](https://github.com/tom5079/Pupil/tree/tags)

View File

@@ -1,27 +1,44 @@
apply plugin: 'com.android.application' apply plugin: "com.android.application"
apply plugin: 'kotlin-android' apply plugin: "kotlin-android"
apply plugin: 'kotlin-kapt' apply plugin: "kotlin-kapt"
apply plugin: 'kotlin-android-extensions' apply plugin: "kotlin-android-extensions"
apply plugin: 'kotlinx-serialization' apply plugin: "kotlinx-serialization"
apply plugin: 'com.google.android.gms.oss-licenses-plugin' apply plugin: "com.google.android.gms.oss-licenses-plugin"
if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) { if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
logger.lifecycle("Firebase Enabled") logger.lifecycle("Firebase Enabled")
apply plugin: 'com.google.gms.google-services' apply plugin: "com.google.gms.google-services"
apply plugin: 'com.google.firebase.crashlytics' apply plugin: "com.google.firebase.crashlytics"
apply plugin: 'com.google.firebase.firebase-perf' apply plugin: "com.google.firebase.firebase-perf"
} else { } else {
logger.lifecycle("Firebase Disabled") logger.lifecycle("Firebase Disabled")
} }
ext {
okhttp_version = "3.12.12"
}
configurations {
all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
if (details.requested.group == "com.squareup.okhttp3" && details.requested.name == "okhttp") {
// OkHttp drops support before 5.0 since 3.13.0
details.useVersion okhttp_version
}
}
}
}
}
android { android {
compileSdkVersion 30 compileSdkVersion 30
defaultConfig { defaultConfig {
applicationId "xyz.quaver.pupil" applicationId "xyz.quaver.pupil"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 60 versionCode 63
versionName "5.1" versionName "5.1.6-hotfix6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
@@ -34,8 +51,7 @@ android {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
versionNameSuffix "-DEBUG" versionNameSuffix "-DEBUG"
buildConfigField('Boolean', 'CENSOR', 'false') proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
ext.enableCrashlytics = false ext.enableCrashlytics = false
ext.alwaysUpdateBuildId = false ext.alwaysUpdateBuildId = false
@@ -44,76 +60,78 @@ android {
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
buildConfigField('Boolean', 'CENSOR', 'false') proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }
kotlinOptions { kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString() jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental' freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental"
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
buildToolsVersion = '29.0.3' buildToolsVersion = "29.0.3"
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0-M1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0-RC2"
implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.activity:activity-ktx:1.2.0-alpha08" implementation "androidx.activity:activity-ktx:1.2.0-beta01"
implementation 'androidx.fragment:fragment-ktx:1.3.0-alpha08' implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
implementation 'androidx.preference:preference:1.1.1' implementation "androidx.preference:preference-ktx:1.1.1"
implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation "androidx.constraintlayout:constraintlayout:2.0.2"
implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.biometric:biometric:1.0.1" implementation "androidx.biometric:biometric:1.0.1"
implementation "androidx.work:work-runtime-ktx:2.4.0"
implementation "com.daimajia.swipelayout:library:1.2.0@aar" implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'com.google.firebase:firebase-core:17.5.0' implementation "com.google.android.material:material:1.3.0-alpha03"
implementation 'com.google.firebase:firebase-analytics:17.5.0'
implementation 'com.google.firebase:firebase-crashlytics:17.2.1' implementation "com.google.firebase:firebase-core:17.5.1"
implementation 'com.google.firebase:firebase-perf:19.0.8' implementation "com.google.firebase:firebase-analytics:17.6.0"
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0' implementation "com.google.firebase:firebase-crashlytics:17.2.2"
implementation 'com.google.android.gms:play-services-mlkit-face-detection:16.1.1' implementation "com.google.firebase:firebase-perf:19.0.9"
implementation 'com.github.clans:fab:1.6.4'
//implementation 'com.quiph.ui:recyclerviewfastscroller:0.2.1' implementation "com.google.android.gms:play-services-oss-licenses:17.0.0"
implementation "com.google.android.gms:play-services-mlkit-face-detection:16.1.1"
implementation "com.github.clans:fab:1.6.4"
//implementation "com.quiph.ui:recyclerviewfastscroller:0.2.1"
implementation 'com.github.piasy:BigImageViewer:1.6.7'
implementation 'com.github.piasy:FrescoImageLoader:1.6.7'
implementation 'com.github.piasy:FrescoImageViewFactory:1.6.7'
//noinspection GradleDependency //noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.12.12' implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation ("com.github.bumptech.glide:okhttp3-integration:4.11.0") { implementation "com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2"
transitive = false
} implementation "net.rdrei.android.dirchooser:library:3.2@aar"
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { implementation "com.gu:option:1.3"
transitive = false
} implementation "com.andrognito.patternlockview:patternlockview:1.0.0"
implementation 'com.github.bumptech.glide:annotations:4.11.0' //implementation "com.andrognito.pinlockview:pinlockview:2.1.0"
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0'
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
transitive = false
}
implementation 'com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2'
implementation 'com.gu:option:1.3'
implementation 'net.rdrei.android.dirchooser:library:3.2@aar'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
implementation "ru.noties.markwon:core:3.1.0" implementation "ru.noties.markwon:core:3.1.0"
implementation ("xyz.quaver:libpupil:1.6") {
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm' implementation "xyz.quaver:libpupil:1.8.15"
} implementation "xyz.quaver:documentfilex:0.4-alpha02"
implementation "xyz.quaver:documentfilex:0.2.15" implementation "xyz.quaver:floatingsearchview:1.0.7"
implementation "xyz.quaver:floatingsearchview:1.0.4"
testImplementation 'junit:junit:4.13' testImplementation "junit:junit:4.13"
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation "androidx.test:rules:1.3.0"
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation "androidx.test:runner:1.3.0"
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
} }
androidExtensions { androidExtensions {

View File

@@ -22,21 +22,6 @@
-dontobfuscate -dontobfuscate
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep class * extends com.bumptech.glide.module.AppGlideModule {
<init>(...);
}
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
*** rewind();
}
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl
-keepattributes *Annotation*, InnerClasses -keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.SerializationKt -dontnote kotlinx.serialization.SerializationKt
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's -keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
@@ -48,4 +33,3 @@
} }
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment -keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment -keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
-keep class xyz.quaver.pupil.util.Preferences

View File

@@ -1,19 +1,17 @@
{ {
"version": 1, "version": 2,
"artifactType": { "artifactType": {
"type": "APK", "type": "APK",
"kind": "Directory" "kind": "Directory"
}, },
"applicationId": "xyz.quaver.pupil", "applicationId": "xyz.quaver.pupil",
"variantName": "release", "variantName": "processReleaseResources",
"elements": [ "elements": [
{ {
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"properties": [], "versionCode": 63,
"versionCode": 60, "versionName": "5.1.6-hotfix6",
"versionName": "5.1",
"enabled": true,
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }
] ]

View File

@@ -25,8 +25,8 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
tools:replace="android:theme" tools:replace="android:theme"
android:largeHeap="true" tools:ignore="UnusedAttribute"
tools:ignore="UnusedAttribute"> android:largeHeap="true">
<meta-data <meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES" android:name="com.google.mlkit.vision.DEPENDENCIES"

View File

@@ -18,7 +18,10 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import android.app.* import android.app.Application
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@@ -26,14 +29,13 @@ import android.os.Build
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.github.piasy.biv.BigImageViewer
import com.github.piasy.biv.loader.fresco.FrescoImageLoader
import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller import com.google.android.gms.security.ProviderInstaller
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
@@ -113,25 +115,16 @@ class Pupil : Application() {
Preferences.remove("download_folder") Preferences.remove("download_folder")
} }
if (!Preferences["reset_secure", false]) {
Preferences["security_mode"] = false
Preferences["reset_secure"] = true
}
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0) histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0) favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse("")) favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "") searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
if (Preferences["new_history"]) {
CoroutineScope(Dispatchers.IO).launch {
histories.reversed().let {
histories.clear()
histories.addAll(it)
}
favorites.reversed().let {
favorites.clear()
favorites.addAll(it)
}
}
Preferences["new_history"] = true
}
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false) FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
@@ -143,6 +136,8 @@ class Pupil : Application() {
e.printStackTrace() e.printStackTrace()
} }
BigImageViewer.initialize(FrescoImageLoader.with(this))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

View File

@@ -1,41 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2020 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil
import android.content.Context
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.AppGlideModule
import java.io.InputStream
@GlideModule
class PupilGlideModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
registry.append(
GlideUrl::class.java,
InputStream::class.java,
OkHttpUrlLoader.Factory(client)
)
}
}

View File

@@ -18,50 +18,42 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.Log
import android.util.SparseBooleanArray import android.util.SparseBooleanArray
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.view.children import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.daimajia.swipe.SwipeLayout import com.daimajia.swipe.SwipeLayout
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
import com.daimajia.swipe.interfaces.SwipeAdapterInterface import com.daimajia.swipe.interfaces.SwipeAdapterInterface
import com.github.piasy.biv.loader.ImageLoader
import kotlinx.android.synthetic.main.item_galleryblock.view.* import kotlinx.android.synthetic.main.item_galleryblock.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers import xyz.quaver.hitomi.getGallery
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import xyz.quaver.hitomi.getReader import xyz.quaver.hitomi.getReader
import xyz.quaver.io.util.getChild import xyz.quaver.io.util.getChild
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.favoriteTags import xyz.quaver.pupil.favoriteTags
import xyz.quaver.pupil.favorites import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.ui.view.TagChip
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.pupil.util.wordCapitalize import xyz.quaver.pupil.util.wordCapitalize
import java.util.* import java.io.File
import kotlin.collections.ArrayList
import kotlin.concurrent.schedule
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface { class GalleryBlockAdapter(private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
enum class ViewType { enum class ViewType {
NEXT, NEXT,
@@ -69,18 +61,26 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
PREV PREV
} }
val timer = Timer() var updateAll = true
var thin: Boolean = Preferences["thin"] var thin: Boolean = Preferences["thin"]
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) { inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
var timerTask: TimerTask? = null private var galleryID: Int = 0
private fun updateProgress(context: Context, galleryID: Int) { init {
CoroutineScope(Dispatchers.Main).launch {
while (updateAll) {
updateProgress(view.context)
delay(1000)
}
}
}
private fun updateProgress(context: Context) {
val cache = Cache.getInstance(context, galleryID) val cache = Cache.getInstance(context, galleryID)
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
if (cache.metadata.reader == null || Preferences["cache_disable"]) { if (cache.metadata.reader == null) {
view.galleryblock_progressbar_layout.visibility = View.GONE view.galleryblock_progressbar_layout.visibility = View.GONE
view.galleryblock_progress_complete.visibility = View.INVISIBLE view.galleryblock_progress_complete.visibility = View.INVISIBLE
return@launch return@launch
@@ -97,6 +97,13 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
visibility = View.VISIBLE visibility = View.VISIBLE
} }
view.galleryblock_id.setOnClickListener {
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
ClipData.newPlainText("gallery_id", galleryID.toString())
)
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}
if (!imageList.contains(null)) { if (!imageList.contains(null)) {
val downloadManager = DownloadManager.getInstance(context) val downloadManager = DownloadManager.getInstance(context)
@@ -129,9 +136,14 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
} }
fun bind(galleryID: Int) { fun bind(galleryID: Int) {
this.galleryID = galleryID
updateProgress(view.context)
val cache = Cache.getInstance(view.context, galleryID) val cache = Cache.getInstance(view.context, galleryID)
val galleryBlock = cache.metadata.galleryBlock ?: return val galleryBlock = runBlocking {
cache.getGalleryBlock()
} ?: return
with(view) { with(view) {
val resources = context.resources val resources = context.resources
@@ -144,63 +156,61 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
val artists = galleryBlock.artists val artists = galleryBlock.artists
val series = galleryBlock.series val series = galleryBlock.series
if (thin) galleryblock_thumbnail.apply {
galleryblock_thumbnail.layoutParams.width = context.resources.getDimensionPixelSize( setOnClickListener {
R.dimen.galleryblock_thumbnail_thin view.performClick()
)
galleryblock_thumbnail.setImageDrawable(CircularProgressDrawable(context).also {
it.start()
})
CoroutineScope(Dispatchers.IO).launch {
val thumbnail = cache.getThumbnail()
glide
.load(thumbnail)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(R.drawable.image_broken_variant)
.listener(object: RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
Cache.getInstance(context, galleryID).let {
it.cacheFolder.getChild(".thumbnail").let { if (it.exists()) it.delete() }
it.downloadFolder?.getChild(".thumbnail")?.let { if (it.exists()) it.delete() }
}
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean = false
})
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}.let { launch(Dispatchers.Main) { it.into(galleryblock_thumbnail) } }
}
if (timerTask == null)
timerTask = timer.schedule(0, 1000) {
updateProgress(context, galleryID)
} }
setOnLongClickListener {
view.performLongClick()
}
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
setImageLoaderCallback(object: ImageLoader.Callback {
override fun onFail(error: Exception?) {
Cache.getInstance(context, galleryID).let { cache ->
cache.cacheFolder.getChild(".thumbnail").let { if (it.exists()) it.delete() }
cache.downloadFolder?.getChild(".thumbnail")?.let { if (it.exists()) it.delete() }
}
}
override fun onCacheHit(imageType: Int, image: File?) {}
override fun onCacheMiss(imageType: Int, image: File?) {}
override fun onFinish() {}
override fun onProgress(progress: Int) {}
override fun onStart() {}
override fun onSuccess(image: File?) {}
})
ssiv?.recycle()
CoroutineScope(Dispatchers.IO).launch {
cache.getThumbnail().let { launch(Dispatchers.Main) {
showImage(it)
} }
}
}
galleryblock_title.text = galleryBlock.title galleryblock_title.text = galleryBlock.title
with(galleryblock_artist) { with(galleryblock_artist) {
text = artists.joinToString(", ") { it.wordCapitalize() } text = artists.joinToString { it.wordCapitalize() }
visibility = when { visibility = when {
artists.isNotEmpty() -> View.VISIBLE artists.isNotEmpty() -> View.VISIBLE
else -> View.GONE else -> View.GONE
} }
CoroutineScope(Dispatchers.IO).launch {
val gallery = runCatching {
getGallery(galleryID)
}.getOrNull()
if (gallery?.groups?.isNotEmpty() != true)
return@launch
launch(Dispatchers.Main) {
text = context.getString(
R.string.galleryblock_artist_with_group,
artists.joinToString { it.wordCapitalize() },
gallery.groups.joinToString { it.wordCapitalize() }
)
}
}
} }
with(galleryblock_series) { with(galleryblock_series) {
text = text =
@@ -222,27 +232,37 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
} }
} }
galleryblock_tag_group.removeAllViews() with(galleryblock_tag_group) {
CoroutineScope(Dispatchers.Default).launch { onClickListener = {
galleryBlock.relatedTags.sortedBy { onChipClickedHandler.forEach { callback ->
val tag = Tag.parse(it) callback.invoke(it)
if (favoriteTags.contains(tag))
-1
else
when(Tag.parse(it).area) {
"female" -> 0
"male" -> 1
else -> 2
}
}.map {
TagChip(context, Tag.parse(it)).apply {
setOnClickListener { view ->
for (callback in onChipClickedHandler)
callback.invoke((view as TagChip).tag)
}
} }
}.let { launch(Dispatchers.Main) { it.forEach { galleryblock_tag_group.addView(it) } } } }
tags.clear()
CoroutineScope(Dispatchers.IO).launch {
tags.addAll(
galleryBlock.relatedTags.sortedBy {
val tag = Tag.parse(it)
if (favoriteTags.contains(tag))
-1
else
when(Tag.parse(it).area) {
"female" -> 0
"male" -> 1
else -> 2
}
}.map {
Tag.parse(it)
}
)
launch(Dispatchers.Main) {
refresh()
}
}
} }
galleryblock_id.text = galleryBlock.id.toString() galleryblock_id.text = galleryBlock.id.toString()
@@ -286,8 +306,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
// Make some views invisible to make it thinner // Make some views invisible to make it thinner
if (thin) { if (thin) {
galleryblock_language.visibility = View.GONE
galleryblock_type.visibility = View.GONE
galleryblock_tag_group.visibility = View.GONE galleryblock_tag_group.visibility = View.GONE
} }
} }
@@ -382,15 +400,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
} }
} }
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
if (holder is GalleryViewHolder) {
holder.timerTask?.cancel()
holder.timerTask = null
}
}
override fun getItemCount() = override fun getItemCount() =
galleries.size + galleries.size +
(if (showNext) 1 else 0) + (if (showNext) 1 else 0) +

View File

@@ -18,57 +18,93 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.graphics.drawable.Drawable import android.content.Context
import android.graphics.drawable.Animatable
import android.net.Uri
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.bumptech.glide.load.DataSource import com.facebook.drawee.backends.pipeline.Fresco
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.facebook.drawee.controller.BaseControllerListener
import com.bumptech.glide.load.engine.GlideException import com.facebook.drawee.drawable.ScalingUtils
import com.bumptech.glide.load.model.GlideUrl import com.facebook.drawee.interfaces.DraweeController
import com.bumptech.glide.load.model.LazyHeaders import com.facebook.drawee.view.SimpleDraweeView
import com.bumptech.glide.request.RequestListener import com.facebook.imagepipeline.image.ImageInfo
import com.bumptech.glide.request.target.Target import com.github.piasy.biv.view.BigImageView
import com.github.piasy.biv.view.ImageShownCallback
import com.github.piasy.biv.view.ImageViewFactory
import kotlinx.android.synthetic.main.item_reader.view.* import kotlinx.android.synthetic.main.item_reader.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.quaver.Code
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getReferer
import xyz.quaver.hitomi.imageUrlFromImage
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.services.DownloadService
import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.ui.ReaderActivity
import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import java.util.* import java.io.File
import kotlin.concurrent.schedule
import kotlin.math.roundToInt import kotlin.math.roundToInt
class ReaderAdapter(private val activity: ReaderActivity, class ReaderAdapter(
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() { private val activity: ReaderActivity,
private val galleryID: Int
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
var reader: Reader? = null var reader: Reader? = null
val timer = Timer()
private val glide = Glide.with(activity)
var isFullScreen = false var isFullScreen = false
var onItemClickListener : ((Int) -> (Unit))? = null var onItemClickListener : (() -> (Unit))? = null
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun clear() {
view.image.mainView.let {
when (it) {
is SubsamplingScaleImageView ->
it.recycle()
is SimpleDraweeView ->
it.controller = null
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return LayoutInflater.from(parent.context).inflate( return LayoutInflater.from(parent.context).inflate(
R.layout.item_reader, parent, false R.layout.item_reader, parent, false
).let { ).let {
with(it) {
image.setImageViewFactory(FrescoImageViewFactory().apply {
updateView = { imageInfo ->
it.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
dimensionRatio = "${imageInfo.width}:${imageInfo.height}"
}
}
})
image.setImageShownCallback(object : ImageShownCallback {
override fun onMainImageShown() {
it.image.mainView.let { v ->
when (v) {
is SubsamplingScaleImageView ->
if (!isFullScreen) it.image.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
}
}
override fun onThumbnailShown() {}
})
image.setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
image.setOnClickListener {
this.performClick()
}
setOnClickListener {
onItemClickListener?.invoke()
}
}
ViewHolder(it) ViewHolder(it)
} }
} }
@@ -80,126 +116,134 @@ class ReaderAdapter(private val activity: ReaderActivity,
if (cache == null) if (cache == null)
cache = Cache.getInstance(holder.view.context, galleryID) cache = Cache.getInstance(holder.view.context, galleryID)
if (isFullScreen) { if (!isFullScreen) {
holder.view.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT holder.view.setBackgroundResource(R.drawable.reader_item_boundary)
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = 0
dimensionRatio =
"${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
}
} else { } else {
holder.view.layoutParams.height = ConstraintLayout.LayoutParams.WRAP_CONTENT holder.view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
(holder.view.progress_layout.layoutParams as ConstraintLayout.LayoutParams) height = ConstraintLayout.LayoutParams.MATCH_PARENT
.dimensionRatio = "${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}" dimensionRatio = null
} }
holder.view.background = null
holder.view.image.setOnPhotoTapListener { _, _, _ ->
onItemClickListener?.invoke(position)
}
holder.view.setOnClickListener {
onItemClickListener?.invoke(position)
} }
holder.view.reader_index.text = (position+1).toString() holder.view.reader_index.text = (position+1).toString()
if (Preferences["cache_disable"]) { val image = cache!!.getImage(position)
val lowQuality: Boolean = Preferences["low_quality"] val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
val url = when (reader!!.code) { if (progress?.isInfinite() == true && image != null) {
Code.HITOMI -> holder.view.progress_group.visibility = View.INVISIBLE
GlideUrl( holder.view.image.showImage(image.uri)
imageUrlFromImage(
galleryID,
reader!!.galleryInfo.files[position],
!lowQuality
)
, LazyHeaders.Builder().addHeader("Referer", getReferer(galleryID)).build())
Code.HIYOBI ->
GlideUrl(createImgList(galleryID, reader!!, lowQuality)[position].path)
else -> null
}
holder.view.image.post {
glide
.load(url!!)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(false)
.error(R.drawable.image_broken_variant)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
else
override(
holder.view.context.resources.displayMetrics.widthPixels,
holder.view.context.resources.getDimensionPixelSize(R.dimen.reader_max_height)
)
}
.error(R.drawable.image_broken_variant)
.into(holder.view.image)
}
} else { } else {
val image = cache!!.getImage(position) holder.view.progress_group.visibility = View.VISIBLE
val progress = activity.downloader?.progress?.get(galleryID)?.get(position) holder.view.reader_item_progressbar.progress =
if (progress?.isInfinite() == true)
100
else
progress?.roundToInt() ?: 0
if (progress?.isInfinite() == true && image != null) { holder.clear()
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.Main).launch {
glide delay(1000)
.load(image.uri) notifyItemChanged(position)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
else
override(
holder.view.context.resources.displayMetrics.widthPixels,
holder.view.context.resources.getDimensionPixelSize(R.dimen.reader_max_height)
)
}
.error(R.drawable.image_broken_variant)
.listener(object: RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
cache!!.metadata.imageList?.set(position, null)
image.delete()
DownloadService.cancel(holder.view.context, galleryID)
DownloadService.download(holder.view.context, galleryID, true)
return true
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
) = false
}).let { launch(Dispatchers.Main) { it.into(holder.view.image) } }
}
} else {
holder.view.reader_item_progressbar.visibility = View.VISIBLE
glide.clear(holder.view.image)
holder.view.reader_item_progressbar.progress =
if (progress?.isInfinite() == true)
100
else
progress?.roundToInt() ?: 0
holder.view.image.setImageDrawable(null)
timer.schedule(1000) {
CoroutineScope(Dispatchers.Main).launch {
notifyItemChanged(position)
}
}
} }
} }
} }
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0 override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
override fun onViewRecycled(holder: ViewHolder) {
holder.clear()
}
}
class FrescoImageViewFactory : ImageViewFactory() {
var updateView: ((ImageInfo) -> Unit)? = null
override fun createAnimatedImageView(
context: Context, imageType: Int,
initScaleType: Int
): View {
val view = SimpleDraweeView(context)
view.hierarchy.actualImageScaleType = scaleType(initScaleType)
return view
}
override fun loadAnimatedContent(
view: View, imageType: Int,
imageFile: File
) {
if (view is SimpleDraweeView) {
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
.setUri(Uri.parse("file://" + imageFile.absolutePath))
.setAutoPlayAnimations(true)
.setControllerListener(object: BaseControllerListener<ImageInfo>() {
override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
imageInfo?.let { updateView?.invoke(it) }
}
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
imageInfo?.let { updateView?.invoke(it) }
}
})
.build()
view.controller = controller
}
}
override fun createThumbnailView(
context: Context,
scaleType: ImageView.ScaleType, willLoadFromNetwork: Boolean
): View {
return if (willLoadFromNetwork) {
val thumbnailView = SimpleDraweeView(context)
thumbnailView.hierarchy.actualImageScaleType = scaleType(scaleType)
thumbnailView
} else {
super.createThumbnailView(context, scaleType, false)
}
}
override fun loadThumbnailContent(view: View, thumbnail: Uri) {
if (view is SimpleDraweeView) {
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
.setUri(thumbnail)
.build()
view.controller = controller
}
}
private fun scaleType(value: Int): ScalingUtils.ScaleType {
return when (value) {
BigImageView.INIT_SCALE_TYPE_CENTER -> ScalingUtils.ScaleType.CENTER
BigImageView.INIT_SCALE_TYPE_CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
BigImageView.INIT_SCALE_TYPE_CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
BigImageView.INIT_SCALE_TYPE_FIT_END -> ScalingUtils.ScaleType.FIT_END
BigImageView.INIT_SCALE_TYPE_FIT_START -> ScalingUtils.ScaleType.FIT_START
BigImageView.INIT_SCALE_TYPE_FIT_XY -> ScalingUtils.ScaleType.FIT_XY
BigImageView.INIT_SCALE_TYPE_FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
else -> ScalingUtils.ScaleType.FIT_CENTER
}
}
private fun scaleType(scaleType: ImageView.ScaleType): ScalingUtils.ScaleType {
return when (scaleType) {
ImageView.ScaleType.CENTER -> ScalingUtils.ScaleType.CENTER
ImageView.ScaleType.CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
ImageView.ScaleType.CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
ImageView.ScaleType.FIT_END -> ScalingUtils.ScaleType.FIT_END
ImageView.ScaleType.FIT_START -> ScalingUtils.ScaleType.FIT_START
ImageView.ScaleType.FIT_XY -> ScalingUtils.ScaleType.FIT_XY
ImageView.ScaleType.FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
else -> ScalingUtils.ScaleType.FIT_CENTER
}
}
} }

View File

@@ -18,32 +18,35 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.net.Uri
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager import com.github.piasy.biv.view.BigImageView
import com.bumptech.glide.load.engine.DiskCacheStrategy import xyz.quaver.pupil.R
import xyz.quaver.pupil.BuildConfig
class ThumbnailAdapter(private val glide: RequestManager, var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() { class ThumbnailAdapter(var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view) class ViewHolder(val view: BigImageView) : RecyclerView.ViewHolder(view) {
fun clear() {
view.ssiv?.recycle()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ImageView(parent.context)) return ViewHolder(BigImageView(parent.context).apply {
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
})
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
glide holder.view.showImage(Uri.parse(thumbnails[position]))
.load(thumbnails[position])
.diskCacheStrategy(DiskCacheStrategy.NONE)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
.into(holder.view)
} }
override fun getItemCount() = thumbnails.size override fun getItemCount() = thumbnails.size
override fun onViewRecycled(holder: ViewHolder) {
holder.clear()
}
} }

View File

@@ -21,17 +21,19 @@ package xyz.quaver.pupil.adapters
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import kotlin.math.min import kotlin.math.min
class ThumbnailPageAdapter(private val glide: RequestManager, private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailPageAdapter.ViewHolder>() { class ThumbnailPageAdapter(private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailPageAdapter.ViewHolder>() {
class ViewHolder(val view: RecyclerView) : RecyclerView.ViewHolder(view) class ViewHolder(val view: RecyclerView) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(RecyclerView(parent.context).apply { return ViewHolder(RecyclerView(parent.context).apply {
layoutManager = GridLayoutManager(parent.context, 3) val layoutManager = GridLayoutManager(parent.context, 3)
adapter = ThumbnailAdapter(glide, listOf()) val adapter = ThumbnailAdapter(listOf())
this.layoutManager = layoutManager
this.adapter = adapter
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}) })
} }
@@ -41,7 +43,7 @@ class ThumbnailPageAdapter(private val glide: RequestManager, private val thumbn
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size)) thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
notifyDataSetChanged() notifyDataSetChanged()
holder.view.layoutManager?.scrollToPosition(itemCount-1) (holder.view.layoutManager as GridLayoutManager).scrollToPosition(8)
} }
} }

View File

@@ -23,12 +23,10 @@ import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.SparseArray
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.TaskStackBuilder import androidx.core.app.TaskStackBuilder
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -38,21 +36,19 @@ import okhttp3.Callback
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okio.* import okio.*
import xyz.quaver.pupil.PupilInterceptor import xyz.quaver.pupil.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.client
import xyz.quaver.pupil.interceptors
import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.ui.ReaderActivity
import xyz.quaver.pupil.util.cleanCache
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.pupil.util.ellipsize import xyz.quaver.pupil.util.ellipsize
import xyz.quaver.pupil.util.normalizeID import xyz.quaver.pupil.util.normalizeID
import xyz.quaver.pupil.util.requestBuilders import xyz.quaver.pupil.util.requestBuilders
import java.io.IOException import java.io.IOException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.roundToInt
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
class DownloadService : Service() { class DownloadService : Service() {
@@ -71,7 +67,7 @@ class DownloadService : Service() {
.setOngoing(true) .setOngoing(true)
} }
private val notification = SparseArray<NotificationCompat.Builder?>() private val notification = ConcurrentHashMap<Int, NotificationCompat.Builder?>()
private fun initNotification(galleryID: Int) { private fun initNotification(galleryID: Int) {
val intent = Intent(this, ReaderActivity::class.java) val intent = Intent(this, ReaderActivity::class.java)
@@ -92,7 +88,7 @@ class DownloadService : Service() {
PendingIntent.FLAG_UPDATE_CURRENT), PendingIntent.FLAG_UPDATE_CURRENT),
).build() ).build()
notification.put(galleryID, NotificationCompat.Builder(this, "download").apply { notification[galleryID] = NotificationCompat.Builder(this, "download").apply {
setContentTitle(getString(R.string.reader_loading)) setContentTitle(getString(R.string.reader_loading))
setContentText(getString(R.string.reader_notification_text)) setContentText(getString(R.string.reader_notification_text))
setSmallIcon(R.drawable.ic_notification) setSmallIcon(R.drawable.ic_notification)
@@ -100,7 +96,7 @@ class DownloadService : Service() {
addAction(action) addAction(action)
setProgress(0, 0, true) setProgress(0, 0, true)
setOngoing(true) setOngoing(true)
}) }
notify(galleryID) notify(galleryID)
} }
@@ -125,7 +121,7 @@ class DownloadService : Service() {
.setProgress(max, progress, false) .setProgress(max, progress, false)
.setContentText("$progress/$max") .setContentText("$progress/$max")
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null) if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null || galleryID == priority)
notification.let { notificationManager.notify(galleryID, it.build()) } notification.let { notificationManager.notify(galleryID, it.build()) }
else else
notificationManager.cancel(galleryID) notificationManager.cancel(galleryID)
@@ -199,7 +195,8 @@ class DownloadService : Service() {
* 0 <= value < 100 -> Download in progress * 0 <= value < 100 -> Download in progress
* Float.POSITIVE_INFINITY -> Download completed * Float.POSITIVE_INFINITY -> Download completed
*/ */
val progress = SparseArray<MutableList<Float>?>() val progress = ConcurrentHashMap<Int, MutableList<Float>>()
var priority = 0
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
@@ -291,14 +288,16 @@ class DownloadService : Service() {
fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch { fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
cancel(galleryID) cancel(galleryID)
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID) DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
Cache.delete(galleryID) Cache.delete(this@DownloadService, galleryID)
startId?.let { stopSelf(it) } startId?.let { stopSelf(it) }
} }
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch { fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
if (progress.indexOfKey(galleryID) >= 0) if (DownloadManager.getInstance(this@DownloadService).isDownloading(galleryID))
cancel(galleryID) return@launch
cleanCache(this@DownloadService)
val cache = Cache.getInstance(this@DownloadService, galleryID) val cache = Cache.getInstance(this@DownloadService, galleryID)
@@ -309,30 +308,20 @@ class DownloadService : Service() {
// Gallery doesn't exist // Gallery doesn't exist
if (reader == null) { if (reader == null) {
delete(galleryID) delete(galleryID)
progress.put(galleryID, null) progress[galleryID] = mutableListOf()
return@launch return@launch
} }
val list = MutableList(reader.galleryInfo.files.size) { 0F } histories.add(galleryID)
progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 0F }
cache.metadata.imageList?.let { cache.metadata.imageList?.let {
if (list.size != it.size) {
FirebaseCrashlytics.getInstance().log(
"""
GALLERYID: $galleryID
${it.size} - ${list.size}
""".trimIndent()
)
error("ImageList Size does not match")
}
it.forEachIndexed { index, image -> it.forEachIndexed { index, image ->
list[index] = if (image != null) Float.POSITIVE_INFINITY else 0F progress[galleryID]?.set(index, if (image != null) Float.POSITIVE_INFINITY else 0F)
} }
} }
progress.put(galleryID, list)
if (isCompleted(galleryID)) { if (isCompleted(galleryID)) {
if (DownloadManager.getInstance(this@DownloadService) if (DownloadManager.getInstance(this@DownloadService)
.getDownloadFolder(galleryID) != null ) .getDownloadFolder(galleryID) != null )
@@ -357,18 +346,8 @@ class DownloadService : Service() {
} }
} }
reader.requestBuilders.also { reader.requestBuilders.forEachIndexed { index, it ->
if (it.size != list.size) { if (progress[galleryID]?.get(index)?.isInfinite() == false) {
FirebaseCrashlytics.getInstance().log(
"""
GALLERYID: $galleryID
${it.size} - ${list.size}
""".trimIndent()
)
error("Requests Size does not match")
}
}.forEachIndexed { index, it ->
if (!list[index].isInfinite()) {
val request = it.tag(Tag(galleryID, index, startId)).build() val request = it.tag(Tag(galleryID, index, startId)).build()
client.newCall(request).enqueue(callback) client.newCall(request).enqueue(callback)
} }

View File

@@ -22,13 +22,18 @@ import kotlinx.android.parcel.IgnoredOnParcel
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.hitomi.Suggestion import xyz.quaver.hitomi.Suggestion
import xyz.quaver.pupil.util.translations
@Parcelize @Parcelize
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion { data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n) constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
@IgnoredOnParcel @IgnoredOnParcel
override val body = s override val body =
if (translations[s] != null)
"${translations[s]} ($s)"
else
s
} }
@Parcelize @Parcelize

View File

@@ -23,16 +23,19 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
import android.text.util.Linkify
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.widget.* import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import com.bumptech.glide.Glide
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@@ -59,6 +62,7 @@ import xyz.quaver.pupil.util.checkUpdate
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.pupil.util.restore import xyz.quaver.pupil.util.restore
import java.util.regex.Pattern
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.min import kotlin.math.min
@@ -121,11 +125,15 @@ class MainActivity :
if (Preferences["download_folder", ""].isEmpty()) if (Preferences["download_folder", ""].isEmpty())
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog") DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog")
checkUpdate(this)
initView() initView()
} }
override fun onResume() {
super.onResume()
checkUpdate(this)
}
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
override fun onBackPressed() { override fun onBackPressed() {
when { when {
@@ -142,21 +150,10 @@ class MainActivity :
} }
} }
override fun onResume() {
super.onResume()
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel() (main_recyclerview?.adapter as? GalleryBlockAdapter)?.updateAll = false
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
@@ -214,6 +211,8 @@ class MainActivity :
} }
) )
Linkify.addLinks(main_noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) })
//NavigationView //NavigationView
main_nav_view.setNavigationItemSelectedListener(this) main_nav_view.setNavigationItemSelectedListener(this)
@@ -263,11 +262,7 @@ class MainActivity :
if (it?.isEmpty() == false) { if (it?.isEmpty() == false) {
val galleryID = it.random() val galleryID = it.random()
GalleryDialog( GalleryDialog(this@MainActivity, galleryID).apply {
this@MainActivity,
Glide.with(this@MainActivity),
galleryID
).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
runOnUiThread { runOnUiThread {
query = it.toQuery() query = it.toQuery()
@@ -298,12 +293,22 @@ class MainActivity :
setTitle(R.string.main_open_gallery_by_id) setTitle(R.string.main_open_gallery_by_id)
setPositiveButton(android.R.string.ok) { _, _ -> setPositiveButton(android.R.string.ok) { _, _ ->
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID)
}
startActivity(intent) GalleryDialog(this@MainActivity, galleryID).apply {
onChipClickedHandler.add {
runOnUiThread {
query = it.toQuery()
currentPage = 0
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
dismiss()
}
}.show()
} }
}.show() }.show()
} }
@@ -318,7 +323,7 @@ class MainActivity :
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun setupRecyclerView() { private fun setupRecyclerView() {
with(main_recyclerview) { with(main_recyclerview) {
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply { adapter = GalleryBlockAdapter(galleries).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
runOnUiThread { runOnUiThread {
query = it.toQuery() query = it.toQuery()
@@ -332,16 +337,13 @@ class MainActivity :
} }
onDownloadClickedHandler = { position -> onDownloadClickedHandler = { position ->
val galleryID = galleries[position] val galleryID = galleries[position]
if (Preferences["cache_disable"])
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show() if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
DownloadService.cancel(this@MainActivity, galleryID)
}
else { else {
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress DownloadManager.getInstance(context).addDownloadFolder(galleryID)
DownloadService.cancel(this@MainActivity, galleryID) DownloadService.download(this@MainActivity, galleryID)
}
else {
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
DownloadService.download(this@MainActivity, galleryID)
}
} }
closeAllItems() closeAllItems()
@@ -382,13 +384,9 @@ class MainActivity :
if (v !is CardView) if (v !is CardView)
return@listener false return@listener false
val galleryID = galleries[position] val galleryID = galleries.getOrNull(position) ?: return@listener true
GalleryDialog( GalleryDialog(this@MainActivity, galleryID).apply {
this@MainActivity,
Glide.with(this@MainActivity),
galleryID
).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
runOnUiThread { runOnUiThread {
query = it.toQuery() query = it.toQuery()
@@ -621,7 +619,7 @@ class MainActivity :
else -> { else -> {
searchHistory.map { searchHistory.map {
Suggestion(it) Suggestion(it)
}.takeLast(20) + FavoriteHistorySwitch(getString(R.string.search_show_tags)) }.takeLast(10) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
} }
}.reversed() }.reversed()
@@ -672,7 +670,9 @@ class MainActivity :
swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString()))) swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString())))
val currentQuery = query.split(" ").last().replace('_', ' ') val currentQuery = query.split(" ").last()
.replace(Regex("^-"), "")
.replace('_', ' ')
suggestionJob = CoroutineScope(Dispatchers.IO).launch { suggestionJob = CoroutineScope(Dispatchers.IO).launch {
val suggestions = kotlin.runCatching { val suggestions = kotlin.runCatching {
@@ -938,7 +938,7 @@ class MainActivity :
} }
} }
} }
} }.toList()
} }
} }
@@ -985,14 +985,4 @@ class MainActivity :
} }
} }
} }
override fun onLowMemory() {
super.onLowMemory()
Glide.get(this).onLowMemory()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
Glide.get(this).onTrimMemory(level)
}
} }

View File

@@ -33,17 +33,14 @@ import android.view.animation.Animation
import android.view.animation.AnticipateInterpolator import android.view.animation.AnticipateInterpolator
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import android.view.animation.TranslateAnimation import android.view.animation.TranslateAnimation
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.mlkit.vision.face.Face import com.google.mlkit.vision.face.Face
@@ -60,7 +57,6 @@ import xyz.quaver.Code
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.favorites import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.services.DownloadService
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.camera import xyz.quaver.pupil.util.camera
@@ -68,8 +64,6 @@ import xyz.quaver.pupil.util.closeCamera
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.pupil.util.startCamera import xyz.quaver.pupil.util.startCamera
import java.util.*
import kotlin.concurrent.schedule
class ReaderActivity : BaseActivity() { class ReaderActivity : BaseActivity() {
@@ -88,7 +82,12 @@ class ReaderActivity : BaseActivity() {
var downloader: DownloadService? = null var downloader: DownloadService? = null
private val conn = object: ServiceConnection { private val conn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) { override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
downloader = (service as DownloadService.Binder).service downloader = (service as DownloadService.Binder).service.also {
it.priority = 0
if (!it.progress.containsKey(galleryID))
DownloadService.download(this@ReaderActivity, galleryID, true)
}
} }
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
@@ -96,7 +95,6 @@ class ReaderActivity : BaseActivity() {
} }
} }
private val timer = Timer()
private val snapHelper = PagerSnapHelper() private val snapHelper = PagerSnapHelper()
private var menu: Menu? = null private var menu: Menu? = null
@@ -136,38 +134,7 @@ class ReaderActivity : BaseActivity() {
return return
} }
if (Preferences["cache_disable"]) { initDownloadListener()
reader_download_progressbar.visibility = View.GONE
CoroutineScope(Dispatchers.IO).launch {
val reader = cache.getReader()
launch(Dispatchers.Main) initDownloader@{
if (reader == null) {
Snackbar
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
.show()
return@initDownloader
}
histories.add(galleryID)
(reader_recyclerview.adapter as ReaderAdapter).apply {
this.reader = reader
notifyDataSetChanged()
}
title = reader.galleryInfo.title ?: ""
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}"
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
when (reader.code) {
Code.HITOMI -> R.drawable.hitomi
Code.HIYOBI -> R.drawable.ic_hiyobi
else -> android.R.color.transparent
})
}
}
} else
initDownloader()
initView() initView()
} }
@@ -259,13 +226,14 @@ class ReaderActivity : BaseActivity() {
if (downloader != null) if (downloader != null)
unbindService(conn) unbindService(conn)
downloader?.priority = galleryID
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
timer.cancel() update = false
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
if (!DownloadManager.getInstance(this).isDownloading(galleryID)) if (!DownloadManager.getInstance(this).isDownloading(galleryID))
DownloadService.cancel(this, galleryID) DownloadService.cancel(this, galleryID)
@@ -290,7 +258,7 @@ class ReaderActivity : BaseActivity() {
//currentPage is 1-based //currentPage is 1-based
return when(keyCode) { return when(keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> { KeyEvent.KEYCODE_VOLUME_UP -> {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage-2, 0) (reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-2, 0)
true true
} }
@@ -303,46 +271,51 @@ class ReaderActivity : BaseActivity() {
} }
} }
private fun initDownloader() { private var update = true
DownloadService.download(this, galleryID, true) private fun initDownloadListener() {
CoroutineScope(Dispatchers.Main).launch {
while (update) {
delay(1000)
timer.schedule(1000, 1000) { val downloader = downloader ?: continue
val downloader = downloader ?: return@schedule
if (downloader.progress.indexOfKey(galleryID) < 0) //loading if (!downloader.progress.containsKey(galleryID)) //loading
return@schedule continue
if (downloader.progress[galleryID] == null) { //Gallery not found if (downloader.progress[galleryID]?.isEmpty() == true) { //Gallery not found
timer.cancel() update = false
Snackbar Snackbar
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE) .make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
.show() .show()
}
histories.add(galleryID) return@launch
}
runOnUiThread {
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0 reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
reader_download_progressbar.progress = downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0 reader_download_progressbar.progress =
downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
if (title == getString(R.string.reader_loading)) { if (title == getString(R.string.reader_loading)) {
val reader = cache.metadata.reader val reader = cache.metadata.reader
if (reader != null) { if (reader != null) {
with (reader_recyclerview.adapter as ReaderAdapter) { with(reader_recyclerview.adapter as ReaderAdapter) {
this.reader = reader this.reader = reader
notifyDataSetChanged() notifyDataSetChanged()
} }
title = reader.galleryInfo.title title = reader.galleryInfo.title
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}" menu?.findItem(R.id.reader_menu_page_indicator)?.title =
"$currentPage/${reader.galleryInfo.files.size}"
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity, menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(
this@ReaderActivity,
when (reader.code) { when (reader.code) {
Code.HITOMI -> R.drawable.hitomi Code.HITOMI -> R.drawable.hitomi
Code.HIYOBI -> R.drawable.ic_hiyobi Code.HIYOBI -> R.drawable.ic_hiyobi
else -> android.R.color.transparent else -> android.R.color.transparent
}) }
)
} }
} }
@@ -366,7 +339,7 @@ class ReaderActivity : BaseActivity() {
scrollMode(false) scrollMode(false)
fullscreen(true) fullscreen(true)
} else { } else {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage) //Moves to next page because currentPage is 1-based indexing (reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage, 0) //Moves to next page because currentPage is 1-based indexing
} }
} }
} }
@@ -386,7 +359,6 @@ class ReaderActivity : BaseActivity() {
return return
currentPage = layoutManager.findFirstVisibleItemPosition()+1 currentPage = layoutManager.findFirstVisibleItemPosition()+1
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}" menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}"
} }
}) })
} }
@@ -395,19 +367,15 @@ class ReaderActivity : BaseActivity() {
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
setOnClickListener { setOnClickListener {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false)) val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
else {
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
if (downloadManager.isDownloading(galleryID)) { if (downloadManager.isDownloading(galleryID)) {
downloadManager.deleteDownloadFolder(galleryID) downloadManager.deleteDownloadFolder(galleryID)
animateDownloadFAB(false) animateDownloadFAB(false)
} else { } else {
downloadManager.addDownloadFolder(galleryID) downloadManager.addDownloadFolder(galleryID)
DownloadService.download(context, galleryID, true) DownloadService.download(context, galleryID, true)
animateDownloadFAB(true) animateDownloadFAB(true)
}
} }
} }
} }
@@ -486,7 +454,11 @@ class ReaderActivity : BaseActivity() {
reader_recyclerview.layoutManager = LinearLayoutManager(this) reader_recyclerview.layoutManager = LinearLayoutManager(this)
} else { } else {
snapHelper.attachToRecyclerView(reader_recyclerview) snapHelper.attachToRecyclerView(reader_recyclerview)
reader_recyclerview.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, Preferences["rtl", false]) reader_recyclerview.layoutManager = object: LinearLayoutManager(this, HORIZONTAL, Preferences["rtl", false]) {
override fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {
extraLayoutSpace.fill(600)
}
}
} }
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0) (reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
@@ -636,14 +608,4 @@ class ReaderActivity : BaseActivity() {
} }
} }
} }
override fun onLowMemory() {
super.onLowMemory()
Glide.get(this).onLowMemory()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
Glide.get(this).onTrimMemory(level)
}
} }

View File

@@ -39,7 +39,7 @@ class DownloadFolderNameDialogFragment : DialogFragment() {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
private fun build(): View { private fun build(): View {
val galleryID = Cache.instances.let { if (it.size() == 0) 1199708 else it.keyAt((0 until it.size()).random()) } val galleryID = Cache.instances.let { if (it.size == 0) 1199708 else it.keys.elementAt((0 until it.size).random()) }
val galleryBlock = runBlocking { val galleryBlock = runBlocking {
Cache.getInstance(requireContext(), galleryID).getGalleryBlock() Cache.getInstance(requireContext(), galleryID).getGalleryBlock()
} }

View File

@@ -20,16 +20,17 @@ package xyz.quaver.pupil.ui.dialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout.LayoutParams import android.widget.LinearLayout.LayoutParams
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.RequestManager
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_gallery.* import kotlinx.android.synthetic.main.dialog_gallery.*
import kotlinx.android.synthetic.main.dialog_gallery_details.view.* import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
@@ -41,7 +42,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import xyz.quaver.hitomi.Gallery import xyz.quaver.hitomi.Gallery
import xyz.quaver.hitomi.getGallery import xyz.quaver.hitomi.getGallery
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
@@ -53,8 +53,10 @@ import xyz.quaver.pupil.ui.view.TagChip
import xyz.quaver.pupil.util.ItemClickSupport import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.wordCapitalize import xyz.quaver.pupil.util.wordCapitalize
import java.util.*
import kotlin.collections.ArrayList
class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : AlertDialog(context) { class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(context) {
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>() val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
@@ -75,7 +77,6 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
context.startActivity(Intent(context, ReaderActivity::class.java).apply { context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID) putExtra("galleryID", galleryID)
}) })
histories.add(galleryID)
} }
} }
@@ -105,19 +106,19 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
} }
} }
glide gallery_cover.showImage(Uri.parse(gallery.cover))
.load(gallery.cover)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}.into(gallery_cover)
addDetails(gallery) addDetails(gallery)
addThumbnails(gallery) addThumbnails(gallery)
addRelated(gallery) addRelated(gallery)
} }
} catch (e: Exception) { } catch (e: Exception) {
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show() Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).apply {
if (Locale.getDefault().language == "ko")
setAction(context.getText(R.string.https_text)) {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.https))))
}
}.show()
} }
} }
} }
@@ -195,7 +196,8 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
gallery_details.setText(R.string.gallery_thumbnails) gallery_details.setText(R.string.gallery_thumbnails)
val pager = ViewPager2(context).apply { val pager = ViewPager2(context).apply {
adapter = ThumbnailPageAdapter(glide, gallery.thumbnails) adapter = ThumbnailPageAdapter(gallery.thumbnails)
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
} }
gallery_details_contents.addView( gallery_details_contents.addView(
@@ -215,7 +217,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
val galleries = ArrayList<Int>() val galleries = ArrayList<Int>()
val adapter = GalleryBlockAdapter(glide, galleries).apply { val adapter = GalleryBlockAdapter(galleries).apply {
onChipClickedHandler.add { tag -> onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler -> this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag) handler.invoke(tag)
@@ -235,14 +237,9 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
context.startActivity(Intent(context, ReaderActivity::class.java).apply { context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleries[position]) putExtra("galleryID", galleries[position])
}) })
histories.add(galleries[position])
} }
onItemLongClickListener = { _, position, _ -> onItemLongClickListener = { _, position, _ ->
GalleryDialog( GalleryDialog(context, galleries[position]).apply {
context,
glide,
galleries[position]
).apply {
onChipClickedHandler.add { tag -> onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) } this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
} }

View File

@@ -44,8 +44,7 @@ import java.net.Proxy
class ProxyDialog(context: Context) : AlertDialog(context) { class ProxyDialog(context: Context) : AlertDialog(context) {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setContentView(build()) setView(build())
window?.attributes?.width = ViewGroup.LayoutParams.MATCH_PARENT
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
} }

View File

@@ -31,6 +31,7 @@ import xyz.quaver.io.util.deleteRecursively
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.histories import xyz.quaver.pupil.histories
import xyz.quaver.pupil.util.byteToString import xyz.quaver.pupil.util.byteToString
import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.downloader.DownloadManager
import java.io.File import java.io.File
@@ -61,6 +62,8 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
if (dir.exists()) if (dir.exists())
dir.deleteRecursively() dir.deleteRecursively()
Cache.instances.clear()
summary = context.getString(R.string.settings_storage_usage, byteToString(0)) summary = context.getString(R.string.settings_storage_usage, byteToString(0))
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
var size = 0L var size = 0L

View File

@@ -24,11 +24,11 @@ import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.Preference import androidx.preference.*
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.io.util.getChild import xyz.quaver.io.util.getChild
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
@@ -37,6 +37,7 @@ import xyz.quaver.pupil.ui.SettingsActivity
import xyz.quaver.pupil.ui.dialog.* import xyz.quaver.pupil.ui.dialog.*
import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.*
import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.downloader.DownloadManager
import java.util.*
class SettingsFragment : class SettingsFragment :
PreferenceFragmentCompat(), PreferenceFragmentCompat(),
@@ -109,7 +110,7 @@ class SettingsFragment :
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip( (context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
ClipData.newPlainText("user_id", Preferences.get<String>("user_id")) ClipData.newPlainText("user_id", Preferences.get<String>("user_id"))
) )
Toast.makeText(context, R.string.settings_user_id_toast, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
} }
else -> return false else -> return false
} }
@@ -123,6 +124,9 @@ class SettingsFragment :
this ?: return false this ?: return false
when (key) { when (key) {
"tag_translation" -> {
updateTranslations()
}
"nomedia" -> { "nomedia" -> {
val create = (newValue as? Boolean) ?: return false val create = (newValue as? Boolean) ?: return false
@@ -243,6 +247,27 @@ class SettingsFragment :
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }
"tag_translation" -> {
this as ListPreference
isEnabled = false
CoroutineScope(Dispatchers.IO).launch {
kotlin.runCatching {
val languages = getAvailableLanguages().distinct().toTypedArray()
entries = languages.map { Locale(it).let { loc -> loc.getDisplayLanguage(loc) } }.toTypedArray()
entryValues = languages
launch(Dispatchers.Main) {
isEnabled = true
}
}
}
onPreferenceChangeListener = this@SettingsFragment
}
"mirrors" -> { "mirrors" -> {
onPreferenceClickListener = this@SettingsFragment onPreferenceClickListener = this@SettingsFragment
} }

View File

@@ -57,7 +57,7 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
var onFavoriteHistorySwitchClickListener: (() -> Unit)? = null var onFavoriteHistorySwitchClickListener: (() -> Unit)? = null
init { init {
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI or searchInputView.imeOptions
searchInputView.addTextChangedListener(this) searchInputView.addTextChangedListener(this)
onSearchListener = this onSearchListener = this
@@ -164,9 +164,7 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
} }
} }
if (item.t == -1) { if (item.t > 0) {
textView?.text = item.s
} else {
(suggestionView as? LinearLayout)?.let { (suggestionView as? LinearLayout)?.let {
val count = it.findViewById<TextView>(R.id.count) val count = it.findViewById<TextView>(R.id.count)
if (count == null) if (count == null)

View File

@@ -25,16 +25,17 @@ import com.google.android.material.chip.Chip
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.favoriteTags import xyz.quaver.pupil.favoriteTags
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.translations
import xyz.quaver.pupil.util.wordCapitalize import xyz.quaver.pupil.util.wordCapitalize
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
class TagChip(context: Context, tag: Tag) : Chip(context) { class TagChip(context: Context, _tag: Tag) : Chip(context) {
val tag: Tag = val tag: Tag =
tag.let { _tag.let {
when { when {
it.area != null -> it it.area != null -> it
else -> Tag("tag", tag.tag) else -> Tag("tag", _tag.tag)
} }
} }
@@ -45,23 +46,24 @@ class TagChip(context: Context, tag: Tag) : Chip(context) {
}.toMap() }.toMap()
init { init {
chipIcon = when(tag.area) { when(tag.area) {
"male" -> { "male" -> {
setChipBackgroundColorResource(R.color.material_blue_700) setChipBackgroundColorResource(R.color.material_blue_700)
setTextColor(ContextCompat.getColor(context, android.R.color.white)) setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.gender_male_white) setCloseIconTintResource(android.R.color.white)
chipIcon = ContextCompat.getDrawable(context, R.drawable.gender_male_white)
} }
"female" -> { "female" -> {
setChipBackgroundColorResource(R.color.material_pink_600) setChipBackgroundColorResource(R.color.material_pink_600)
setTextColor(ContextCompat.getColor(context, android.R.color.white)) setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.gender_female_white) setCloseIconTintResource(android.R.color.white)
chipIcon = ContextCompat.getDrawable(context, R.drawable.gender_female_white)
} }
else -> null
}.also {
if (favoriteTags.contains(tag))
setChipBackgroundColorResource(R.color.material_orange_500)
} }
if (favoriteTags.contains(tag))
setChipBackgroundColorResource(R.color.material_orange_500)
isCloseIconVisible = true isCloseIconVisible = true
closeIcon = ContextCompat.getDrawable(context, closeIcon = ContextCompat.getDrawable(context,
if (favoriteTags.contains(tag)) if (favoriteTags.contains(tag))
@@ -89,7 +91,7 @@ class TagChip(context: Context, tag: Tag) : Chip(context) {
text = when (tag.area) { text = when (tag.area) {
"language" -> languages[tag.tag] "language" -> languages[tag.tag]
else -> tag.tag.wordCapitalize() else -> (translations[tag.tag] ?: tag.tag).wordCapitalize()
} }
setEnsureMinTouchTargetSize(false) setEnsureMinTouchTargetSize(false)

View File

@@ -0,0 +1,100 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2020 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui.view
import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.util.Log
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import kotlinx.coroutines.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.types.Tags
class TagChipGroup @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, attrStyle: Int = R.attr.chipGroupStyle, val tags: Tags = Tags()) : ChipGroup(context, attr, attrStyle), MutableSet<Tag> by tags {
object Defaults {
val maxChipSize = 10
}
var maxChipSize: Int = Defaults.maxChipSize
set(value) {
field = value
refresh()
}
private val moreView = Chip(context).apply {
text = ""
setEnsureMinTouchTargetSize(false)
setOnClickListener {
removeView(this)
for (i in maxChipSize until tags.size) {
val tag = tags.elementAt(i)
addView(TagChip(context, tag).apply {
setOnClickListener {
onClickListener?.invoke(tag)
}
})
}
}
}
var onClickListener: ((Tag) -> Unit)? = null
private fun applyAttributes(attr: TypedArray) {
maxChipSize = attr.getInt(R.styleable.TagChipGroup_maxTag, Defaults.maxChipSize)
}
private var refreshJob: Job? = null
fun refresh() {
refreshJob?.cancel()
this.removeAllViews()
refreshJob = CoroutineScope(Dispatchers.Main).launch {
tags.take(maxChipSize).map {
CoroutineScope(Dispatchers.Default).async {
TagChip(context, it).apply {
setOnClickListener {
onClickListener?.invoke(this.tag)
}
}
}
}.forEach {
addView(it.await())
}
if (maxChipSize > 0 && tags.size > maxChipSize && parent == null)
addView(moreView)
}
}
init {
applyAttributes(context.obtainStyledAttributes(attr, R.styleable.TagChipGroup))
refresh()
}
}

View File

@@ -22,6 +22,7 @@ import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
import java.util.*
class SavedSet <T: Any> (private val file: File, private val any: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> by set { class SavedSet <T: Any> (private val file: File, private val any: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> by set {
@@ -38,24 +39,23 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
load() load()
} }
@Synchronized
fun load() { fun load() {
synchronized(this) { set.clear()
set.clear() kotlin.runCatching {
kotlin.runCatching { Json.decodeFromString(serializer, file.readText())
Json.decodeFromString(serializer, file.readText()) }.onSuccess {
}.onSuccess { set.addAll(it)
set.addAll(it)
}
} }
} }
@Synchronized
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
fun save() { fun save() {
synchronized(this) { file.writeText(Json.encodeToString(serializer, set.toList()))
file.writeText(Json.encodeToString(serializer, set.toList()))
}
} }
@Synchronized
override fun add(element: T): Boolean { override fun add(element: T): Boolean {
load() load()
@@ -66,6 +66,7 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
} }
} }
@Synchronized
override fun addAll(elements: Collection<T>): Boolean { override fun addAll(elements: Collection<T>): Boolean {
load() load()
@@ -76,6 +77,7 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
} }
} }
@Synchronized
override fun remove(element: T): Boolean { override fun remove(element: T): Boolean {
load() load()
@@ -84,6 +86,7 @@ class SavedSet <T: Any> (private val file: File, private val any: T, private val
} }
} }
@Synchronized
override fun clear() { override fun clear() {
set.clear() set.clear()
save() save()

View File

@@ -1,37 +0,0 @@
package xyz.quaver.pupil.util
import android.graphics.Paint
import android.text.style.LineHeightSpan
class SetLineOverlap(private val overlap: Boolean) : LineHeightSpan {
companion object {
private var originalBottom = 15
private var originalDescent = 13
private var overlapSaved = false
}
override fun chooseHeight(
text: CharSequence?,
start: Int,
end: Int,
spanstartv: Int,
lineHeight: Int,
fm: Paint.FontMetricsInt?
) {
fm ?: return
if (overlap) {
if (overlapSaved) {
originalBottom = fm.bottom
originalDescent = fm.descent
overlapSaved = true
}
fm.bottom += fm.top
fm.descent += fm.top
} else {
fm.bottom = originalBottom
fm.descent = originalDescent
overlapSaved = false
}
}
}

View File

@@ -20,10 +20,12 @@ package xyz.quaver.pupil.util.downloader
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.util.SparseArray import android.net.Uri
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
@@ -37,7 +39,9 @@ import xyz.quaver.io.FileX
import xyz.quaver.io.util.* import xyz.quaver.io.util.*
import xyz.quaver.pupil.client import xyz.quaver.pupil.client
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
@Serializable @Serializable
data class Metadata( data class Metadata(
@@ -51,7 +55,7 @@ data class Metadata(
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) { class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
companion object { companion object {
val instances = SparseArray<Cache>() val instances = ConcurrentHashMap<Int, Cache>()
fun getInstance(context: Context, galleryID: Int) = fun getInstance(context: Context, galleryID: Int) =
instances[galleryID] ?: synchronized(this) { instances[galleryID] ?: synchronized(this) {
@@ -59,9 +63,9 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
} }
@Synchronized @Synchronized
fun delete(galleryID: Int) { fun delete(context: Context, galleryID: Int) {
instances[galleryID]?.cacheFolder?.deleteRecursively() File(context.cacheDir, "imageCache/$galleryID").deleteRecursively()
instances.delete(galleryID) instances.remove(galleryID)
} }
} }
@@ -131,8 +135,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
} }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
suspend fun getThumbnail(): ByteArray? = suspend fun getThumbnail(): Uri =
findFile(".thumbnail")?.readBytes() findFile(".thumbnail")?.uri
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) { ?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
kotlin.runCatching { kotlin.runCatching {
val request = Request.Builder() val request = Request.Builder()
@@ -140,10 +144,15 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
.build() .build()
client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() } client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() }
}.getOrNull()?.also { kotlin.run { }.getOrNull()?.let { thumbnail -> kotlin.runCatching {
cacheFolder.getChild(".thumbnail").writeBytes(it) cacheFolder.getChild(".thumbnail").also {
} } if (!it.exists())
} } it.createNewFile()
it.writeBytes(thumbnail)
}
}.getOrNull()?.uri }
} } ?: Uri.EMPTY
suspend fun getReader(): Reader? { suspend fun getReader(): Reader? {
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') } val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
@@ -185,67 +194,76 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
} }
fun getImage(index: Int): FileX? = fun getImage(index: Int): FileX? =
metadata.imageList?.get(index)?.let { findFile(it) } metadata.imageList?.getOrNull(index)?.let { findFile(it) }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
fun putImage(index: Int, fileName: String, data: ByteArray) { fun putImage(index: Int, fileName: String, data: ByteArray) {
val file = cacheFolder.getChild(fileName) val file = cacheFolder.getChild(fileName)
file.createNewFile() if (!file.exists())
file.createNewFile()
file.writeBytes(data) file.writeBytes(data)
setMetadata { metadata -> metadata.imageList!![index] = fileName } setMetadata { metadata -> metadata.imageList!![index] = fileName }
} }
private val lock = ConcurrentHashMap<Int, Mutex>()
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch { fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
val downloadFolder = downloadFolder ?: return@launch val downloadFolder = downloadFolder ?: return@launch
val cacheMetadata = cacheFolder.getChild(".metadata") if (lock[galleryID]?.isLocked == true)
val downloadMetadata = downloadFolder.getChild(".metadata")
if (downloadMetadata.exists() || !cacheMetadata.exists())
return@launch return@launch
if (cacheMetadata.exists()) { (lock[galleryID] ?: Mutex().also { lock[galleryID] = it }).withLock {
kotlin.runCatching { val cacheMetadata = cacheFolder.getChild(".metadata")
downloadMetadata.createNewFile() val downloadMetadata = downloadFolder.getChild(".metadata")
downloadMetadata.writeText(Json.encodeToString(metadata))
cacheMetadata.delete() if (!cacheMetadata.exists())
return@launch
if (cacheMetadata.exists()) {
kotlin.runCatching {
if (!downloadMetadata.exists())
downloadMetadata.createNewFile()
downloadMetadata.writeText(Json.encodeToString(metadata))
}
} }
}
val cacheThumbnail = cacheFolder.getChild(".thumbnail") val cacheThumbnail = cacheFolder.getChild(".thumbnail")
val downloadThumbnail = downloadFolder.getChild(".thumbnail") val downloadThumbnail = downloadFolder.getChild(".thumbnail")
if (cacheThumbnail.exists() && !downloadThumbnail.exists()) { if (cacheThumbnail.exists()) {
kotlin.runCatching { kotlin.runCatching {
if (!downloadThumbnail.exists()) if (!downloadThumbnail.exists())
downloadThumbnail.createNewFile() downloadThumbnail.createNewFile()
downloadThumbnail.outputStream()?.use { target -> cacheThumbnail.inputStream()?.use { source -> downloadThumbnail.outputStream()?.use { target -> target.channel.truncate(0L); cacheThumbnail.inputStream()?.use { source ->
source.copyTo(target) source.copyTo(target)
} } } }
cacheThumbnail.delete() cacheThumbnail.delete()
}
} }
}
metadata.imageList?.forEach { imageName -> metadata.imageList?.forEach { imageName ->
imageName ?: return@forEach imageName ?: return@forEach
val target = downloadFolder.getChild(imageName) val target = downloadFolder.getChild(imageName)
val source = cacheFolder.getChild(imageName) val source = cacheFolder.getChild(imageName)
if (!source.exists() || target.exists()) if (!source.exists())
return@forEach return@forEach
kotlin.runCatching { kotlin.runCatching {
if (!target.exists()) if (!target.exists())
target.createNewFile() target.createNewFile()
target.outputStream()?.use { target -> source.inputStream()?.use { source -> target.outputStream()?.use { target -> target.channel.truncate(0L); source.inputStream()?.use { source ->
source.copyTo(target) source.copyTo(target)
} } } }
}
} }
cacheFolder.deleteRecursively()
} }
} }
} }

View File

@@ -80,7 +80,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
}.invoke() }.invoke()
} }
return downloadFolderMapInstance!! return downloadFolderMapInstance ?: mutableMapOf()
} }

View File

@@ -19,35 +19,49 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import android.content.Context import android.content.Context
import android.os.storage.StorageManager import kotlinx.coroutines.CoroutineScope
import androidx.core.content.ContextCompat import kotlinx.coroutines.Dispatchers
import androidx.core.net.toUri import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.lang.reflect.Array
import java.net.URL
@Suppress("DEPRECATION") val mutex = Mutex()
@Deprecated("Use downloader.Cache instead") fun cleanCache(context: Context) = CoroutineScope(Dispatchers.IO).launch {
fun getCachedGallery(context: Context, galleryID: Int) = if (mutex.isLocked) return@launch
File(getDownloadDirectory(context), galleryID.toString()).let {
if (it.exists()) mutex.withLock {
it val cacheFolder = File(context.cacheDir, "imageCache")
else val downloadManager = DownloadManager.getInstance(context)
File(context.cacheDir, "imageCache/$galleryID")
val limit = (Preferences.get<String>("cache_limit").toLongOrNull() ?: 0L)*1024*1024*1024
if (limit == 0L) return@withLock
val cacheSize = {
var size = 0L
cacheFolder.walk().forEach {
size += it.length()
}
size
}
if (cacheSize.invoke() > limit)
while (cacheSize.invoke() > limit/2) {
val caches = cacheFolder.list() ?: return@withLock
synchronized(histories) {
(histories.firstOrNull {
caches.contains(it.toString()) && !downloadManager.isDownloading(it)
} ?: return@withLock).let {
Cache.delete(context, it)
}
}
}
} }
}
@Suppress("DEPRECATION")
@Deprecated("Use downloader.Cache instead")
fun getDownloadDirectory(context: Context) =
Preferences.get<String>("dl_location").let {
if (it.isNotEmpty() && !it.startsWith("content"))
File(it)
else
context.getExternalFilesDir(null)!!
}
@Suppress("DEPRECATION")
@Deprecated("Use FileX instead")
fun File.isParentOf(another: File) =
another.absolutePath.startsWith(this.absolutePath)

View File

@@ -23,6 +23,9 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import xyz.quaver.Code import xyz.quaver.Code
@@ -93,14 +96,14 @@ fun GalleryBlock.formatDownloadFolder(): String =
formatMap.entries.fold(it) { str, (k, v) -> formatMap.entries.fold(it) { str, (k, v) ->
str.replace(k, v.invoke(this), true) str.replace(k, v.invoke(this), true)
} }
}.replace(Regex("""[*\\|"?><:/]"""), "") }.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
fun GalleryBlock.formatDownloadFolderTest(format: String): String = fun GalleryBlock.formatDownloadFolderTest(format: String): String =
format.let { format.let {
formatMap.entries.fold(it) { str, (k, v) -> formatMap.entries.fold(it) { str, (k, v) ->
str.replace(k, v.invoke(this), true) str.replace(k, v.invoke(this), true)
} }
}.replace(Regex("""[*\\|"?><:/]"""), "") }.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
val Reader.requestBuilders: List<Request.Builder> val Reader.requestBuilders: List<Request.Builder>
get() { get() {
@@ -129,3 +132,9 @@ fun String.ellipsize(n: Int): String =
this.slice(0 until n) + "" this.slice(0 until n) + ""
else else
this this
operator fun JsonElement.get(index: Int) =
this.jsonArray[index]
operator fun JsonElement.get(tag: String) =
this.jsonObject[tag]

View File

@@ -0,0 +1,68 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2020 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Request
import xyz.quaver.pupil.client
import java.io.IOException
import java.util.*
private val filesURL = "https://api.github.com/repos/tom5079/Pupil/git/trees/tags"
private val contentURL = "https://raw.githubusercontent.com/tom5079/Pupil/tags/"
var translations: Map<String, String> = run {
updateTranslations()
emptyMap()
}
private set
@Suppress("BlockingMethodInNonBlockingContext")
fun updateTranslations() = CoroutineScope(Dispatchers.IO).launch {
translations = emptyMap()
kotlin.runCatching {
translations = Json.decodeFromString<Map<String, String>>(client.newCall(
Request.Builder()
.url(contentURL + "${Preferences["tag_translation", ""].let { if (it.isEmpty()) Locale.getDefault().language else it }}.json")
.build()
).execute().also { if (it.code() != 200) return@launch }.body()?.use { it.string() } ?: return@launch).filterValues { it.isNotEmpty() }
}
}
fun getAvailableLanguages(): List<String> {
val languages = Locale.getISOLanguages()
val json = Json.parseToJsonElement(client.newCall(
Request.Builder()
.url(filesURL)
.build()
).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.string() } ?: return emptyList())
return listOf("en") + (json["tree"]?.jsonArray?.mapNotNull {
val name = it["path"]?.jsonPrimitive?.content?.takeWhile { c -> c != '.' }
languages.firstOrNull { code -> code.equals(name, ignoreCase = true) }
} ?: emptyList())
}

View File

@@ -27,13 +27,11 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import android.util.Log
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -52,7 +50,9 @@ import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.hitomi.getReader import xyz.quaver.hitomi.getReader
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.io.util.getChild import xyz.quaver.io.util.getChild
import xyz.quaver.io.util.* import xyz.quaver.io.util.readText
import xyz.quaver.io.util.writeBytes
import xyz.quaver.io.util.writeText
import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.client import xyz.quaver.pupil.client
@@ -160,7 +160,6 @@ fun checkUpdate(context: Context, force: Boolean = false) {
val msg = extractReleaseNote(update, Locale.getDefault()) val msg = extractReleaseNote(update, Locale.getDefault())
setMessage(Markwon.create(context).toMarkdown(msg)) setMessage(Markwon.create(context).toMarkdown(msg))
setPositiveButton(android.R.string.ok) { _, _ -> setPositiveButton(android.R.string.ok) { _, _ ->
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
//Cancel any download queued before //Cancel any download queued before
@@ -314,7 +313,7 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
) )
synchronized(Cache) { synchronized(Cache) {
Cache.delete(galleryID) Cache.delete(this@migrate, galleryID)
} }
downloadFolderMap[galleryID] = folder.name downloadFolderMap[galleryID] = folder.name

View File

@@ -47,22 +47,6 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.ContentLoadingProgressBar
style="?android:attr/progressBarStyle"
android:id="@+id/main_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"/>
<TextView
android:id="@+id/main_noresult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/main_no_result"
android:visibility="invisible"/>
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller <com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -86,6 +70,24 @@
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller> </com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
<androidx.core.widget.ContentLoadingProgressBar
style="?android:attr/progressBarStyle"
android:id="@+id/main_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"/>
<TextView
android:id="@+id/main_noresult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/main_no_result"
android:linksClickable="true"
android:visibility="invisible"/>
<com.github.clans.fab.FloatingActionMenu <com.github.clans.fab.FloatingActionMenu
android:id="@+id/main_fab" android:id="@+id/main_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@@ -40,7 +40,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="8dp"> android:padding="8dp">
<ImageView <com.github.piasy.biv.view.BigImageView
android:id="@+id/gallery_cover" android:id="@+id/gallery_cover"
android:layout_width="150dp" android:layout_width="150dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -107,15 +107,17 @@
</FrameLayout> </FrameLayout>
<ImageView <com.github.piasy.biv.view.BigImageView
android:id="@+id/galleryblock_thumbnail" android:id="@+id/galleryblock_thumbnail"
android:layout_width="150dp" android:layout_width="150dp"
android:layout_height="wrap_content" android:layout_height="0dp"
android:contentDescription="@string/galleryblock_thumbnail_description" android:contentDescription="@string/galleryblock_thumbnail_description"
android:adjustViewBounds="true" android:adjustViewBounds="true"
app:layout_constraintHeight_default="spread"
app:layout_constraintHeight_min="200dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar_layout" app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar_layout"
app:layout_constraintBottom_toBottomOf="@id/barrier"/> app:layout_constraintBottom_toTopOf="@id/barrier"/>
<TextView <TextView
style="@style/TextAppearance.AppCompat.Headline" style="@style/TextAppearance.AppCompat.Headline"
@@ -164,14 +166,7 @@
app:layout_constraintTop_toBottomOf="@id/galleryblock_type" app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" /> app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" />
<androidx.constraintlayout.widget.Barrier <xyz.quaver.pupil.ui.view.TagChipGroup
android:id="@+id/padding"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="galleryblock_language"/>
<com.google.android.material.chip.ChipGroup
android:id="@+id/galleryblock_tag_group" android:id="@+id/galleryblock_tag_group"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -179,7 +174,7 @@
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
app:chipSpacing="4dp" app:chipSpacing="4dp"
app:layout_constraintTop_toBottomOf="@id/padding" app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"/> app:layout_constraintRight_toRightOf="parent"/>
@@ -188,7 +183,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:barrierDirection="bottom" app:barrierDirection="bottom"
app:constraint_referenced_ids="galleryblock_thumbnail,galleryblock_tag_group"/> app:constraint_referenced_ids="galleryblock_thumbnail, galleryblock_tag_group"/>
<View <View
android:id="@+id/divider" android:id="@+id/divider"
@@ -202,9 +197,7 @@
android:id="@+id/galleryblock_id" android:id="@+id/galleryblock_id"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:layout_margin="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/divider" app:layout_constraintTop_toBottomOf="@id/divider"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"/> app:layout_constraintLeft_toLeftOf="parent"/>
@@ -217,8 +210,8 @@
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/divider" app:layout_constraintTop_toBottomOf="@id/divider"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="parent" /> app:layout_constraintRight_toRightOf="parent" />
<ImageView <ImageView
android:id="@+id/galleryblock_favorite" android:id="@+id/galleryblock_favorite"

View File

@@ -21,47 +21,51 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintHeight_max="2000dp" android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:paddingBottom="8dp"
android:background="@drawable/reader_item_boundary"> android:background="@drawable/reader_item_boundary">
<LinearLayout <androidx.constraintlayout.widget.Guideline
android:id="@+id/progress_layout" android:id="@+id/guideline_center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5"/>
<ProgressBar
android:id="@+id/reader_item_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:progressBarStyleHorizontal"
android:indeterminate="false"
android:progress="0"
android:max="100"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/guideline_center_vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<TextView
android:id="@+id/reader_index"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/guideline_center_vertical"
app:layout_constraintLeft_toLeftOf="@id/reader_item_progressbar"
app:layout_constraintRight_toRightOf="@id/reader_item_progressbar"
style="@style/TextAppearance.AppCompat.Caption"/>
<androidx.constraintlayout.widget.Group
android:id="@+id/progress_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
app:constraint_referenced_ids="reader_item_progressbar, reader_index"/>
<com.github.piasy.biv.view.BigImageView
android:id="@+id/image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent" app:initScaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent" app:optimizeDisplay="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="center"
android:orientation="vertical">
<ProgressBar
android:id="@+id/reader_item_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:progressBarStyleHorizontal"
android:indeterminate="false"
android:progress="0"
android:max="100"
android:visibility="visible"/>
<TextView
android:id="@+id/reader_index"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Caption"/>
</LinearLayout>
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/image"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>

View File

@@ -127,10 +127,8 @@
<string name="settings_lock_fingerprint_prompt">Pupil指紋ロック™</string> <string name="settings_lock_fingerprint_prompt">Pupil指紋ロック™</string>
<string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</string> <string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</string>
<string name="default_query_dialog_filter_loli">登場人物を全て18歳以上にする</string> <string name="default_query_dialog_filter_loli">登場人物を全て18歳以上にする</string>
<string name="settings_cache_disable">キャッシュを使用しない</string>
<string name="settings_download_when_cache_disable_warning">キャッシュを使用しないため、ダウンロードできません</string>
<string name="settings_user_id">ユーザーID</string> <string name="settings_user_id">ユーザーID</string>
<string name="settings_user_id_toast">ユーザーIDをクリップボードにコピーしました</string> <string name="copied_to_clipboard">クリップボードにコピーしました</string>
<string name="reader_fab_retry">リトライ</string> <string name="reader_fab_retry">リトライ</string>
<string name="reader_fab_auto">まばたき検知スクロール</string> <string name="reader_fab_auto">まばたき検知スクロール</string>
<string name="search_all">全てのギャラリーを対象に検索</string> <string name="search_all">全てのギャラリーを対象に検索</string>
@@ -152,4 +150,9 @@
<string name="camera_denied">カメラ権限が拒否されているため、まばたき検知使用できません</string> <string name="camera_denied">カメラ権限が拒否されているため、まばたき検知使用できません</string>
<string name="no_camera">この機器には前面カメラが装着されていません</string> <string name="no_camera">この機器には前面カメラが装着されていません</string>
<string name="error">エラー</string> <string name="error">エラー</string>
<string name="settings_cache_limit">キャッシュサイズ制限</string>
<string name="settings_cache_unlimited">制限なし</string>
<string name="settings_tag_translation">タグ言語</string>
<string name="settings_tag_translation_message">Githubにて翻訳に参加できます</string>
<string name="settings_concurrent_download">並列ダウンロード</string>
</resources> </resources>

View File

@@ -12,10 +12,10 @@
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string> <string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
<string name="settings_search_title">검색 설정</string> <string name="settings_search_title">검색 설정</string>
<string name="settings_title">설정</string> <string name="settings_title">설정</string>
<string name="update_notification_description">apk 다운로드중&#8230;</string> <string name="update_notification_description">업데이트 다운로드중&#8230;</string>
<string name="update_title">업데이트가 있습니다!</string> <string name="update_title">업데이트가 있습니다!</string>
<string name="warning">경고</string> <string name="warning">경고</string>
<string name="main_no_result">결과 없음</string> <string name="main_no_result">결과 없음\n해결법</string>
<string name="settings_miscellaneous_title">기타</string> <string name="settings_miscellaneous_title">기타</string>
<string name="settings_clear_history">기록 삭제</string> <string name="settings_clear_history">기록 삭제</string>
<string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string> <string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string>
@@ -127,10 +127,8 @@
<string name="settings_lock_fingerprint_prompt">Pupil 지문 인식™</string> <string name="settings_lock_fingerprint_prompt">Pupil 지문 인식™</string>
<string name="settings_lock_fingerprint_prompt_subtitle">힘세고 강한 지문 인식</string> <string name="settings_lock_fingerprint_prompt_subtitle">힘세고 강한 지문 인식</string>
<string name="default_query_dialog_filter_loli">판사님 저는 페도가 아닙니다</string> <string name="default_query_dialog_filter_loli">판사님 저는 페도가 아닙니다</string>
<string name="settings_cache_disable">캐시 비활성화</string>
<string name="settings_download_when_cache_disable_warning">캐시를 활성화 해야 다운로드를 진행할 수 있습니다</string>
<string name="settings_user_id">유저 ID</string> <string name="settings_user_id">유저 ID</string>
<string name="settings_user_id_toast">유저 ID를 클립보드에 복사했습니다</string> <string name="copied_to_clipboard">클립보드에 복사</string>
<string name="reader_fab_retry">재시도</string> <string name="reader_fab_retry">재시도</string>
<string name="reader_fab_auto">눈 깜빡임 감지 스크롤</string> <string name="reader_fab_auto">눈 깜빡임 감지 스크롤</string>
<string name="search_all">모든 갤러리 검색</string> <string name="search_all">모든 갤러리 검색</string>
@@ -152,4 +150,9 @@
<string name="camera_denied">카메라 권한이 거부되었기 때문에 눈 깜빡임 감지가 불가능합니다</string> <string name="camera_denied">카메라 권한이 거부되었기 때문에 눈 깜빡임 감지가 불가능합니다</string>
<string name="no_camera">이 장치에는 전면 카메라가 없습니다</string> <string name="no_camera">이 장치에는 전면 카메라가 없습니다</string>
<string name="error">오류</string> <string name="error">오류</string>
<string name="settings_cache_limit">캐시 크기 제한</string>
<string name="settings_cache_unlimited">무제한</string>
<string name="settings_tag_translation">태그 언어</string>
<string name="settings_tag_translation_message">Github에서 번역에 참여하세요</string>
<string name="settings_concurrent_download">병렬 다운로드</string>
</resources> </resources>

View File

@@ -2,6 +2,7 @@
<resources> <resources>
<string-array name="settings_galleries_per_page"> <string-array name="settings_galleries_per_page">
<item>5</item>
<item>10</item> <item>10</item>
<item>25</item> <item>25</item>
<item>50</item> <item>50</item>
@@ -58,4 +59,24 @@
<item>SOCKS</item> <item>SOCKS</item>
</string-array> </string-array>
<string-array name="cache_size">
<item>0</item>
<item>1</item>
<item>2</item>
<item>4</item>
<item>8</item>
<item>16</item>
<item>32</item>
</string-array>
<string-array name="cache_size_text">
<item>@string/settings_cache_unlimited</item>
<item>1G</item>
<item>2G</item>
<item>4G</item>
<item>8G</item>
<item>16G</item>
<item>32G</item>
</string-array>
</resources> </resources>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2020 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<declare-styleable name="TagChipGroup">
<attr name="maxTag" format="integer"/>
</declare-styleable>
</resources>

View File

@@ -10,4 +10,6 @@
<dimen name="thumb_width">24dp</dimen> <dimen name="thumb_width">24dp</dimen>
<dimen name="thumb_height">72dp</dimen> <dimen name="thumb_height">72dp</dimen>
<dimen name="thumbnail_page_height">300dp</dimen>
</resources> </resources>

View File

@@ -9,6 +9,10 @@
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string> <string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string>
<string name="discord" translatable="false">https://discord.gg/Stj4b5v</string> <string name="discord" translatable="false">https://discord.gg/Stj4b5v</string>
<!-- Korean only -->
<string name="https_text" translatable="false">해결법</string>
<string name="https" translatable="false">https://bit.ly/34dUBwy</string>
<string name="backup_url" translatable="false">http://ix.io/</string> <string name="backup_url" translatable="false">http://ix.io/</string>
<string name="main_settings" translatable="false">Settings</string> <string name="main_settings" translatable="false">Settings</string>
@@ -17,6 +21,8 @@
<string name="reader_imageview_description" translatable="false">Content ImageView</string> <string name="reader_imageview_description" translatable="false">Content ImageView</string>
<string name="page_indicator_placeholder" translatable="false">-/-</string> <string name="page_indicator_placeholder" translatable="false">-/-</string>
<string name="galleryblock_artist_with_group" translatable="false">%s (%s)</string>
<!-- Translate needed down here --> <!-- Translate needed down here -->
<string name="warning">Warning</string> <string name="warning">Warning</string>
@@ -24,6 +30,8 @@
<string name="ignore_update">Ignore</string> <string name="ignore_update">Ignore</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="channel_download">Download</string> <string name="channel_download">Download</string>
<string name="channel_download_description">Shows download status</string> <string name="channel_download_description">Shows download status</string>
@@ -155,8 +163,9 @@
<string name="settings_download_folder_available">%s available</string> <string name="settings_download_folder_available">%s available</string>
<string name="settings_download_folder_custom">Custom Location</string> <string name="settings_download_folder_custom">Custom Location</string>
<string name="settings_download_folder_not_writable">This folder is not writable. Please select another folder.</string> <string name="settings_download_folder_not_writable">This folder is not writable. Please select another folder.</string>
<string name="settings_cache_disable">Disable Cache</string> <string name="settings_cache_limit">Cache Limit</string>
<string name="settings_download_when_cache_disable_warning">Download is disabled when the cache is disabled</string> <string name="settings_cache_unlimited">Unlimited</string>
<string name="settings_nomedia_title">Hide image from gallery</string>
<string name="settings_low_quality">Low quality images</string> <string name="settings_low_quality">Low quality images</string>
<string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string> <string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string>
@@ -168,6 +177,9 @@
<!-- SETTINGS/MISCELLANEOUS --> <!-- SETTINGS/MISCELLANEOUS -->
<string name="settings_miscellaneous_title">Miscellaneous</string> <string name="settings_miscellaneous_title">Miscellaneous</string>
<string name="settings_tag_translation">Tag Language</string>
<string name="settings_concurrent_download">Concurrent Download</string>
<string name="settings_tag_translation_message">Participate in translation on Github</string>
<string name="settings_mirror_summary">Load images from mirrors</string> <string name="settings_mirror_summary">Load images from mirrors</string>
<string name="settings_proxy_title">Proxy</string> <string name="settings_proxy_title">Proxy</string>
<string name="settings_rtl">Turn pages Right-to-Left</string> <string name="settings_rtl">Turn pages Right-to-Left</string>
@@ -175,10 +187,8 @@
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string> <string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>
<string name="settings_dark_mode_title">Dark mode</string> <string name="settings_dark_mode_title">Dark mode</string>
<string name="settings_dark_mode_summary">Protect yourself against light attacks!</string> <string name="settings_dark_mode_summary">Protect yourself against light attacks!</string>
<string name="settings_nomedia_title">Hide image from gallery</string>
<string name="settings_import_old_galleries">Import old galleries</string> <string name="settings_import_old_galleries">Import old galleries</string>
<string name="settings_user_id">User ID</string> <string name="settings_user_id">User ID</string>
<string name="settings_user_id_toast">User ID is copied to clipboard</string>
<string name="settings_oss">Open Source Notice</string> <string name="settings_oss">Open Source Notice</string>
<!-- MANAGE FAVORITES --> <!-- MANAGE FAVORITES -->

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference <Preference
app:key="app_version" app:key="app_version"
@@ -44,9 +43,13 @@
app:key="download_folder" app:key="download_folder"
app:title="@string/settings_download_folder"/> app:title="@string/settings_download_folder"/>
<SwitchPreferenceCompat <ListPreference
app:key="cache_disable" app:key="cache_limit"
app:title="@string/settings_cache_disable"/> app:title="@string/settings_cache_limit"
app:entries="@array/cache_size_text"
app:entryValues="@array/cache_size"
app:defaultValue="8"
app:useSimpleSummaryProvider="true"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="nomedia" app:key="nomedia"
@@ -72,6 +75,11 @@
<PreferenceCategory <PreferenceCategory
app:title="@string/settings_miscellaneous_title"> app:title="@string/settings_miscellaneous_title">
<ListPreference
app:key="tag_translation"
app:title="@string/settings_tag_translation"
app:useSimpleSummaryProvider="true"/>
<Preference <Preference
app:key="mirrors" app:key="mirrors"
app:title="@string/settings_mirror_title" app:title="@string/settings_mirror_title"

View File

@@ -6,25 +6,26 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.3' classpath "com.google.gms:google-services:4.3.3"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0' classpath "com.google.firebase:firebase-crashlytics-gradle:2.3.0"
classpath 'com.google.firebase:perf-plugin:1.3.1' classpath "com.google.firebase:perf-plugin:1.3.2"
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2' classpath "com.google.android.gms:oss-licenses-plugin:0.10.2"
} }
} }
allprojects { allprojects {
repositories { repositories {
maven { url "http://dl.bintray.com/piasy/maven" }
google() google()
jcenter() jcenter()
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url 'https://guardian.github.com/maven/repo-releases' } maven { url "https://guardian.github.com/maven/repo-releases" }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.configureondemand=true org.gradle.configureondemand=true
org.gradle.caching=true
kotlin.code.style=official kotlin.code.style=official
android.enableJetifier=true android.enableJetifier=true
android.useAndroidX=true android.useAndroidX=true

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Thu Jun 18 15:48:09 KST 2020 #Tue Oct 13 22:37:11 KST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

51
gradlew vendored
View File

@@ -1,5 +1,21 @@
#!/usr/bin/env sh #!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
############################################################################## ##############################################################################
## ##
## Gradle start up script for UN*X ## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS="" DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
@@ -109,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi fi
# For Cygwin, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if $cygwin ; then if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"` JAVACMD=`cygpath --unix "$JAVACMD"`
@@ -138,19 +154,19 @@ if $cygwin ; then
else else
eval `echo args$i`="\"$arg\"" eval `echo args$i`="\"$arg\""
fi fi
i=$((i+1)) i=`expr $i + 1`
done done
case $i in case $i in
(0) set -- ;; 0) set -- ;;
(1) set -- "$args0" ;; 1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;; 2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;; 3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
@@ -159,14 +175,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " " echo " "
} }
APP_ARGS=$(save "$@") APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

18
gradlew.bat vendored
View File

@@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS= set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome