Compare commits

..

34 Commits

Author SHA1 Message Date
tom5079
365fc56e9d Update README.md 2020-10-02 13:09:46 +09:00
tom5079
54a5cd21ea Merge branch 'dev' into master 2020-10-02 13:00:02 +09:00
tom5079
38c0399b09 App built 2020-10-02 12:59:32 +09:00
tom5079
2b67858453 Auto cache clean 2020-10-02 12:51:59 +09:00
tom5079
87fdbdbb6e Open GalleryDialog first instead of opening Reader directly 2020-10-02 00:19:56 +09:00
tom5079
bab77a4116 (KO) Added support link 2020-10-02 00:13:34 +09:00
tom5079
d20756ab96 Reset security mode 2020-10-01 22:51:52 +09:00
tom5079
dc75a753c3 Minimum thumbnail height 2020-10-01 22:46:03 +09:00
tom5079
4712d47903 Show 10 tags maximum 2020-10-01 22:20:49 +09:00
tom5079
c5561801e1 Add group name to GalleryBlock 2020-10-01 21:32:42 +09:00
tom5079
5c259fa07a Dependency update
Fixed duplicated download file
Better download progress update handling

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

View File

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

View File

@@ -1,18 +1,12 @@
# Pupil
![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true) ![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true)
*Pupil, Hitomi.la viewer for Android* *Pupil, Hitomi.la viewer for Android*
![](https://img.shields.io/github/downloads/tom5079/Pupil/total)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/latest/Pupil-v5.1.2.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.1.1-hotfix3/Pupil-v5.1.2.apk)
[![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v) [![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v)
# Screenshot # Features
![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.png?raw=true) ![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.jpg?raw=true)
*Main Screen*
![Reader Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/reader-screenshot.png?raw=true)
*Reader Screen*
Images are censored to be SFW
# Installation # Installation

View File

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

View File

@@ -11,8 +11,8 @@
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"properties": [], "properties": [],
"versionCode": 60, "versionCode": 61,
"versionName": "5.1-hotfix2", "versionName": "5.1.2",
"enabled": true, "enabled": true,
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }

View File

@@ -25,7 +25,6 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
tools:replace="android:theme" tools:replace="android:theme"
android:largeHeap="true"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<meta-data <meta-data

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,6 +42,7 @@ import xyz.quaver.pupil.R
import xyz.quaver.pupil.client import xyz.quaver.pupil.client
import xyz.quaver.pupil.interceptors import xyz.quaver.pupil.interceptors
import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.ui.ReaderActivity
import xyz.quaver.pupil.util.cleanCache
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.pupil.util.ellipsize import xyz.quaver.pupil.util.ellipsize
@@ -295,6 +296,8 @@ class DownloadService : Service() {
} }
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch { fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
cleanCache(this@DownloadService)
if (progress.containsKey(galleryID)) if (progress.containsKey(galleryID))
cancel(galleryID) cancel(galleryID)

View File

@@ -23,6 +23,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
import android.text.util.Linkify
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent import android.view.MotionEvent
@@ -31,8 +32,8 @@ import android.widget.*
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.text.util.LinkifyCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import com.bumptech.glide.Glide
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@@ -59,6 +60,7 @@ import xyz.quaver.pupil.util.checkUpdate
import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.pupil.util.restore import xyz.quaver.pupil.util.restore
import java.util.regex.Pattern
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.min import kotlin.math.min
@@ -142,21 +144,10 @@ class MainActivity :
} }
} }
override fun onResume() {
super.onResume()
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel() (main_recyclerview?.adapter as? GalleryBlockAdapter)?.updateAll = false
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
@@ -214,6 +205,8 @@ class MainActivity :
} }
) )
Linkify.addLinks(main_noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) })
//NavigationView //NavigationView
main_nav_view.setNavigationItemSelectedListener(this) main_nav_view.setNavigationItemSelectedListener(this)
@@ -263,11 +256,7 @@ class MainActivity :
if (it?.isEmpty() == false) { if (it?.isEmpty() == false) {
val galleryID = it.random() val galleryID = it.random()
GalleryDialog( GalleryDialog(this@MainActivity, galleryID).apply {
this@MainActivity,
Glide.with(this@MainActivity),
galleryID
).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
runOnUiThread { runOnUiThread {
query = it.toQuery() query = it.toQuery()
@@ -298,12 +287,22 @@ class MainActivity :
setTitle(R.string.main_open_gallery_by_id) setTitle(R.string.main_open_gallery_by_id)
setPositiveButton(android.R.string.ok) { _, _ -> setPositiveButton(android.R.string.ok) { _, _ ->
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID)
}
startActivity(intent) GalleryDialog(this@MainActivity, galleryID).apply {
onChipClickedHandler.add {
runOnUiThread {
query = it.toQuery()
currentPage = 0
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
dismiss()
}
}.show()
} }
}.show() }.show()
} }
@@ -318,7 +317,7 @@ class MainActivity :
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun setupRecyclerView() { private fun setupRecyclerView() {
with(main_recyclerview) { with(main_recyclerview) {
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply { adapter = GalleryBlockAdapter(galleries).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
runOnUiThread { runOnUiThread {
query = it.toQuery() query = it.toQuery()
@@ -332,16 +331,13 @@ class MainActivity :
} }
onDownloadClickedHandler = { position -> onDownloadClickedHandler = { position ->
val galleryID = galleries[position] val galleryID = galleries[position]
if (Preferences["cache_disable"])
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show() if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
DownloadService.cancel(this@MainActivity, galleryID)
}
else { else {
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress DownloadManager.getInstance(context).addDownloadFolder(galleryID)
DownloadService.cancel(this@MainActivity, galleryID) DownloadService.download(this@MainActivity, galleryID)
}
else {
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
DownloadService.download(this@MainActivity, galleryID)
}
} }
closeAllItems() closeAllItems()
@@ -384,11 +380,7 @@ class MainActivity :
val galleryID = galleries[position] val galleryID = galleries[position]
GalleryDialog( GalleryDialog(this@MainActivity, galleryID).apply {
this@MainActivity,
Glide.with(this@MainActivity),
galleryID
).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
runOnUiThread { runOnUiThread {
query = it.toQuery() query = it.toQuery()
@@ -987,14 +979,4 @@ class MainActivity :
} }
} }
} }
override fun onLowMemory() {
super.onLowMemory()
Glide.get(this).onLowMemory()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
Glide.get(this).onTrimMemory(level)
}
} }

View File

@@ -43,7 +43,6 @@ import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.mlkit.vision.face.Face import com.google.mlkit.vision.face.Face
@@ -99,7 +98,6 @@ class ReaderActivity : BaseActivity() {
} }
} }
private val timer = Timer()
private val snapHelper = PagerSnapHelper() private val snapHelper = PagerSnapHelper()
private var menu: Menu? = null private var menu: Menu? = null
@@ -139,38 +137,7 @@ class ReaderActivity : BaseActivity() {
return return
} }
if (Preferences["cache_disable"]) { initDownloadListener()
reader_download_progressbar.visibility = View.GONE
CoroutineScope(Dispatchers.IO).launch {
val reader = cache.getReader()
launch(Dispatchers.Main) initDownloader@{
if (reader == null) {
Snackbar
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
.show()
return@initDownloader
}
histories.add(galleryID)
(reader_recyclerview.adapter as ReaderAdapter).apply {
this.reader = reader
notifyDataSetChanged()
}
title = reader.galleryInfo.title ?: ""
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}"
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
when (reader.code) {
Code.HITOMI -> R.drawable.hitomi
Code.HIYOBI -> R.drawable.ic_hiyobi
else -> android.R.color.transparent
})
}
}
} else
initDownloadListener()
initView() initView()
} }
@@ -270,8 +237,7 @@ class ReaderActivity : BaseActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
timer.cancel() update = false
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
} }
override fun onBackPressed() { override fun onBackPressed() {
@@ -306,44 +272,53 @@ class ReaderActivity : BaseActivity() {
} }
} }
private var update = true
private fun initDownloadListener() { private fun initDownloadListener() {
timer.schedule(1000, 1000) { CoroutineScope(Dispatchers.Main).launch {
val downloader = downloader ?: return@schedule while (update) {
delay(1000)
if (!downloader.progress.containsKey(galleryID)) //loading val downloader = downloader ?: continue
return@schedule
if (downloader.progress[galleryID]?.isEmpty() == true) { //Gallery not found if (!downloader.progress.containsKey(galleryID)) //loading
timer.cancel() continue
Snackbar
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
.show()
}
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
}
histories.add(galleryID)
runOnUiThread {
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0 reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
reader_download_progressbar.progress = downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0 reader_download_progressbar.progress =
downloader.progress[galleryID]?.count { it.isInfinite() } ?: 0
if (title == getString(R.string.reader_loading)) { if (title == getString(R.string.reader_loading)) {
val reader = cache.metadata.reader val reader = cache.metadata.reader
if (reader != null) { if (reader != null) {
with (reader_recyclerview.adapter as ReaderAdapter) { with(reader_recyclerview.adapter as ReaderAdapter) {
this.reader = reader this.reader = reader
notifyDataSetChanged() notifyDataSetChanged()
} }
title = reader.galleryInfo.title title = reader.galleryInfo.title
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.files.size}" menu?.findItem(R.id.reader_menu_page_indicator)?.title =
"$currentPage/${reader.galleryInfo.files.size}"
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity, menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(
this@ReaderActivity,
when (reader.code) { when (reader.code) {
Code.HITOMI -> R.drawable.hitomi Code.HITOMI -> R.drawable.hitomi
Code.HIYOBI -> R.drawable.ic_hiyobi Code.HIYOBI -> R.drawable.ic_hiyobi
else -> android.R.color.transparent else -> android.R.color.transparent
}) }
)
} }
} }
@@ -396,19 +371,15 @@ class ReaderActivity : BaseActivity() {
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
setOnClickListener { setOnClickListener {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("cache_disable", false)) val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show()
else {
val downloadManager = DownloadManager.getInstance(this@ReaderActivity)
if (downloadManager.isDownloading(galleryID)) { if (downloadManager.isDownloading(galleryID)) {
downloadManager.deleteDownloadFolder(galleryID) downloadManager.deleteDownloadFolder(galleryID)
animateDownloadFAB(false) animateDownloadFAB(false)
} else { } else {
downloadManager.addDownloadFolder(galleryID) downloadManager.addDownloadFolder(galleryID)
DownloadService.download(context, galleryID, true) DownloadService.download(context, galleryID, true)
animateDownloadFAB(true) animateDownloadFAB(true)
}
} }
} }
} }
@@ -637,14 +608,4 @@ class ReaderActivity : BaseActivity() {
} }
} }
} }
override fun onLowMemory() {
super.onLowMemory()
Glide.get(this).onLowMemory()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
Glide.get(this).onTrimMemory(level)
}
} }

View File

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

View File

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

View File

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

View File

@@ -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()
}
}

View File

@@ -20,6 +20,7 @@ package xyz.quaver.pupil.util.downloader
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.net.Uri
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -131,8 +132,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
} }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
suspend fun getThumbnail(): ByteArray? = suspend fun getThumbnail(): Uri =
findFile(".thumbnail")?.readBytes() findFile(".thumbnail")?.uri
?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) { ?: getGalleryBlock()?.thumbnails?.firstOrNull()?.let { withContext(Dispatchers.IO) {
kotlin.runCatching { kotlin.runCatching {
val request = Request.Builder() val request = Request.Builder()
@@ -140,10 +141,10 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
.build() .build()
client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() } client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() }
}.getOrNull()?.also { kotlin.run { }.getOrNull()?.let { thumbnail -> kotlin.runCatching {
cacheFolder.getChild(".thumbnail").writeBytes(it) cacheFolder.getChild(".thumbnail").also { it.writeBytes(thumbnail) }
} } }.getOrNull()?.uri }
} } } } ?: Uri.EMPTY
suspend fun getReader(): Reader? { suspend fun getReader(): Reader? {
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') } val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
@@ -185,13 +186,14 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
} }
fun getImage(index: Int): FileX? = fun getImage(index: Int): FileX? =
metadata.imageList?.get(index)?.let { findFile(it) } metadata.imageList?.getOrNull(index)?.let { findFile(it) }
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
fun putImage(index: Int, fileName: String, data: ByteArray) { fun putImage(index: Int, fileName: String, data: ByteArray) {
val file = cacheFolder.getChild(fileName) val file = cacheFolder.getChild(fileName)
file.createNewFile() if (!file.exists())
file.createNewFile()
file.writeBytes(data) file.writeBytes(data)
setMetadata { metadata -> metadata.imageList!![index] = fileName } setMetadata { metadata -> metadata.imageList!![index] = fileName }
} }

View File

@@ -19,35 +19,63 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import android.content.Context import android.content.Context
import android.os.storage.StorageManager import kotlinx.coroutines.CoroutineScope
import androidx.core.content.ContextCompat import kotlinx.coroutines.Dispatchers
import androidx.core.net.toUri import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.lang.reflect.Array
import java.net.URL
@Suppress("DEPRECATION") val mutex = Mutex()
@Deprecated("Use downloader.Cache instead") fun cleanCache(context: Context) = CoroutineScope(Dispatchers.IO).launch {
fun getCachedGallery(context: Context, galleryID: Int) = if (mutex.isLocked) return@launch
File(getDownloadDirectory(context), galleryID.toString()).let {
if (it.exists()) mutex.withLock {
it val cacheFolder = File(context.cacheDir, "imageCache")
else val downloadManager = DownloadManager.getInstance(context)
File(context.cacheDir, "imageCache/$galleryID")
cacheFolder.listFiles { file ->
val galleryID = file.name.toIntOrNull() ?: return@listFiles true
!(downloadManager.downloadFolderMap.containsKey(galleryID) || histories.contains(galleryID))
}?.forEach {
it.deleteRecursively()
}
DownloadManager.getInstance(context).downloadFolderMap.keys.forEach {
val folder = File(cacheFolder, it.toString())
if (!downloadManager.isDownloading(it) && folder.exists()) {
folder.deleteRecursively()
}
}
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.firstOrNull {
caches.contains(it.toString()) && !downloadManager.isDownloading(it)
} ?: return@withLock).let {
Cache.delete(it)
}
}
} }
}
@Suppress("DEPRECATION")
@Deprecated("Use downloader.Cache instead")
fun getDownloadDirectory(context: Context) =
Preferences.get<String>("dl_location").let {
if (it.isNotEmpty() && !it.startsWith("content"))
File(it)
else
context.getExternalFilesDir(null)!!
}
@Suppress("DEPRECATION")
@Deprecated("Use FileX instead")
fun File.isParentOf(another: File) =
another.absolutePath.startsWith(this.absolutePath)

View File

@@ -27,13 +27,11 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import android.util.Log
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -52,7 +50,9 @@ import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.hitomi.getReader import xyz.quaver.hitomi.getReader
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.io.util.getChild import xyz.quaver.io.util.getChild
import xyz.quaver.io.util.* import xyz.quaver.io.util.readText
import xyz.quaver.io.util.writeBytes
import xyz.quaver.io.util.writeText
import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.client import xyz.quaver.pupil.client

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -58,4 +58,24 @@
<item>SOCKS</item> <item>SOCKS</item>
</string-array> </string-array>
<string-array name="cache_size">
<item>0</item>
<item>1</item>
<item>2</item>
<item>4</item>
<item>8</item>
<item>16</item>
<item>32</item>
</string-array>
<string-array name="cache_size_text">
<item>@string/settings_cache_unlimited</item>
<item>1G</item>
<item>2G</item>
<item>4G</item>
<item>8G</item>
<item>16G</item>
<item>32G</item>
</string-array>
</resources> </resources>

View File

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

View File

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

View File

@@ -9,6 +9,10 @@
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string> <string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string>
<string name="discord" translatable="false">https://discord.gg/Stj4b5v</string> <string name="discord" translatable="false">https://discord.gg/Stj4b5v</string>
<!-- Korean only -->
<string name="https_text" translatable="false">해결법</string>
<string name="https" translatable="false">https://bit.ly/34dUBwy</string>
<string name="backup_url" translatable="false">http://ix.io/</string> <string name="backup_url" translatable="false">http://ix.io/</string>
<string name="main_settings" translatable="false">Settings</string> <string name="main_settings" translatable="false">Settings</string>
@@ -17,6 +21,8 @@
<string name="reader_imageview_description" translatable="false">Content ImageView</string> <string name="reader_imageview_description" translatable="false">Content ImageView</string>
<string name="page_indicator_placeholder" translatable="false">-/-</string> <string name="page_indicator_placeholder" translatable="false">-/-</string>
<string name="galleryblock_artist_with_group" translatable="false">%s (%s)</string>
<!-- Translate needed down here --> <!-- Translate needed down here -->
<string name="warning">Warning</string> <string name="warning">Warning</string>
@@ -155,8 +161,9 @@
<string name="settings_download_folder_available">%s available</string> <string name="settings_download_folder_available">%s available</string>
<string name="settings_download_folder_custom">Custom Location</string> <string name="settings_download_folder_custom">Custom Location</string>
<string name="settings_download_folder_not_writable">This folder is not writable. Please select another folder.</string> <string name="settings_download_folder_not_writable">This folder is not writable. Please select another folder.</string>
<string name="settings_cache_disable">Disable Cache</string> <string name="settings_cache_limit">Cache Limit</string>
<string name="settings_download_when_cache_disable_warning">Download is disabled when the cache is disabled</string> <string name="settings_cache_unlimited">Unlimited</string>
<string name="settings_nomedia_title">Hide image from gallery</string>
<string name="settings_low_quality">Low quality images</string> <string name="settings_low_quality">Low quality images</string>
<string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string> <string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string>
@@ -175,7 +182,6 @@
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string> <string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>
<string name="settings_dark_mode_title">Dark mode</string> <string name="settings_dark_mode_title">Dark mode</string>
<string name="settings_dark_mode_summary">Protect yourself against light attacks!</string> <string name="settings_dark_mode_summary">Protect yourself against light attacks!</string>
<string name="settings_nomedia_title">Hide image from gallery</string>
<string name="settings_import_old_galleries">Import old galleries</string> <string name="settings_import_old_galleries">Import old galleries</string>
<string name="settings_user_id">User ID</string> <string name="settings_user_id">User ID</string>
<string name="settings_user_id_toast">User ID is copied to clipboard</string> <string name="settings_user_id_toast">User ID is copied to clipboard</string>

View File

@@ -44,9 +44,13 @@
app:key="download_folder" app:key="download_folder"
app:title="@string/settings_download_folder"/> app:title="@string/settings_download_folder"/>
<SwitchPreferenceCompat <ListPreference
app:key="cache_disable" app:key="cache_limit"
app:title="@string/settings_cache_disable"/> app:title="@string/settings_cache_limit"
app:entries="@array/cache_size_text"
app:entryValues="@array/cache_size"
app:defaultValue="8"
app:useSimpleSummaryProvider="true"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:key="nomedia" app:key="nomedia"

View File

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,4 +1,4 @@
#Thu Jun 18 15:48:09 KST 2020 #Thu Oct 01 20:54:37 KST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

51
gradlew vendored
View File

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

18
gradlew.bat vendored
View File

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