Compare commits
51 Commits
5.1-hotfix
...
5.1.4-hotf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b165a2308f | ||
|
|
8757b08cd2 | ||
|
|
3800543fba | ||
|
|
02ef60c818 | ||
|
|
88f3b30266 | ||
|
|
9203dc0112 | ||
|
|
4c683bec68 | ||
|
|
0cfd1eb453 | ||
|
|
19744dab37 | ||
|
|
12d58e5aa7 | ||
|
|
e46dd37a26 | ||
|
|
49c3ebc36b | ||
|
|
11e9bc2235 | ||
|
|
3029b3bf0e | ||
|
|
9a6c6f67ce | ||
|
|
a6ed0baef2 | ||
|
|
d3b43d80da | ||
|
|
46d4316d49 | ||
|
|
ade2864351 | ||
|
|
365fc56e9d | ||
|
|
54a5cd21ea | ||
|
|
38c0399b09 | ||
|
|
2b67858453 | ||
|
|
87fdbdbb6e | ||
|
|
bab77a4116 | ||
|
|
d20756ab96 | ||
|
|
dc75a753c3 | ||
|
|
4712d47903 | ||
|
|
c5561801e1 | ||
|
|
5c259fa07a | ||
|
|
60e8b18702 | ||
|
|
a8317824a9 | ||
|
|
0c3c78cc72 | ||
|
|
cfd4a8faac | ||
|
|
7f3fb0db0d | ||
|
|
385d3f0d1b | ||
|
|
8fa6bd12a2 | ||
|
|
57c2004e46 | ||
|
|
c6b069bbfb | ||
|
|
c18bffd08f | ||
|
|
47ec181439 | ||
|
|
90ad40b1b7 | ||
|
|
4d3f20cf98 | ||
|
|
86df9d52bc | ||
|
|
1bd025e070 | ||
|
|
86ee239c71 | ||
|
|
27d0c01e1f | ||
|
|
7a9507be01 | ||
|
|
1490035893 | ||
|
|
a6afcb0ed0 | ||
|
|
ea7e8584cb |
6
.idea/compiler.xml
generated
Normal file
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8" />
|
||||
</component>
|
||||
</project>
|
||||
5
.idea/jarRepositories.xml
generated
5
.idea/jarRepositories.xml
generated
@@ -66,5 +66,10 @@
|
||||
<option name="name" value="maven3" />
|
||||
<option name="url" value="https://dl.bintray.com/tom5079/maven" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven3" />
|
||||
<option name="name" value="maven3" />
|
||||
<option name="url" value="http://dl.bintray.com/piasy/maven" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
19
README.md
19
README.md
@@ -1,18 +1,12 @@
|
||||
# Pupil
|
||||
|
||||

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

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

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

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

|
||||
|
||||
# Installation
|
||||
|
||||
@@ -26,4 +20,7 @@ or Build app yourself
|
||||
|
||||
# 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)
|
||||
|
||||
107
app/build.gradle
107
app/build.gradle
@@ -1,27 +1,44 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "kotlin-android"
|
||||
apply plugin: "kotlin-kapt"
|
||||
apply plugin: "kotlin-android-extensions"
|
||||
apply plugin: "kotlinx-serialization"
|
||||
apply plugin: "com.google.android.gms.oss-licenses-plugin"
|
||||
|
||||
if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
|
||||
logger.lifecycle("Firebase Enabled")
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
apply plugin: 'com.google.firebase.crashlytics'
|
||||
apply plugin: 'com.google.firebase.firebase-perf'
|
||||
apply plugin: "com.google.gms.google-services"
|
||||
apply plugin: "com.google.firebase.crashlytics"
|
||||
apply plugin: "com.google.firebase.firebase-perf"
|
||||
} else {
|
||||
logger.lifecycle("Firebase Disabled")
|
||||
}
|
||||
|
||||
ext {
|
||||
okhttp_version = "3.12.12"
|
||||
}
|
||||
|
||||
configurations {
|
||||
all {
|
||||
resolutionStrategy {
|
||||
eachDependency { DependencyResolveDetails details ->
|
||||
if (details.requested.group == "com.squareup.okhttp3" && details.requested.name == "okhttp") {
|
||||
// OkHttp drops support before 5.0 since 3.13.0
|
||||
details.useVersion okhttp_version
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
defaultConfig {
|
||||
applicationId "xyz.quaver.pupil"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 60
|
||||
versionName "5.1-hotfix3"
|
||||
versionCode 63
|
||||
versionName "5.1.4-hotfix1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
@@ -34,8 +51,7 @@ android {
|
||||
applicationIdSuffix ".debug"
|
||||
versionNameSuffix "-DEBUG"
|
||||
|
||||
buildConfigField('Boolean', 'CENSOR', 'false')
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
|
||||
ext.enableCrashlytics = false
|
||||
ext.alwaysUpdateBuildId = false
|
||||
@@ -44,69 +60,72 @@ android {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
|
||||
buildConfigField('Boolean', 'CENSOR', 'false')
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
|
||||
freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental"
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
buildToolsVersion = '29.0.3'
|
||||
buildToolsVersion = "29.0.3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0-RC2"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.activity:activity-ktx:1.2.0-alpha08"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.0-alpha08"
|
||||
implementation "androidx.activity:activity-ktx:1.2.0-beta01"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.1"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.2"
|
||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||
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.google.android.material:material:1.3.0-alpha02"
|
||||
implementation "com.google.firebase:firebase-core:17.5.0"
|
||||
implementation "com.google.firebase:firebase-analytics:17.5.0"
|
||||
implementation "com.google.firebase:firebase-crashlytics:17.2.1"
|
||||
implementation "com.google.firebase:firebase-perf:19.0.8"
|
||||
|
||||
implementation "com.google.android.material:material:1.3.0-alpha03"
|
||||
|
||||
implementation "com.google.firebase:firebase-core:17.5.1"
|
||||
implementation "com.google.firebase:firebase-analytics:17.6.0"
|
||||
implementation "com.google.firebase:firebase-crashlytics:17.2.2"
|
||||
implementation "com.google.firebase:firebase-perf:19.0.9"
|
||||
|
||||
implementation "com.google.android.gms:play-services-oss-licenses:17.0.0"
|
||||
implementation "com.google.android.gms:play-services-mlkit-face-detection:16.1.1"
|
||||
|
||||
implementation "com.github.clans:fab:1.6.4"
|
||||
|
||||
//implementation "com.quiph.ui:recyclerviewfastscroller:0.2.1"
|
||||
|
||||
implementation 'com.github.piasy:BigImageViewer:1.6.7'
|
||||
implementation 'com.github.piasy:FrescoImageLoader:1.6.7'
|
||||
implementation 'com.github.piasy:FrescoImageViewFactory:1.6.7'
|
||||
|
||||
//noinspection GradleDependency
|
||||
implementation "com.squareup.okhttp3:okhttp:3.12.12"
|
||||
implementation "com.github.bumptech.glide:glide:4.11.0"
|
||||
implementation ("com.github.bumptech.glide:okhttp3-integration:4.11.0") {
|
||||
transitive = false
|
||||
}
|
||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
|
||||
transitive = false
|
||||
}
|
||||
implementation "com.github.bumptech.glide:annotations:4.11.0"
|
||||
annotationProcessor "com.github.bumptech.glide:compiler:4.11.0"
|
||||
kapt "com.github.bumptech.glide:compiler:4.11.0"
|
||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
|
||||
transitive = false
|
||||
}
|
||||
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
|
||||
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.gu:option:1.3"
|
||||
|
||||
implementation "com.andrognito.patternlockview:patternlockview:1.0.0"
|
||||
//implementation "com.andrognito.pinlockview:pinlockview:2.1.0"
|
||||
|
||||
implementation "ru.noties.markwon:core:3.1.0"
|
||||
|
||||
implementation "xyz.quaver:libpupil:1.7.2"
|
||||
implementation "xyz.quaver:documentfilex:0.2.15"
|
||||
implementation "xyz.quaver:floatingsearchview:1.0.5"
|
||||
implementation "xyz.quaver:documentfilex:0.3.1"
|
||||
implementation "xyz.quaver:floatingsearchview:1.0.7"
|
||||
|
||||
testImplementation "junit:junit:4.13"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
||||
androidTestImplementation "androidx.test:rules:1.3.0"
|
||||
|
||||
18
app/proguard-rules.pro
vendored
18
app/proguard-rules.pro
vendored
@@ -22,21 +22,6 @@
|
||||
|
||||
-dontobfuscate
|
||||
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
||||
<init>(...);
|
||||
}
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
|
||||
*** rewind();
|
||||
}
|
||||
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep class com.bumptech.glide.GeneratedAppGlideModuleImpl
|
||||
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.SerializationKt
|
||||
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
|
||||
@@ -47,5 +32,4 @@
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
|
||||
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
||||
-keep class xyz.quaver.pupil.util.Preferences
|
||||
-keep class xyz.quaver.pupil.ui.fragment.ManageStorageFragment
|
||||
@@ -11,8 +11,8 @@
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"properties": [],
|
||||
"versionCode": 60,
|
||||
"versionName": "5.1-hotfix3",
|
||||
"versionCode": 63,
|
||||
"versionName": "5.1.4-hotfix1",
|
||||
"enabled": true,
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
android:theme="@style/AppTheme"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:replace="android:theme"
|
||||
android:largeHeap="true"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<meta-data
|
||||
|
||||
@@ -26,14 +26,13 @@ import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.github.piasy.biv.BigImageViewer
|
||||
import com.github.piasy.biv.loader.fresco.FrescoImageLoader
|
||||
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
|
||||
import com.google.android.gms.common.GooglePlayServicesRepairableException
|
||||
import com.google.android.gms.security.ProviderInstaller
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
@@ -113,25 +112,16 @@ class Pupil : Application() {
|
||||
Preferences.remove("download_folder")
|
||||
}
|
||||
|
||||
if (!Preferences["reset_secure", false]) {
|
||||
Preferences["security_mode"] = false
|
||||
Preferences["reset_secure"] = true
|
||||
}
|
||||
|
||||
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
|
||||
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
|
||||
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
|
||||
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
|
||||
|
||||
if (Preferences["new_history"]) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
histories.reversed().let {
|
||||
histories.clear()
|
||||
histories.addAll(it)
|
||||
}
|
||||
favorites.reversed().let {
|
||||
favorites.clear()
|
||||
favorites.addAll(it)
|
||||
}
|
||||
}
|
||||
Preferences["new_history"] = true
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG)
|
||||
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
|
||||
|
||||
@@ -143,6 +133,8 @@ class Pupil : Application() {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
BigImageViewer.initialize(FrescoImageLoader.with(this))
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Pupil, Hitomi.la viewer for Android
|
||||
* Copyright (C) 2020 tom5079
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.content.Context
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import java.io.InputStream
|
||||
|
||||
@GlideModule
|
||||
class PupilGlideModule : AppGlideModule() {
|
||||
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
registry.append(
|
||||
GlideUrl::class.java,
|
||||
InputStream::class.java,
|
||||
OkHttpUrlLoader.Factory(client)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,42 +26,30 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.view.children
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.daimajia.swipe.SwipeLayout
|
||||
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
||||
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
||||
import com.github.piasy.biv.loader.ImageLoader
|
||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.getGallery
|
||||
import xyz.quaver.hitomi.getReader
|
||||
import xyz.quaver.io.util.getChild
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.favoriteTags
|
||||
import xyz.quaver.pupil.favorites
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.ui.view.TagChip
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import xyz.quaver.pupil.util.wordCapitalize
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.concurrent.schedule
|
||||
import java.io.File
|
||||
|
||||
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||
class GalleryBlockAdapter(private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
||||
|
||||
enum class ViewType {
|
||||
NEXT,
|
||||
@@ -69,18 +57,26 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
PREV
|
||||
}
|
||||
|
||||
val timer = Timer()
|
||||
|
||||
var updateAll = true
|
||||
var thin: Boolean = Preferences["thin"]
|
||||
|
||||
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
var timerTask: TimerTask? = null
|
||||
private var galleryID: Int = 0
|
||||
|
||||
private fun updateProgress(context: Context, galleryID: Int) {
|
||||
init {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
while (updateAll) {
|
||||
updateProgress(view.context)
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProgress(context: Context) {
|
||||
val cache = Cache.getInstance(context, galleryID)
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
if (cache.metadata.reader == null || Preferences["cache_disable"]) {
|
||||
if (cache.metadata.reader == null) {
|
||||
view.galleryblock_progressbar_layout.visibility = View.GONE
|
||||
view.galleryblock_progress_complete.visibility = View.INVISIBLE
|
||||
return@launch
|
||||
@@ -129,9 +125,14 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
}
|
||||
|
||||
fun bind(galleryID: Int) {
|
||||
this.galleryID = galleryID
|
||||
updateProgress(view.context)
|
||||
|
||||
val cache = Cache.getInstance(view.context, galleryID)
|
||||
|
||||
val galleryBlock = cache.metadata.galleryBlock ?: return
|
||||
val galleryBlock = runBlocking {
|
||||
cache.getGalleryBlock()
|
||||
} ?: return
|
||||
|
||||
with(view) {
|
||||
val resources = context.resources
|
||||
@@ -144,63 +145,61 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
val artists = galleryBlock.artists
|
||||
val series = galleryBlock.series
|
||||
|
||||
if (thin)
|
||||
galleryblock_thumbnail.layoutParams.width = context.resources.getDimensionPixelSize(
|
||||
R.dimen.galleryblock_thumbnail_thin
|
||||
)
|
||||
|
||||
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)
|
||||
galleryblock_thumbnail.apply {
|
||||
setOnClickListener {
|
||||
view.performClick()
|
||||
}
|
||||
setOnLongClickListener {
|
||||
view.performLongClick()
|
||||
}
|
||||
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||
setImageLoaderCallback(object: ImageLoader.Callback {
|
||||
override fun onFail(error: Exception?) {
|
||||
Cache.getInstance(context, galleryID).let { cache ->
|
||||
cache.cacheFolder.getChild(".thumbnail").let { if (it.exists()) it.delete() }
|
||||
cache.downloadFolder?.getChild(".thumbnail")?.let { if (it.exists()) it.delete() }
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
with(galleryblock_artist) {
|
||||
text = artists.joinToString(", ") { it.wordCapitalize() }
|
||||
text = artists.joinToString { it.wordCapitalize() }
|
||||
visibility = when {
|
||||
artists.isNotEmpty() -> View.VISIBLE
|
||||
else -> View.GONE
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val gallery = runCatching {
|
||||
getGallery(galleryID)
|
||||
}.getOrNull()
|
||||
|
||||
if (gallery?.groups?.isNotEmpty() != true)
|
||||
return@launch
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
text = context.getString(
|
||||
R.string.galleryblock_artist_with_group,
|
||||
artists.joinToString { it.wordCapitalize() },
|
||||
gallery.groups.joinToString { it.wordCapitalize() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
with(galleryblock_series) {
|
||||
text =
|
||||
@@ -222,27 +221,32 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
}
|
||||
}
|
||||
|
||||
galleryblock_tag_group.removeAllViews()
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
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 {
|
||||
TagChip(context, Tag.parse(it)).apply {
|
||||
setOnClickListener { view ->
|
||||
for (callback in onChipClickedHandler)
|
||||
callback.invoke((view as TagChip).tag)
|
||||
}
|
||||
with(galleryblock_tag_group) {
|
||||
onClickListener = {
|
||||
onChipClickedHandler.forEach { callback ->
|
||||
callback.invoke(it)
|
||||
}
|
||||
}.let { launch(Dispatchers.Main) { it.forEach { galleryblock_tag_group.addView(it) } } }
|
||||
}
|
||||
|
||||
tags.clear()
|
||||
tags.addAll(
|
||||
galleryBlock.relatedTags.sortedBy {
|
||||
val tag = Tag.parse(it)
|
||||
|
||||
if (favoriteTags.contains(tag))
|
||||
-1
|
||||
else
|
||||
when(Tag.parse(it).area) {
|
||||
"female" -> 0
|
||||
"male" -> 1
|
||||
else -> 2
|
||||
}
|
||||
}.map {
|
||||
Tag.parse(it)
|
||||
}
|
||||
)
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
galleryblock_id.text = galleryBlock.id.toString()
|
||||
@@ -286,8 +290,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
|
||||
// Make some views invisible to make it thinner
|
||||
if (thin) {
|
||||
galleryblock_language.visibility = View.GONE
|
||||
galleryblock_type.visibility = View.GONE
|
||||
galleryblock_tag_group.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
@@ -382,15 +384,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
|
||||
if (holder is GalleryViewHolder) {
|
||||
holder.timerTask?.cancel()
|
||||
holder.timerTask = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() =
|
||||
galleries.size +
|
||||
(if (showNext) 1 else 0) +
|
||||
|
||||
@@ -18,57 +18,94 @@
|
||||
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.content.Context
|
||||
import android.graphics.DiscretePathEffect
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.LazyHeaders
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.facebook.drawee.backends.pipeline.Fresco
|
||||
import com.facebook.drawee.controller.BaseControllerListener
|
||||
import com.facebook.drawee.drawable.ScalingUtils
|
||||
import com.facebook.drawee.interfaces.DraweeController
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import com.facebook.imagepipeline.image.ImageInfo
|
||||
import com.github.piasy.biv.view.BigImageView
|
||||
import com.github.piasy.biv.view.ImageShownCallback
|
||||
import com.github.piasy.biv.view.ImageViewFactory
|
||||
import kotlinx.android.synthetic.main.item_reader.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import xyz.quaver.Code
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.getReferer
|
||||
import xyz.quaver.hitomi.imageUrlFromImage
|
||||
import xyz.quaver.hiyobi.createImgList
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.services.DownloadService
|
||||
import xyz.quaver.pupil.ui.ReaderActivity
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
import java.io.File
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ReaderAdapter(private val activity: ReaderActivity,
|
||||
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||
class ReaderAdapter(
|
||||
private val activity: ReaderActivity,
|
||||
private val galleryID: Int
|
||||
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||
|
||||
var reader: Reader? = null
|
||||
val timer = Timer()
|
||||
|
||||
private val glide = Glide.with(activity)
|
||||
|
||||
var isFullScreen = false
|
||||
|
||||
var onItemClickListener : ((Int) -> (Unit))? = null
|
||||
var onItemClickListener : (() -> (Unit))? = null
|
||||
|
||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
|
||||
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
fun clear() {
|
||||
view.image.mainView.let {
|
||||
when (it) {
|
||||
is SubsamplingScaleImageView ->
|
||||
it.recycle()
|
||||
is SimpleDraweeView ->
|
||||
it.controller = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return LayoutInflater.from(parent.context).inflate(
|
||||
R.layout.item_reader, parent, false
|
||||
).let {
|
||||
with(it) {
|
||||
image.setImageViewFactory(FrescoImageViewFactory().apply {
|
||||
updateView = { imageInfo ->
|
||||
it.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
dimensionRatio = "${imageInfo.width}:${imageInfo.height}"
|
||||
}
|
||||
}
|
||||
})
|
||||
image.setImageShownCallback(object : ImageShownCallback {
|
||||
override fun onMainImageShown() {
|
||||
it.image.mainView.let { v ->
|
||||
when (v) {
|
||||
is SubsamplingScaleImageView ->
|
||||
if (!isFullScreen) it.image.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onThumbnailShown() {}
|
||||
})
|
||||
image.setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||
image.setOnClickListener {
|
||||
this.performClick()
|
||||
}
|
||||
setOnClickListener {
|
||||
onItemClickListener?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
ViewHolder(it)
|
||||
}
|
||||
}
|
||||
@@ -80,126 +117,134 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
||||
if (cache == null)
|
||||
cache = Cache.getInstance(holder.view.context, galleryID)
|
||||
|
||||
if (isFullScreen) {
|
||||
holder.view.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||
if (!isFullScreen) {
|
||||
holder.view.setBackgroundResource(R.drawable.reader_item_boundary)
|
||||
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = 0
|
||||
dimensionRatio =
|
||||
"${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
||||
}
|
||||
} else {
|
||||
holder.view.layoutParams.height = ConstraintLayout.LayoutParams.WRAP_CONTENT
|
||||
|
||||
(holder.view.progress_layout.layoutParams as ConstraintLayout.LayoutParams)
|
||||
.dimensionRatio = "${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
||||
}
|
||||
|
||||
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
||||
onItemClickListener?.invoke(position)
|
||||
}
|
||||
|
||||
holder.view.setOnClickListener {
|
||||
onItemClickListener?.invoke(position)
|
||||
holder.view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
holder.view.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||
dimensionRatio = null
|
||||
}
|
||||
holder.view.background = null
|
||||
}
|
||||
|
||||
holder.view.reader_index.text = (position+1).toString()
|
||||
|
||||
if (Preferences["cache_disable"]) {
|
||||
val lowQuality: Boolean = Preferences["low_quality"]
|
||||
val image = cache!!.getImage(position)
|
||||
val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
|
||||
|
||||
val url = when (reader!!.code) {
|
||||
Code.HITOMI ->
|
||||
GlideUrl(
|
||||
imageUrlFromImage(
|
||||
galleryID,
|
||||
reader!!.galleryInfo.files[position],
|
||||
!lowQuality
|
||||
)
|
||||
, LazyHeaders.Builder().addHeader("Referer", getReferer(galleryID)).build())
|
||||
Code.HIYOBI ->
|
||||
GlideUrl(createImgList(galleryID, reader!!, lowQuality)[position].path)
|
||||
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)
|
||||
}
|
||||
if (progress?.isInfinite() == true && image != null) {
|
||||
holder.view.progress_group.visibility = View.INVISIBLE
|
||||
holder.view.image.showImage(image.uri)
|
||||
} else {
|
||||
val image = cache!!.getImage(position)
|
||||
val progress = activity.downloader?.progress?.get(galleryID)?.get(position)
|
||||
holder.view.progress_group.visibility = View.VISIBLE
|
||||
holder.view.reader_item_progressbar.progress =
|
||||
if (progress?.isInfinite() == true)
|
||||
100
|
||||
else
|
||||
progress?.roundToInt() ?: 0
|
||||
|
||||
if (progress?.isInfinite() == true && image != null) {
|
||||
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
||||
holder.clear()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
glide
|
||||
.load(image.uri)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
delay(1000)
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
|
||||
|
||||
override fun onViewRecycled(holder: ViewHolder) {
|
||||
holder.clear()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FrescoImageViewFactory : ImageViewFactory() {
|
||||
var updateView: ((ImageInfo) -> Unit)? = null
|
||||
|
||||
override fun createAnimatedImageView(
|
||||
context: Context, imageType: Int,
|
||||
initScaleType: Int
|
||||
): View {
|
||||
val view = SimpleDraweeView(context)
|
||||
view.hierarchy.actualImageScaleType = scaleType(initScaleType)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun loadAnimatedContent(
|
||||
view: View, imageType: Int,
|
||||
imageFile: File
|
||||
) {
|
||||
if (view is SimpleDraweeView) {
|
||||
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||
.setUri(Uri.parse("file://" + imageFile.absolutePath))
|
||||
.setAutoPlayAnimations(true)
|
||||
.setControllerListener(object: BaseControllerListener<ImageInfo>() {
|
||||
override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
|
||||
imageInfo?.let { updateView?.invoke(it) }
|
||||
}
|
||||
|
||||
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
|
||||
imageInfo?.let { updateView?.invoke(it) }
|
||||
}
|
||||
})
|
||||
.build()
|
||||
view.controller = controller
|
||||
}
|
||||
}
|
||||
|
||||
override fun createThumbnailView(
|
||||
context: Context,
|
||||
scaleType: ImageView.ScaleType, willLoadFromNetwork: Boolean
|
||||
): View {
|
||||
return if (willLoadFromNetwork) {
|
||||
val thumbnailView = SimpleDraweeView(context)
|
||||
thumbnailView.hierarchy.actualImageScaleType = scaleType(scaleType)
|
||||
thumbnailView
|
||||
} else {
|
||||
super.createThumbnailView(context, scaleType, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadThumbnailContent(view: View, thumbnail: Uri) {
|
||||
if (view is SimpleDraweeView) {
|
||||
val controller: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||
.setUri(thumbnail)
|
||||
.build()
|
||||
view.controller = controller
|
||||
}
|
||||
}
|
||||
|
||||
private fun scaleType(value: Int): ScalingUtils.ScaleType {
|
||||
return when (value) {
|
||||
BigImageView.INIT_SCALE_TYPE_CENTER -> ScalingUtils.ScaleType.CENTER
|
||||
BigImageView.INIT_SCALE_TYPE_CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
|
||||
BigImageView.INIT_SCALE_TYPE_CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
|
||||
BigImageView.INIT_SCALE_TYPE_FIT_END -> ScalingUtils.ScaleType.FIT_END
|
||||
BigImageView.INIT_SCALE_TYPE_FIT_START -> ScalingUtils.ScaleType.FIT_START
|
||||
BigImageView.INIT_SCALE_TYPE_FIT_XY -> ScalingUtils.ScaleType.FIT_XY
|
||||
BigImageView.INIT_SCALE_TYPE_FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
|
||||
else -> ScalingUtils.ScaleType.FIT_CENTER
|
||||
}
|
||||
}
|
||||
|
||||
private fun scaleType(scaleType: ImageView.ScaleType): ScalingUtils.ScaleType {
|
||||
return when (scaleType) {
|
||||
ImageView.ScaleType.CENTER -> ScalingUtils.ScaleType.CENTER
|
||||
ImageView.ScaleType.CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
|
||||
ImageView.ScaleType.CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
|
||||
ImageView.ScaleType.FIT_END -> ScalingUtils.ScaleType.FIT_END
|
||||
ImageView.ScaleType.FIT_START -> ScalingUtils.ScaleType.FIT_START
|
||||
ImageView.ScaleType.FIT_XY -> ScalingUtils.ScaleType.FIT_XY
|
||||
ImageView.ScaleType.FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
|
||||
else -> ScalingUtils.ScaleType.FIT_CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,32 +18,35 @@
|
||||
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import com.github.piasy.biv.view.BigImageView
|
||||
import xyz.quaver.pupil.R
|
||||
|
||||
class ThumbnailAdapter(private val glide: RequestManager, var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
||||
class ThumbnailAdapter(var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
||||
|
||||
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
|
||||
class ViewHolder(val view: BigImageView) : RecyclerView.ViewHolder(view) {
|
||||
fun clear() {
|
||||
view.ssiv?.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(ImageView(parent.context))
|
||||
return ViewHolder(BigImageView(parent.context).apply {
|
||||
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||
})
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
glide
|
||||
.load(thumbnails[position])
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.apply {
|
||||
if (BuildConfig.CENSOR)
|
||||
override(5, 8)
|
||||
}
|
||||
.into(holder.view)
|
||||
holder.view.showImage(Uri.parse(thumbnails[position]))
|
||||
}
|
||||
|
||||
override fun getItemCount() = thumbnails.size
|
||||
|
||||
override fun onViewRecycled(holder: ViewHolder) {
|
||||
holder.clear()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,17 +21,19 @@ package xyz.quaver.pupil.adapters
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.RequestManager
|
||||
import kotlin.math.min
|
||||
|
||||
class ThumbnailPageAdapter(private val glide: RequestManager, private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailPageAdapter.ViewHolder>() {
|
||||
class ThumbnailPageAdapter(private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailPageAdapter.ViewHolder>() {
|
||||
|
||||
class ViewHolder(val view: RecyclerView) : RecyclerView.ViewHolder(view)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(RecyclerView(parent.context).apply {
|
||||
layoutManager = GridLayoutManager(parent.context, 3)
|
||||
adapter = ThumbnailAdapter(glide, listOf())
|
||||
val layoutManager = GridLayoutManager(parent.context, 3)
|
||||
val adapter = ThumbnailAdapter(listOf())
|
||||
|
||||
this.layoutManager = layoutManager
|
||||
this.adapter = adapter
|
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
})
|
||||
}
|
||||
@@ -41,7 +43,7 @@ class ThumbnailPageAdapter(private val glide: RequestManager, private val thumbn
|
||||
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
|
||||
notifyDataSetChanged()
|
||||
|
||||
holder.view.layoutManager?.scrollToPosition(itemCount-1)
|
||||
(holder.view.layoutManager as GridLayoutManager).scrollToPosition(8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -37,17 +36,16 @@ import okhttp3.Callback
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import okio.*
|
||||
import xyz.quaver.pupil.PupilInterceptor
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.interceptors
|
||||
import xyz.quaver.pupil.*
|
||||
import xyz.quaver.pupil.ui.ReaderActivity
|
||||
import xyz.quaver.pupil.util.cleanCache
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import xyz.quaver.pupil.util.ellipsize
|
||||
import xyz.quaver.pupil.util.normalizeID
|
||||
import xyz.quaver.pupil.util.requestBuilders
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.log10
|
||||
@@ -90,7 +88,7 @@ class DownloadService : Service() {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT),
|
||||
).build()
|
||||
|
||||
notification.put(galleryID, NotificationCompat.Builder(this, "download").apply {
|
||||
notification[galleryID] = NotificationCompat.Builder(this, "download").apply {
|
||||
setContentTitle(getString(R.string.reader_loading))
|
||||
setContentText(getString(R.string.reader_notification_text))
|
||||
setSmallIcon(R.drawable.ic_notification)
|
||||
@@ -98,7 +96,7 @@ class DownloadService : Service() {
|
||||
addAction(action)
|
||||
setProgress(0, 0, true)
|
||||
setOngoing(true)
|
||||
})
|
||||
}
|
||||
|
||||
notify(galleryID)
|
||||
}
|
||||
@@ -123,7 +121,7 @@ class DownloadService : Service() {
|
||||
.setProgress(max, progress, false)
|
||||
.setContentText("$progress/$max")
|
||||
|
||||
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null)
|
||||
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null || galleryID == priority)
|
||||
notification.let { notificationManager.notify(galleryID, it.build()) }
|
||||
else
|
||||
notificationManager.cancel(galleryID)
|
||||
@@ -198,6 +196,7 @@ class DownloadService : Service() {
|
||||
* Float.POSITIVE_INFINITY -> Download completed
|
||||
*/
|
||||
val progress = ConcurrentHashMap<Int, MutableList<Float>>()
|
||||
var priority = 0
|
||||
|
||||
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
|
||||
|
||||
@@ -289,14 +288,16 @@ class DownloadService : Service() {
|
||||
fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
|
||||
cancel(galleryID)
|
||||
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
||||
Cache.delete(galleryID)
|
||||
Cache.delete(this@DownloadService, galleryID)
|
||||
|
||||
startId?.let { stopSelf(it) }
|
||||
}
|
||||
|
||||
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
|
||||
if (progress.containsKey(galleryID))
|
||||
cancel(galleryID)
|
||||
if (DownloadManager.getInstance(this@DownloadService).isDownloading(galleryID))
|
||||
return@launch
|
||||
|
||||
cleanCache(this@DownloadService)
|
||||
|
||||
val cache = Cache.getInstance(this@DownloadService, galleryID)
|
||||
|
||||
@@ -311,6 +312,8 @@ class DownloadService : Service() {
|
||||
return@launch
|
||||
}
|
||||
|
||||
histories.add(galleryID)
|
||||
|
||||
progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 0F }
|
||||
|
||||
cache.metadata.imageList?.let {
|
||||
|
||||
@@ -22,13 +22,18 @@ import kotlinx.android.parcel.IgnoredOnParcel
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import xyz.quaver.hitomi.Suggestion
|
||||
import xyz.quaver.pupil.util.translations
|
||||
|
||||
@Parcelize
|
||||
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
|
||||
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
||||
|
||||
@IgnoredOnParcel
|
||||
override val body = s
|
||||
override val body =
|
||||
if (translations[s] != null)
|
||||
"${translations[s]} ($s)"
|
||||
else
|
||||
s
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -23,16 +23,19 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.text.util.Linkify
|
||||
import android.view.KeyEvent
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.view.GravityCompat
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
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.DownloadManager
|
||||
import xyz.quaver.pupil.util.restore
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
@@ -142,21 +146,10 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
runOnUiThread {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel()
|
||||
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.updateAll = false
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
@@ -214,6 +207,8 @@ class MainActivity :
|
||||
}
|
||||
)
|
||||
|
||||
Linkify.addLinks(main_noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) })
|
||||
|
||||
//NavigationView
|
||||
main_nav_view.setNavigationItemSelectedListener(this)
|
||||
|
||||
@@ -263,11 +258,7 @@ class MainActivity :
|
||||
if (it?.isEmpty() == false) {
|
||||
val galleryID = it.random()
|
||||
|
||||
GalleryDialog(
|
||||
this@MainActivity,
|
||||
Glide.with(this@MainActivity),
|
||||
galleryID
|
||||
).apply {
|
||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
@@ -298,12 +289,22 @@ class MainActivity :
|
||||
setTitle(R.string.main_open_gallery_by_id)
|
||||
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryID", galleryID)
|
||||
}
|
||||
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
||||
|
||||
startActivity(intent)
|
||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
currentPage = 0
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
@@ -318,7 +319,7 @@ class MainActivity :
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun setupRecyclerView() {
|
||||
with(main_recyclerview) {
|
||||
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply {
|
||||
adapter = GalleryBlockAdapter(galleries).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
@@ -332,16 +333,13 @@ class MainActivity :
|
||||
}
|
||||
onDownloadClickedHandler = { position ->
|
||||
val galleryID = galleries[position]
|
||||
if (Preferences["cache_disable"])
|
||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
||||
|
||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
||||
DownloadService.cancel(this@MainActivity, galleryID)
|
||||
}
|
||||
else {
|
||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
||||
DownloadService.cancel(this@MainActivity, galleryID)
|
||||
}
|
||||
else {
|
||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||
DownloadService.download(this@MainActivity, galleryID)
|
||||
}
|
||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||
DownloadService.download(this@MainActivity, galleryID)
|
||||
}
|
||||
|
||||
closeAllItems()
|
||||
@@ -382,13 +380,9 @@ class MainActivity :
|
||||
if (v !is CardView)
|
||||
return@listener false
|
||||
|
||||
val galleryID = galleries[position]
|
||||
val galleryID = galleries.getOrNull(position) ?: return@listener true
|
||||
|
||||
GalleryDialog(
|
||||
this@MainActivity,
|
||||
Glide.with(this@MainActivity),
|
||||
galleryID
|
||||
).apply {
|
||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
@@ -987,14 +981,4 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
super.onLowMemory()
|
||||
Glide.get(this).onLowMemory()
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
Glide.get(this).onTrimMemory(level)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,17 +33,14 @@ import android.view.animation.Animation
|
||||
import android.view.animation.AnticipateInterpolator
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.view.animation.TranslateAnimation
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.PagerSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
import com.bumptech.glide.Glide
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.mlkit.vision.face.Face
|
||||
@@ -60,7 +57,6 @@ import xyz.quaver.Code
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||
import xyz.quaver.pupil.favorites
|
||||
import xyz.quaver.pupil.histories
|
||||
import xyz.quaver.pupil.services.DownloadService
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import xyz.quaver.pupil.util.camera
|
||||
@@ -68,8 +64,6 @@ import xyz.quaver.pupil.util.closeCamera
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import xyz.quaver.pupil.util.startCamera
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
class ReaderActivity : BaseActivity() {
|
||||
|
||||
@@ -89,6 +83,8 @@ class ReaderActivity : BaseActivity() {
|
||||
private val conn = object: ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
downloader = (service as DownloadService.Binder).service.also {
|
||||
it.priority = 0
|
||||
|
||||
if (!it.progress.containsKey(galleryID))
|
||||
DownloadService.download(this@ReaderActivity, galleryID, true)
|
||||
}
|
||||
@@ -99,7 +95,6 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private val timer = Timer()
|
||||
private val snapHelper = PagerSnapHelper()
|
||||
private var menu: Menu? = null
|
||||
|
||||
@@ -139,38 +134,7 @@ class ReaderActivity : BaseActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
if (Preferences["cache_disable"]) {
|
||||
reader_download_progressbar.visibility = View.GONE
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val reader = cache.getReader()
|
||||
|
||||
launch(Dispatchers.Main) initDownloader@{
|
||||
if (reader == null) {
|
||||
Snackbar
|
||||
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||
.show()
|
||||
return@initDownloader
|
||||
}
|
||||
|
||||
histories.add(galleryID)
|
||||
(reader_recyclerview.adapter as ReaderAdapter).apply {
|
||||
this.reader = reader
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
title = reader.galleryInfo.title ?: ""
|
||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}"
|
||||
|
||||
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
|
||||
when (reader.code) {
|
||||
Code.HITOMI -> R.drawable.hitomi
|
||||
Code.HIYOBI -> R.drawable.ic_hiyobi
|
||||
else -> android.R.color.transparent
|
||||
})
|
||||
}
|
||||
}
|
||||
} else
|
||||
initDownloadListener()
|
||||
|
||||
initDownloadListener()
|
||||
initView()
|
||||
}
|
||||
|
||||
@@ -263,15 +227,16 @@ class ReaderActivity : BaseActivity() {
|
||||
if (downloader != null)
|
||||
unbindService(conn)
|
||||
|
||||
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
|
||||
DownloadService.cancel(this, galleryID)
|
||||
downloader?.priority = galleryID
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
timer.cancel()
|
||||
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
|
||||
update = false
|
||||
|
||||
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
|
||||
DownloadService.cancel(this, galleryID)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
@@ -306,44 +271,51 @@ class ReaderActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private var update = true
|
||||
private fun initDownloadListener() {
|
||||
timer.schedule(1000, 1000) {
|
||||
val downloader = downloader ?: return@schedule
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
while (update) {
|
||||
delay(1000)
|
||||
|
||||
if (!downloader.progress.containsKey(galleryID)) //loading
|
||||
return@schedule
|
||||
val downloader = downloader ?: continue
|
||||
|
||||
if (downloader.progress[galleryID]?.isEmpty() == true) { //Gallery not found
|
||||
timer.cancel()
|
||||
Snackbar
|
||||
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||
.show()
|
||||
}
|
||||
if (!downloader.progress.containsKey(galleryID)) //loading
|
||||
continue
|
||||
|
||||
histories.add(galleryID)
|
||||
if (downloader.progress[galleryID]?.isEmpty() == true) { //Gallery not found
|
||||
update = false
|
||||
Snackbar
|
||||
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
|
||||
.show()
|
||||
|
||||
return@launch
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
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)) {
|
||||
val reader = cache.metadata.reader
|
||||
|
||||
if (reader != null) {
|
||||
with (reader_recyclerview.adapter as ReaderAdapter) {
|
||||
with(reader_recyclerview.adapter as ReaderAdapter) {
|
||||
this.reader = reader
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
title = reader.galleryInfo.title
|
||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}"
|
||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title =
|
||||
"$currentPage/${reader.galleryInfo.files.size}"
|
||||
|
||||
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
|
||||
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(
|
||||
this@ReaderActivity,
|
||||
when (reader.code) {
|
||||
Code.HITOMI -> R.drawable.hitomi
|
||||
Code.HIYOBI -> R.drawable.ic_hiyobi
|
||||
else -> android.R.color.transparent
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,19 +368,15 @@ class ReaderActivity : BaseActivity() {
|
||||
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
|
||||
|
||||
setOnClickListener {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false))
|
||||
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
|
||||
else {
|
||||
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
|
||||
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
|
||||
|
||||
if (downloadManager.isDownloading(galleryID)) {
|
||||
downloadManager.deleteDownloadFolder(galleryID)
|
||||
animateDownloadFAB(false)
|
||||
} else {
|
||||
downloadManager.addDownloadFolder(galleryID)
|
||||
DownloadService.download(context, galleryID, true)
|
||||
animateDownloadFAB(true)
|
||||
}
|
||||
if (downloadManager.isDownloading(galleryID)) {
|
||||
downloadManager.deleteDownloadFolder(galleryID)
|
||||
animateDownloadFAB(false)
|
||||
} else {
|
||||
downloadManager.addDownloadFolder(galleryID)
|
||||
DownloadService.download(context, galleryID, true)
|
||||
animateDownloadFAB(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -637,14 +605,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)
|
||||
}
|
||||
}
|
||||
@@ -20,16 +20,17 @@ package xyz.quaver.pupil.ui.dialog
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout.LayoutParams
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.dialog_gallery.*
|
||||
import kotlinx.android.synthetic.main.dialog_gallery_details.view.*
|
||||
@@ -41,7 +42,6 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import xyz.quaver.hitomi.Gallery
|
||||
import xyz.quaver.hitomi.getGallery
|
||||
import xyz.quaver.pupil.BuildConfig
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
||||
@@ -53,8 +53,10 @@ import xyz.quaver.pupil.ui.view.TagChip
|
||||
import xyz.quaver.pupil.util.ItemClickSupport
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.wordCapitalize
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class GalleryDialog(context: Context, private val glide: RequestManager, private val galleryID: Int) : AlertDialog(context) {
|
||||
class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(context) {
|
||||
|
||||
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 {
|
||||
putExtra("galleryID", galleryID)
|
||||
})
|
||||
histories.add(galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,19 +106,19 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
}
|
||||
}
|
||||
|
||||
glide
|
||||
.load(gallery.cover)
|
||||
.apply {
|
||||
if (BuildConfig.CENSOR)
|
||||
override(5, 8)
|
||||
}.into(gallery_cover)
|
||||
gallery_cover.showImage(Uri.parse(gallery.cover))
|
||||
|
||||
addDetails(gallery)
|
||||
addThumbnails(gallery)
|
||||
addRelated(gallery)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show()
|
||||
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).apply {
|
||||
if (Locale.getDefault().language == "ko")
|
||||
setAction(context.getText(R.string.https_text)) {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.https))))
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,7 +196,8 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
gallery_details.setText(R.string.gallery_thumbnails)
|
||||
|
||||
val pager = ViewPager2(context).apply {
|
||||
adapter = ThumbnailPageAdapter(glide, gallery.thumbnails)
|
||||
adapter = ThumbnailPageAdapter(gallery.thumbnails)
|
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
gallery_details_contents.addView(
|
||||
@@ -215,7 +217,7 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val galleries = ArrayList<Int>()
|
||||
|
||||
val adapter = GalleryBlockAdapter(glide, galleries).apply {
|
||||
val adapter = GalleryBlockAdapter(galleries).apply {
|
||||
onChipClickedHandler.add { tag ->
|
||||
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||
handler.invoke(tag)
|
||||
@@ -235,14 +237,9 @@ class GalleryDialog(context: Context, private val glide: RequestManager, private
|
||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryID", galleries[position])
|
||||
})
|
||||
histories.add(galleries[position])
|
||||
}
|
||||
onItemLongClickListener = { _, position, _ ->
|
||||
GalleryDialog(
|
||||
context,
|
||||
glide,
|
||||
galleries[position]
|
||||
).apply {
|
||||
GalleryDialog(context, galleries[position]).apply {
|
||||
onChipClickedHandler.add { tag ->
|
||||
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||
}
|
||||
|
||||
@@ -44,8 +44,7 @@ import java.net.Proxy
|
||||
class ProxyDialog(context: Context) : AlertDialog(context) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setContentView(build())
|
||||
window?.attributes?.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
setView(build())
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import xyz.quaver.io.util.deleteRecursively
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.histories
|
||||
import xyz.quaver.pupil.util.byteToString
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import java.io.File
|
||||
|
||||
@@ -61,6 +62,8 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
||||
if (dir.exists())
|
||||
dir.deleteRecursively()
|
||||
|
||||
Cache.instances.clear()
|
||||
|
||||
summary = context.getString(R.string.settings_storage_usage, byteToString(0))
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var size = 0L
|
||||
|
||||
@@ -21,14 +21,16 @@ package xyz.quaver.pupil.ui.fragment
|
||||
import android.app.Activity
|
||||
import android.content.*
|
||||
import android.os.Bundle
|
||||
import android.os.LocaleList
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.preference.*
|
||||
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.util.getChild
|
||||
import xyz.quaver.pupil.R
|
||||
@@ -37,6 +39,7 @@ import xyz.quaver.pupil.ui.SettingsActivity
|
||||
import xyz.quaver.pupil.ui.dialog.*
|
||||
import xyz.quaver.pupil.util.*
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import java.util.*
|
||||
|
||||
class SettingsFragment :
|
||||
PreferenceFragmentCompat(),
|
||||
@@ -123,6 +126,9 @@ class SettingsFragment :
|
||||
this ?: return false
|
||||
|
||||
when (key) {
|
||||
"tag_language" -> {
|
||||
updateTranslations()
|
||||
}
|
||||
"nomedia" -> {
|
||||
val create = (newValue as? Boolean) ?: return false
|
||||
|
||||
@@ -243,6 +249,25 @@ class SettingsFragment :
|
||||
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
"tag_language" -> {
|
||||
this as ListPreference
|
||||
|
||||
isEnabled = false
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val languages = getAvailableLanguages().distinct().toTypedArray()
|
||||
|
||||
entries = languages.map { Locale(it).let { loc -> loc.getDisplayLanguage(loc) } }.toTypedArray()
|
||||
entryValues = languages
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
onPreferenceChangeListener = this@SettingsFragment
|
||||
|
||||
}
|
||||
"mirrors" -> {
|
||||
onPreferenceClickListener = this@SettingsFragment
|
||||
}
|
||||
|
||||
@@ -164,9 +164,7 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
|
||||
}
|
||||
}
|
||||
|
||||
if (item.t == -1) {
|
||||
textView?.text = item.s
|
||||
} else {
|
||||
if (item.t > 0) {
|
||||
(suggestionView as? LinearLayout)?.let {
|
||||
val count = it.findViewById<TextView>(R.id.count)
|
||||
if (count == null)
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.google.android.material.chip.Chip
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.favoriteTags
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.util.translations
|
||||
import xyz.quaver.pupil.util.wordCapitalize
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
@@ -90,7 +91,7 @@ class TagChip(context: Context, _tag: Tag) : Chip(context) {
|
||||
|
||||
text = when (tag.area) {
|
||||
"language" -> languages[tag.tag]
|
||||
else -> tag.tag.wordCapitalize()
|
||||
else -> (translations[tag.tag] ?: tag.tag).wordCapitalize()
|
||||
}
|
||||
|
||||
setEnsureMinTouchTargetSize(false)
|
||||
|
||||
90
app/src/main/java/xyz/quaver/pupil/ui/view/TagChipGroup.kt
Normal file
90
app/src/main/java/xyz/quaver/pupil/ui/view/TagChipGroup.kt
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Pupil, Hitomi.la viewer for Android
|
||||
* Copyright (C) 2020 tom5079
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.quaver.pupil.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.util.AttributeSet
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.types.Tags
|
||||
|
||||
class TagChipGroup @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, attrStyle: Int = R.attr.chipGroupStyle, val tags: Tags = Tags()) : ChipGroup(context, attr, attrStyle), MutableSet<Tag> by tags {
|
||||
|
||||
object Defaults {
|
||||
val maxChipSize = 10
|
||||
}
|
||||
|
||||
var maxChipSize: Int = Defaults.maxChipSize
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
private val moreView = Chip(context).apply {
|
||||
text = "…"
|
||||
|
||||
setEnsureMinTouchTargetSize(false)
|
||||
|
||||
setOnClickListener {
|
||||
removeView(this)
|
||||
|
||||
for (i in maxChipSize until tags.size) {
|
||||
val tag = tags.elementAt(i)
|
||||
|
||||
addView(TagChip(context, tag).apply {
|
||||
setOnClickListener {
|
||||
onClickListener?.invoke(tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var onClickListener: ((Tag) -> Unit)? = null
|
||||
|
||||
private fun applyAttributes(attr: TypedArray) {
|
||||
maxChipSize = attr.getInt(R.styleable.TagChipGroup_maxTag, Defaults.maxChipSize)
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
this.removeAllViews()
|
||||
|
||||
tags.take(maxChipSize).forEach {
|
||||
this.addView(TagChip(context, it).apply {
|
||||
setOnClickListener {
|
||||
onClickListener?.invoke(this.tag)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (maxChipSize > 0 && this.size > maxChipSize)
|
||||
addView(moreView)
|
||||
}
|
||||
|
||||
init {
|
||||
applyAttributes(context.obtainStyledAttributes(attr, R.styleable.TagChipGroup))
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,8 +22,9 @@ import kotlinx.serialization.*
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class SavedSet <T: Any> (private val file: File, private val any: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> by set {
|
||||
class SavedSet <T: Any> (private val file: File, private val any: T, private val set: MutableSet<T> = Collections.synchronizedSet(mutableSetOf())) : MutableSet<T> by set {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,12 @@ package xyz.quaver.pupil.util.downloader
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
@@ -36,6 +39,7 @@ import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.*
|
||||
import xyz.quaver.pupil.client
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@@ -59,8 +63,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun delete(galleryID: Int) {
|
||||
instances[galleryID]?.cacheFolder?.deleteRecursively()
|
||||
fun delete(context: Context, galleryID: Int) {
|
||||
File(context.cacheDir, "imageCache/$galleryID").deleteRecursively()
|
||||
instances.remove(galleryID)
|
||||
}
|
||||
}
|
||||
@@ -131,8 +135,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun getThumbnail(): ByteArray? =
|
||||
findFile(".thumbnail")?.readBytes()
|
||||
suspend fun getThumbnail(): Uri =
|
||||
findFile(".thumbnail")?.uri
|
||||
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
|
||||
kotlin.runCatching {
|
||||
val request = Request.Builder()
|
||||
@@ -140,10 +144,15 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() }
|
||||
}.getOrNull()?.also { kotlin.run {
|
||||
cacheFolder.getChild(".thumbnail").writeBytes(it)
|
||||
} }
|
||||
} }
|
||||
}.getOrNull()?.let { thumbnail -> kotlin.runCatching {
|
||||
cacheFolder.getChild(".thumbnail").also {
|
||||
if (!it.exists())
|
||||
it.createNewFile()
|
||||
|
||||
it.writeBytes(thumbnail)
|
||||
}
|
||||
}.getOrNull()?.uri }
|
||||
} } ?: Uri.EMPTY
|
||||
|
||||
suspend fun getReader(): Reader? {
|
||||
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
||||
@@ -185,67 +194,76 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}
|
||||
|
||||
fun getImage(index: Int): FileX? =
|
||||
metadata.imageList?.get(index)?.let { findFile(it) }
|
||||
metadata.imageList?.getOrNull(index)?.let { findFile(it) }
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
fun putImage(index: Int, fileName: String, data: ByteArray) {
|
||||
val file = cacheFolder.getChild(fileName)
|
||||
|
||||
file.createNewFile()
|
||||
if (!file.exists())
|
||||
file.createNewFile()
|
||||
file.writeBytes(data)
|
||||
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
||||
}
|
||||
|
||||
private val lock = ConcurrentHashMap<Int, Mutex>()
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
||||
val downloadFolder = downloadFolder ?: return@launch
|
||||
|
||||
val cacheMetadata = cacheFolder.getChild(".metadata")
|
||||
val downloadMetadata = downloadFolder.getChild(".metadata")
|
||||
|
||||
if (downloadMetadata.exists() || !cacheMetadata.exists())
|
||||
if (lock[galleryID]?.isLocked == true)
|
||||
return@launch
|
||||
|
||||
if (cacheMetadata.exists()) {
|
||||
kotlin.runCatching {
|
||||
downloadMetadata.createNewFile()
|
||||
downloadMetadata.writeText(Json.encodeToString(metadata))
|
||||
(lock[galleryID] ?: Mutex().also { lock[galleryID] = it }).withLock {
|
||||
val cacheMetadata = cacheFolder.getChild(".metadata")
|
||||
val downloadMetadata = downloadFolder.getChild(".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 downloadThumbnail = downloadFolder.getChild(".thumbnail")
|
||||
val cacheThumbnail = cacheFolder.getChild(".thumbnail")
|
||||
val downloadThumbnail = downloadFolder.getChild(".thumbnail")
|
||||
|
||||
if (cacheThumbnail.exists() && !downloadThumbnail.exists()) {
|
||||
kotlin.runCatching {
|
||||
if (!downloadThumbnail.exists())
|
||||
downloadThumbnail.createNewFile()
|
||||
if (cacheThumbnail.exists()) {
|
||||
kotlin.runCatching {
|
||||
if (!downloadThumbnail.exists())
|
||||
downloadThumbnail.createNewFile()
|
||||
|
||||
downloadThumbnail.outputStream()?.use { target -> cacheThumbnail.inputStream()?.use { source ->
|
||||
source.copyTo(target)
|
||||
} }
|
||||
cacheThumbnail.delete()
|
||||
downloadThumbnail.outputStream()?.use { target -> target.channel.truncate(0L); cacheThumbnail.inputStream()?.use { source ->
|
||||
source.copyTo(target)
|
||||
} }
|
||||
cacheThumbnail.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metadata.imageList?.forEach { imageName ->
|
||||
imageName ?: return@forEach
|
||||
val target = downloadFolder.getChild(imageName)
|
||||
val source = cacheFolder.getChild(imageName)
|
||||
metadata.imageList?.forEach { imageName ->
|
||||
imageName ?: return@forEach
|
||||
val target = downloadFolder.getChild(imageName)
|
||||
val source = cacheFolder.getChild(imageName)
|
||||
|
||||
if (!source.exists() || target.exists())
|
||||
return@forEach
|
||||
if (!source.exists())
|
||||
return@forEach
|
||||
|
||||
kotlin.runCatching {
|
||||
if (!target.exists())
|
||||
target.createNewFile()
|
||||
kotlin.runCatching {
|
||||
if (!target.exists())
|
||||
target.createNewFile()
|
||||
|
||||
target.outputStream()?.use { target -> source.inputStream()?.use { source ->
|
||||
source.copyTo(target)
|
||||
} }
|
||||
target.outputStream()?.use { target -> target.channel.truncate(0L); source.inputStream()?.use { source ->
|
||||
source.copyTo(target)
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
cacheFolder.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,35 +19,47 @@
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
import android.content.Context
|
||||
import android.os.storage.StorageManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import xyz.quaver.pupil.histories
|
||||
import xyz.quaver.pupil.util.downloader.Cache
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.lang.reflect.Array
|
||||
import java.net.URL
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Deprecated("Use downloader.Cache instead")
|
||||
fun getCachedGallery(context: Context, galleryID: Int) =
|
||||
File(getDownloadDirectory(context), galleryID.toString()).let {
|
||||
if (it.exists())
|
||||
it
|
||||
else
|
||||
File(context.cacheDir, "imageCache/$galleryID")
|
||||
val mutex = Mutex()
|
||||
fun cleanCache(context: Context) = CoroutineScope(Dispatchers.IO).launch {
|
||||
if (mutex.isLocked) return@launch
|
||||
|
||||
mutex.withLock {
|
||||
val cacheFolder = File(context.cacheDir, "imageCache")
|
||||
val downloadManager = DownloadManager.getInstance(context)
|
||||
|
||||
val limit = (Preferences.get<String>("cache_limit").toLongOrNull() ?: 0L)*1024*1024*1024
|
||||
|
||||
if (limit == 0L) return@withLock
|
||||
|
||||
val cacheSize = {
|
||||
var size = 0L
|
||||
|
||||
cacheFolder.walk().forEach {
|
||||
size += it.length()
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
if (cacheSize.invoke() > limit)
|
||||
while (cacheSize.invoke() > limit/2) {
|
||||
val caches = cacheFolder.list() ?: return@withLock
|
||||
|
||||
(histories.toList().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)
|
||||
}
|
||||
@@ -23,6 +23,9 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import xyz.quaver.Code
|
||||
@@ -93,14 +96,14 @@ fun GalleryBlock.formatDownloadFolder(): String =
|
||||
formatMap.entries.fold(it) { str, (k, v) ->
|
||||
str.replace(k, v.invoke(this), true)
|
||||
}
|
||||
}.replace(Regex("""[*\\|"?><:/]"""), "")
|
||||
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||
|
||||
fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
||||
format.let {
|
||||
formatMap.entries.fold(it) { str, (k, v) ->
|
||||
str.replace(k, v.invoke(this), true)
|
||||
}
|
||||
}.replace(Regex("""[*\\|"?><:/]"""), "")
|
||||
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||
|
||||
val Reader.requestBuilders: List<Request.Builder>
|
||||
get() {
|
||||
@@ -128,4 +131,10 @@ fun String.ellipsize(n: Int): String =
|
||||
if (this.length > n)
|
||||
this.slice(0 until n) + "…"
|
||||
else
|
||||
this
|
||||
this
|
||||
|
||||
operator fun JsonElement.get(index: Int) =
|
||||
this.jsonArray[index]
|
||||
|
||||
operator fun JsonElement.get(tag: String) =
|
||||
this.jsonObject[tag]
|
||||
66
app/src/main/java/xyz/quaver/pupil/util/translation.kt
Normal file
66
app/src/main/java/xyz/quaver/pupil/util/translation.kt
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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()
|
||||
translations = Json.decodeFromString<Map<String, String>>(client.newCall(
|
||||
Request.Builder()
|
||||
.url(contentURL + "${Preferences["tag_language", ""]}.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())
|
||||
}
|
||||
@@ -313,7 +313,7 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
||||
)
|
||||
|
||||
synchronized(Cache) {
|
||||
Cache.delete(galleryID)
|
||||
Cache.delete(this@migrate, galleryID)
|
||||
}
|
||||
downloadFolderMap[galleryID] = folder.name
|
||||
|
||||
|
||||
@@ -47,22 +47,6 @@
|
||||
|
||||
</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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -86,6 +70,24 @@
|
||||
|
||||
</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
|
||||
android:id="@+id/main_fab"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageView
|
||||
<com.github.piasy.biv.view.BigImageView
|
||||
android:id="@+id/gallery_cover"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -107,15 +107,17 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
<com.github.piasy.biv.view.BigImageView
|
||||
android:id="@+id/galleryblock_thumbnail"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/galleryblock_thumbnail_description"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_constraintHeight_default="spread"
|
||||
app:layout_constraintHeight_min="200dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar_layout"
|
||||
app:layout_constraintBottom_toBottomOf="@id/barrier"/>
|
||||
app:layout_constraintBottom_toTopOf="@id/barrier"/>
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.AppCompat.Headline"
|
||||
@@ -164,14 +166,7 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
|
||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
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
|
||||
<xyz.quaver.pupil.ui.view.TagChipGroup
|
||||
android:id="@+id/galleryblock_tag_group"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -179,7 +174,7 @@
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
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_constraintRight_toRightOf="parent"/>
|
||||
|
||||
@@ -188,7 +183,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="galleryblock_thumbnail,galleryblock_tag_group"/>
|
||||
app:constraint_referenced_ids="galleryblock_thumbnail, galleryblock_tag_group"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
@@ -217,8 +212,8 @@
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/divider"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="parent" />
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/galleryblock_favorite"
|
||||
|
||||
@@ -21,47 +21,51 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintHeight_max="2000dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:paddingBottom="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/reader_item_boundary">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
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"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/progress_layout"
|
||||
<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_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
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:initScaleType="fitCenter"
|
||||
app:optimizeDisplay="true"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
|
||||
@@ -127,8 +127,6 @@
|
||||
<string name="settings_lock_fingerprint_prompt">Pupil指紋ロック™</string>
|
||||
<string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</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_toast">ユーザーIDをクリップボードにコピーしました</string>
|
||||
<string name="reader_fab_retry">リトライ</string>
|
||||
@@ -152,4 +150,8 @@
|
||||
<string name="camera_denied">カメラ権限が拒否されているため、まばたき検知使用できません</string>
|
||||
<string name="no_camera">この機器には前面カメラが装着されていません</string>
|
||||
<string name="error">エラー</string>
|
||||
<string name="settings_cache_limit">キャッシュサイズ制限</string>
|
||||
<string name="settings_cache_unlimited">制限なし</string>
|
||||
<string name="settings_tag_language">タグ言語</string>
|
||||
<string name="settings_tag_language_message">Githubにて翻訳に参加できます</string>
|
||||
</resources>
|
||||
@@ -12,10 +12,10 @@
|
||||
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
|
||||
<string name="settings_search_title">검색 설정</string>
|
||||
<string name="settings_title">설정</string>
|
||||
<string name="update_notification_description">apk 다운로드중…</string>
|
||||
<string name="update_notification_description">업데이트 다운로드중…</string>
|
||||
<string name="update_title">업데이트가 있습니다!</string>
|
||||
<string name="warning">경고</string>
|
||||
<string name="main_no_result">결과 없음</string>
|
||||
<string name="main_no_result">결과 없음\n해결법</string>
|
||||
<string name="settings_miscellaneous_title">기타</string>
|
||||
<string name="settings_clear_history">기록 삭제</string>
|
||||
<string name="settings_clear_history_alert_message">기록을 삭제하시겠습니까?</string>
|
||||
@@ -127,8 +127,6 @@
|
||||
<string name="settings_lock_fingerprint_prompt">Pupil 지문 인식™</string>
|
||||
<string name="settings_lock_fingerprint_prompt_subtitle">힘세고 강한 지문 인식</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_toast">유저 ID를 클립보드에 복사했습니다</string>
|
||||
<string name="reader_fab_retry">재시도</string>
|
||||
@@ -152,4 +150,8 @@
|
||||
<string name="camera_denied">카메라 권한이 거부되었기 때문에 눈 깜빡임 감지가 불가능합니다</string>
|
||||
<string name="no_camera">이 장치에는 전면 카메라가 없습니다</string>
|
||||
<string name="error">오류</string>
|
||||
<string name="settings_cache_limit">캐시 크기 제한</string>
|
||||
<string name="settings_cache_unlimited">무제한</string>
|
||||
<string name="settings_tag_language">태그 언어</string>
|
||||
<string name="settings_tag_language_message">Github에서 번역에 참여하세요</string>
|
||||
</resources>
|
||||
@@ -2,6 +2,7 @@
|
||||
<resources>
|
||||
|
||||
<string-array name="settings_galleries_per_page">
|
||||
<item>5</item>
|
||||
<item>10</item>
|
||||
<item>25</item>
|
||||
<item>50</item>
|
||||
@@ -58,4 +59,24 @@
|
||||
<item>SOCKS</item>
|
||||
</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>
|
||||
24
app/src/main/res/values/attr.xml
Normal file
24
app/src/main/res/values/attr.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Pupil, Hitomi.la viewer for Android
|
||||
~ Copyright (C) 2020 tom5079
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<declare-styleable name="TagChipGroup">
|
||||
<attr name="maxTag" format="integer"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -10,4 +10,6 @@
|
||||
|
||||
<dimen name="thumb_width">24dp</dimen>
|
||||
<dimen name="thumb_height">72dp</dimen>
|
||||
|
||||
<dimen name="thumbnail_page_height">300dp</dimen>
|
||||
</resources>
|
||||
@@ -9,6 +9,10 @@
|
||||
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</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="main_settings" translatable="false">Settings</string>
|
||||
@@ -17,6 +21,8 @@
|
||||
<string name="reader_imageview_description" translatable="false">Content ImageView</string>
|
||||
<string name="page_indicator_placeholder" translatable="false">-/-</string>
|
||||
|
||||
<string name="galleryblock_artist_with_group" translatable="false">%s (%s)</string>
|
||||
|
||||
<!-- Translate needed down here -->
|
||||
|
||||
<string name="warning">Warning</string>
|
||||
@@ -155,8 +161,9 @@
|
||||
<string name="settings_download_folder_available">%s available</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_cache_disable">Disable Cache</string>
|
||||
<string name="settings_download_when_cache_disable_warning">Download is disabled when the cache is disabled</string>
|
||||
<string name="settings_cache_limit">Cache Limit</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_summary">Load low quality images to improve load speed and data usage</string>
|
||||
|
||||
@@ -168,6 +175,8 @@
|
||||
<!-- SETTINGS/MISCELLANEOUS -->
|
||||
|
||||
<string name="settings_miscellaneous_title">Miscellaneous</string>
|
||||
<string name="settings_tag_language">Tag Language</string>
|
||||
<string name="settings_tag_language_message">Participate in translation on Github</string>
|
||||
<string name="settings_mirror_summary">Load images from mirrors</string>
|
||||
<string name="settings_proxy_title">Proxy</string>
|
||||
<string name="settings_rtl">Turn pages Right-to-Left</string>
|
||||
@@ -175,7 +184,6 @@
|
||||
<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_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_user_id">User ID</string>
|
||||
<string name="settings_user_id_toast">User ID is copied to clipboard</string>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<Preference
|
||||
app:key="app_version"
|
||||
@@ -44,9 +43,13 @@
|
||||
app:key="download_folder"
|
||||
app:title="@string/settings_download_folder"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:key="cache_disable"
|
||||
app:title="@string/settings_cache_disable"/>
|
||||
<ListPreference
|
||||
app:key="cache_limit"
|
||||
app:title="@string/settings_cache_limit"
|
||||
app:entries="@array/cache_size_text"
|
||||
app:entryValues="@array/cache_size"
|
||||
app:defaultValue="8"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
app:key="nomedia"
|
||||
@@ -72,6 +75,12 @@
|
||||
<PreferenceCategory
|
||||
app:title="@string/settings_miscellaneous_title">
|
||||
|
||||
<ListPreference
|
||||
app:key="tag_language"
|
||||
app:title="@string/settings_tag_language"
|
||||
app:defaultValue="en"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
|
||||
<Preference
|
||||
app:key="mirrors"
|
||||
app:title="@string/settings_mirror_title"
|
||||
|
||||
13
build.gradle
13
build.gradle
@@ -6,25 +6,26 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'com.android.tools.build:gradle:4.0.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$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
|
||||
// in the individual module build.gradle files
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
|
||||
classpath 'com.google.firebase:perf-plugin:1.3.1'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
|
||||
classpath "com.google.firebase:firebase-crashlytics-gradle:2.3.0"
|
||||
classpath "com.google.firebase:perf-plugin:1.3.2"
|
||||
classpath "com.google.android.gms:oss-licenses-plugin:0.10.2"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url "http://dl.bintray.com/piasy/maven" }
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "https://jitpack.io" }
|
||||
maven { url 'https://guardian.github.com/maven/repo-releases' }
|
||||
maven { url "https://guardian.github.com/maven/repo-releases" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5555
dependencies.txt
5555
dependencies.txt
File diff suppressed because it is too large
Load Diff
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,4 +1,4 @@
|
||||
#Thu Jun 18 15:48:09 KST 2020
|
||||
#Thu Oct 01 20:54:37 KST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
51
gradlew
vendored
51
gradlew
vendored
@@ -1,5 +1,21 @@
|
||||
#!/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
|
||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
||||
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.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
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\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
@@ -138,19 +154,19 @@ if $cygwin ; then
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
@@ -159,14 +175,9 @@ save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# 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"
|
||||
|
||||
# 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" "$@"
|
||||
|
||||
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
||||
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.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
Reference in New Issue
Block a user