Compare commits

...

3 Commits

Author SHA1 Message Date
tom5079
608c6e6a1d App built 2020-09-26 21:01:36 +09:00
tom5079
bb2c91145f Dependency update 2020-09-26 20:58:46 +09:00
tom5079
db074df0f7 Fixed Download Concurrency issue
Fixed image not showing up after reader is paused and resumed
2020-09-26 11:07:35 +09:00
9 changed files with 71 additions and 96 deletions

View File

@@ -21,7 +21,7 @@ android {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 60 versionCode 60
versionName "5.1-hotfix1" versionName "5.1-hotfix3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
@@ -60,60 +60,58 @@ android {
} }
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 "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.android.material:material:1.3.0-alpha02"
implementation 'com.google.firebase:firebase-core:17.5.0' implementation "com.google.firebase:firebase-core:17.5.0"
implementation 'com.google.firebase:firebase-analytics:17.5.0' implementation "com.google.firebase:firebase-analytics:17.5.0"
implementation 'com.google.firebase:firebase-crashlytics:17.2.1' implementation "com.google.firebase:firebase-crashlytics:17.2.1"
implementation 'com.google.firebase:firebase-perf:19.0.8' implementation "com.google.firebase:firebase-perf:19.0.8"
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0' 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.google.android.gms:play-services-mlkit-face-detection:16.1.1"
implementation 'com.github.clans:fab:1.6.4' implementation "com.github.clans:fab:1.6.4"
//implementation 'com.quiph.ui:recyclerviewfastscroller:0.2.1' //implementation "com.quiph.ui:recyclerviewfastscroller:0.2.1"
//noinspection GradleDependency //noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.12.12' implementation "com.squareup.okhttp3:okhttp:3.12.12"
implementation 'com.github.bumptech.glide:glide:4.11.0' implementation "com.github.bumptech.glide:glide:4.11.0"
implementation ("com.github.bumptech.glide:okhttp3-integration:4.11.0") { implementation ("com.github.bumptech.glide:okhttp3-integration:4.11.0") {
transitive = false transitive = false
} }
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
transitive = false transitive = false
} }
implementation 'com.github.bumptech.glide:annotations:4.11.0' implementation "com.github.bumptech.glide:annotations:4.11.0"
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' annotationProcessor "com.github.bumptech.glide:compiler:4.11.0"
kapt 'com.github.bumptech.glide:compiler:4.11.0' kapt "com.github.bumptech.glide:compiler:4.11.0"
implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") { implementation ("com.github.bumptech.glide:recyclerview-integration:4.11.0") {
transitive = false transitive = false
} }
implementation 'com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2' implementation "com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2"
implementation 'com.gu:option:1.3' implementation "com.gu:option:1.3"
implementation 'net.rdrei.android.dirchooser:library:3.2@aar' implementation "net.rdrei.android.dirchooser:library:3.2@aar"
implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation "com.github.chrisbanes:PhotoView:2.3.0"
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0' implementation "com.andrognito.patternlockview:patternlockview:1.0.0"
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0' //implementation "com.andrognito.pinlockview:pinlockview:2.1.0"
implementation "ru.noties.markwon:core:3.1.0" implementation "ru.noties.markwon:core:3.1.0"
implementation ("xyz.quaver:libpupil:1.6") { implementation "xyz.quaver:libpupil:1.7.2"
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
}
implementation "xyz.quaver:documentfilex:0.2.15" implementation "xyz.quaver:documentfilex:0.2.15"
implementation "xyz.quaver:floatingsearchview:1.0.4" implementation "xyz.quaver:floatingsearchview:1.0.5"
testImplementation 'junit:junit:4.13' testImplementation "junit:junit:4.13"
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation "androidx.test.ext:junit:1.1.2"
androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation "androidx.test:rules:1.3.0"
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation "androidx.test:runner:1.3.0"
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation "androidx.test.espresso:espresso-core:3.3.0"
} }
androidExtensions { androidExtensions {

View File

@@ -12,7 +12,7 @@
"filters": [], "filters": [],
"properties": [], "properties": [],
"versionCode": 60, "versionCode": 60,
"versionName": "5.1-hotfix1", "versionName": "5.1-hotfix3",
"enabled": true, "enabled": true,
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }

View File

@@ -23,7 +23,6 @@ import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.util.SparseArray
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.TaskStackBuilder import androidx.core.app.TaskStackBuilder
@@ -49,10 +48,9 @@ import xyz.quaver.pupil.util.ellipsize
import xyz.quaver.pupil.util.normalizeID import xyz.quaver.pupil.util.normalizeID
import xyz.quaver.pupil.util.requestBuilders import xyz.quaver.pupil.util.requestBuilders
import java.io.IOException import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.ceil import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.log10 import kotlin.math.log10
import kotlin.math.roundToInt
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
class DownloadService : Service() { class DownloadService : Service() {
@@ -71,7 +69,7 @@ class DownloadService : Service() {
.setOngoing(true) .setOngoing(true)
} }
private val notification = SparseArray<NotificationCompat.Builder?>() private val notification = ConcurrentHashMap<Int, NotificationCompat.Builder?>()
private fun initNotification(galleryID: Int) { private fun initNotification(galleryID: Int) {
val intent = Intent(this, ReaderActivity::class.java) val intent = Intent(this, ReaderActivity::class.java)
@@ -199,7 +197,7 @@ class DownloadService : Service() {
* 0 <= value < 100 -> Download in progress * 0 <= value < 100 -> Download in progress
* Float.POSITIVE_INFINITY -> Download completed * Float.POSITIVE_INFINITY -> Download completed
*/ */
val progress = SparseArray<MutableList<Float>?>() val progress = ConcurrentHashMap<Int, MutableList<Float>>()
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
@@ -297,7 +295,7 @@ 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 {
if (progress.indexOfKey(galleryID) >= 0) if (progress.containsKey(galleryID))
cancel(galleryID) cancel(galleryID)
val cache = Cache.getInstance(this@DownloadService, galleryID) val cache = Cache.getInstance(this@DownloadService, galleryID)
@@ -309,30 +307,18 @@ class DownloadService : Service() {
// Gallery doesn't exist // Gallery doesn't exist
if (reader == null) { if (reader == null) {
delete(galleryID) delete(galleryID)
progress.put(galleryID, null) progress[galleryID] = mutableListOf()
return@launch return@launch
} }
val list = MutableList(reader.galleryInfo.files.size) { 0F } progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 0F }
cache.metadata.imageList?.let { cache.metadata.imageList?.let {
if (list.size != it.size) {
FirebaseCrashlytics.getInstance().log(
"""
GALLERYID: $galleryID
${it.size} - ${list.size}
""".trimIndent()
)
error("ImageList Size does not match")
}
it.forEachIndexed { index, image -> it.forEachIndexed { index, image ->
list[index] = if (image != null) Float.POSITIVE_INFINITY else 0F progress[galleryID]?.set(index, if (image != null) Float.POSITIVE_INFINITY else 0F)
} }
} }
progress.put(galleryID, list)
if (isCompleted(galleryID)) { if (isCompleted(galleryID)) {
if (DownloadManager.getInstance(this@DownloadService) if (DownloadManager.getInstance(this@DownloadService)
.getDownloadFolder(galleryID) != null ) .getDownloadFolder(galleryID) != null )
@@ -357,18 +343,8 @@ class DownloadService : Service() {
} }
} }
reader.requestBuilders.also { reader.requestBuilders.forEachIndexed { index, it ->
if (it.size != list.size) { if (progress[galleryID]?.get(index)?.isInfinite() == false) {
FirebaseCrashlytics.getInstance().log(
"""
GALLERYID: $galleryID
${it.size} - ${list.size}
""".trimIndent()
)
error("Requests Size does not match")
}
}.forEachIndexed { index, it ->
if (!list[index].isInfinite()) {
val request = it.tag(Tag(galleryID, index, startId)).build() val request = it.tag(Tag(galleryID, index, startId)).build()
client.newCall(request).enqueue(callback) client.newCall(request).enqueue(callback)
} }

View File

@@ -621,7 +621,7 @@ class MainActivity :
else -> { else -> {
searchHistory.map { searchHistory.map {
Suggestion(it) Suggestion(it)
}.takeLast(20) + FavoriteHistorySwitch(getString(R.string.search_show_tags)) }.takeLast(10) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
} }
}.reversed() }.reversed()

View File

@@ -88,7 +88,10 @@ class ReaderActivity : BaseActivity() {
var downloader: DownloadService? = null var downloader: DownloadService? = null
private val conn = object: ServiceConnection { private val conn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) { override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
downloader = (service as DownloadService.Binder).service downloader = (service as DownloadService.Binder).service.also {
if (!it.progress.containsKey(galleryID))
DownloadService.download(this@ReaderActivity, galleryID, true)
}
} }
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
@@ -166,7 +169,7 @@ class ReaderActivity : BaseActivity() {
} }
} }
} else } else
initDownloader() initDownloadListener()
initView() initView()
} }
@@ -259,6 +262,9 @@ class ReaderActivity : BaseActivity() {
if (downloader != null) if (downloader != null)
unbindService(conn) unbindService(conn)
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
DownloadService.cancel(this, galleryID)
} }
override fun onDestroy() { override fun onDestroy() {
@@ -266,9 +272,6 @@ class ReaderActivity : BaseActivity() {
timer.cancel() timer.cancel()
(reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel() (reader_recyclerview?.adapter as? ReaderAdapter)?.timer?.cancel()
if (!DownloadManager.getInstance(this).isDownloading(galleryID))
DownloadService.cancel(this, galleryID)
} }
override fun onBackPressed() { override fun onBackPressed() {
@@ -303,16 +306,14 @@ class ReaderActivity : BaseActivity() {
} }
} }
private fun initDownloader() { private fun initDownloadListener() {
DownloadService.download(this, galleryID, true)
timer.schedule(1000, 1000) { timer.schedule(1000, 1000) {
val downloader = downloader ?: return@schedule val downloader = downloader ?: return@schedule
if (downloader.progress.indexOfKey(galleryID) < 0) //loading if (!downloader.progress.containsKey(galleryID)) //loading
return@schedule return@schedule
if (downloader.progress[galleryID] == null) { //Gallery not found if (downloader.progress[galleryID]?.isEmpty() == true) { //Gallery not found
timer.cancel() timer.cancel()
Snackbar Snackbar
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE) .make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)

View File

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

View File

@@ -45,23 +45,24 @@ class TagChip(context: Context, _tag: Tag) : Chip(context) {
}.toMap() }.toMap()
init { init {
chipIcon = when(tag.area) { when(tag.area) {
"male" -> { "male" -> {
setChipBackgroundColorResource(R.color.material_blue_700) setChipBackgroundColorResource(R.color.material_blue_700)
setTextColor(ContextCompat.getColor(context, android.R.color.white)) setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.gender_male_white) setCloseIconTintResource(android.R.color.white)
chipIcon = ContextCompat.getDrawable(context, R.drawable.gender_male_white)
} }
"female" -> { "female" -> {
setChipBackgroundColorResource(R.color.material_pink_600) setChipBackgroundColorResource(R.color.material_pink_600)
setTextColor(ContextCompat.getColor(context, android.R.color.white)) setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.gender_female_white) setCloseIconTintResource(android.R.color.white)
chipIcon = ContextCompat.getDrawable(context, R.drawable.gender_female_white)
} }
else -> null
}.also {
if (favoriteTags.contains(tag))
setChipBackgroundColorResource(R.color.material_orange_500)
} }
if (favoriteTags.contains(tag))
setChipBackgroundColorResource(R.color.material_orange_500)
isCloseIconVisible = true isCloseIconVisible = true
closeIcon = ContextCompat.getDrawable(context, closeIcon = ContextCompat.getDrawable(context,
if (favoriteTags.contains(tag)) if (favoriteTags.contains(tag))

View File

@@ -20,7 +20,6 @@ package xyz.quaver.pupil.util.downloader
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.util.SparseArray
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -38,6 +37,7 @@ import xyz.quaver.io.util.*
import xyz.quaver.pupil.client import xyz.quaver.pupil.client
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import java.io.IOException import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
@Serializable @Serializable
data class Metadata( data class Metadata(
@@ -51,7 +51,7 @@ data class Metadata(
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) { class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
companion object { companion object {
val instances = SparseArray<Cache>() val instances = ConcurrentHashMap<Int, Cache>()
fun getInstance(context: Context, galleryID: Int) = fun getInstance(context: Context, galleryID: Int) =
instances[galleryID] ?: synchronized(this) { instances[galleryID] ?: synchronized(this) {
@@ -61,7 +61,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
@Synchronized @Synchronized
fun delete(galleryID: Int) { fun delete(galleryID: Int) {
instances[galleryID]?.cacheFolder?.deleteRecursively() instances[galleryID]?.cacheFolder?.deleteRecursively()
instances.delete(galleryID) instances.remove(galleryID)
} }
} }

View File

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