diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index ea39b091..9b90dbbe 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -51,7 +51,6 @@ class Pupil : MultiDexApplication() { val preference = PreferenceManager.getDefaultSharedPreferences(this) histories = Histories(File(ContextCompat.getDataDir(this), "histories.json")) - downloads = Histories(File(ContextCompat.getDataDir(this), "downloads.json")) favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json")) try { @@ -64,7 +63,7 @@ class Pupil : MultiDexApplication() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply { + val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_MIN).apply { description = getString(R.string.channel_download_description) enableLights(false) enableVibration(false) diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt index 50ef6cde..67f812e4 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt @@ -21,6 +21,7 @@ package xyz.quaver.pupil.adapters import android.content.Context import android.graphics.drawable.Drawable import android.util.Base64 +import android.util.Log import android.util.SparseBooleanArray import android.view.LayoutInflater import android.view.View @@ -29,6 +30,7 @@ import android.widget.LinearLayout import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.bumptech.glide.Glide @@ -65,9 +67,54 @@ class GalleryBlockAdapter(context: Context, private val galleries: List + file.nameWithoutExtension.toIntOrNull() != null + }?.size ?: 0 + + if (visibility == View.GONE) { + visibility = View.VISIBLE + max = reader.galleryInfo.size + } + + if (progress == max) { + if (completeFlag.get(galleryID, false)) { + with(view.galleryblock_progress_complete) { + setImageResource(R.drawable.ic_progressbar) + visibility = View.VISIBLE + } + } else { + with(view.galleryblock_progress_complete) { + setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply { + this?.start() + }) + visibility = View.VISIBLE + } + completeFlag.put(galleryID, true) + } + } else + view.galleryblock_progress_complete.visibility = View.INVISIBLE + } + } + fun bind(galleryBlock: GalleryBlock) { with(view) { val resources = context.resources @@ -80,6 +127,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List - file.nameWithoutExtension.toIntOrNull() != null - }?.size ?: 0 - - if (visibility == View.GONE) { - visibility = View.VISIBLE - max = _reader.galleryInfo.size - } - - if (progress == max) { - if (completeFlag.get(galleryBlock.id, false)) { - with(view.galleryblock_progress_complete) { - setImageResource(R.drawable.ic_progressbar) - visibility = View.VISIBLE - } - } else { - with(view.galleryblock_progress_complete) { - setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply { - this?.start() - }) - visibility = View.VISIBLE - } - completeFlag.put(galleryBlock.id, true) - } - } else - view.galleryblock_progress_complete.visibility = View.INVISIBLE - } - } + timerTask = timer.schedule(0, 1000) { + updateProgress(context, galleryBlock.id) } galleryblock_title.text = galleryBlock.title @@ -343,8 +355,10 @@ class GalleryBlockAdapter(context: Context, private val galleries: List (Unit))? = null @@ -81,51 +82,56 @@ class ReaderAdapter(private val context: Context, onItemClickListener?.invoke(position) } + holder.view.container.setOnClickListener { + onItemClickListener?.invoke(position) + } + (holder.view.container.layoutParams as ConstraintLayout.LayoutParams) .dimensionRatio = "${reader!!.galleryInfo[position].width}:${reader!!.galleryInfo[position].height}" - holder.view.reader_item_progressbar.progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)?.roundToInt() ?: 0 holder.view.reader_index.text = (position+1).toString() - val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position) - if (progress?.isFinite() == false) { - when { - progress.isInfinite() -> { - var image = Cache(context).getImages(galleryID) - - while (image?.get(position) == null) - image = Cache(context).getImages(galleryID) - - glide - .load(image[position]) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(true) - .error(R.drawable.image_broken_variant) - .apply { - if (BuildConfig.CENSOR) - override(5, 8) - } - .into(holder.view.image) - } - progress.isNaN() -> { - glide - .load(R.drawable.image_broken_variant) - .into(holder.view.image) - Snackbar - .make( - holder.view, - DownloadWorker.getInstance(context).exception[galleryID]!![position]?.message - ?: context.getText(R.string.default_error_msg), - Snackbar.LENGTH_INDEFINITE - ) - .show() + val images = Cache(context).getImages(galleryID) + if (images?.get(position) != null) { + glide + .load(images[position]) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .error(R.drawable.image_broken_variant) + .apply { + if (BuildConfig.CENSOR) + override(5, 8) } + .into(holder.view.image) + } else { + val progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position) + + if (progress?.isNaN() == true) { + glide + .load(R.drawable.image_broken_variant) + .into(holder.view.image) + Snackbar + .make( + holder.view, + DownloadWorker.getInstance(context).exception[galleryID]!![position]?.message + ?: context.getText(R.string.default_error_msg), + Snackbar.LENGTH_INDEFINITE + ) + .show() + + return } - } else { + holder.view.reader_item_progressbar.progress = + if (progress?.isInfinite() == true) + 100 + else + progress?.roundToInt() ?: 0 + holder.view.image.setImageDrawable(null) - Timer().schedule(1000) { + + timer.schedule(1000) { CoroutineScope(Dispatchers.Main).launch { notifyItemChanged(position) } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 06129222..f0111bba 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -110,7 +110,6 @@ class MainActivity : AppCompatActivity() { private var currentPage = 0 private lateinit var histories: Histories - private lateinit var downloads: Histories private lateinit var favorites: Histories override fun onCreate(savedInstanceState: Bundle?) { @@ -148,7 +147,6 @@ class MainActivity : AppCompatActivity() { with(application as Pupil) { this@MainActivity.histories = histories - this@MainActivity.downloads = downloads this@MainActivity.favorites = favorites } @@ -174,6 +172,12 @@ class MainActivity : AppCompatActivity() { } } + override fun onDestroy() { + super.onDestroy() + + (main_recyclerview.adapter as GalleryBlockAdapter).timer.cancel() + } + override fun onResume() { val preferences = PreferenceManager.getDefaultSharedPreferences(this) @@ -405,7 +409,6 @@ class MainActivity : AppCompatActivity() { if (worker.progress.indexOfKey(galleryID) >= 0) //download in progress worker.cancel(galleryID) else { - Cache(context).moveToDownload(galleryID) Cache(context).setDownloading(galleryID, true) if (!worker.queue.contains(galleryID)) @@ -429,7 +432,6 @@ class MainActivity : AppCompatActivity() { cache = Cache(context).getCachedGallery(galleryID) } - downloads.remove(galleryID) histories.remove(galleryID) if (this@MainActivity.mode != Mode.SEARCH) @@ -963,8 +965,14 @@ class MainActivity : AppCompatActivity() { } } Mode.DOWNLOAD -> { + val downloads = getDownloadDirectory(this@MainActivity).listFiles { file -> + file.isDirectory and (file.name.toIntOrNull() != null) and File(file, ".metadata").exists() + }?.map { + it.name.toInt() + }?: listOf() + when { - query.isEmpty() -> downloads.toList().apply { + query.isEmpty() -> downloads.apply { totalItems = size } else -> { diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index d71d1562..1d84575b 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -38,9 +38,6 @@ import io.fabric.sdk.android.Fabric import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.android.synthetic.main.activity_reader.view.* import kotlinx.android.synthetic.main.dialog_numberpicker.view.* -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.serialization.ImplicitReflectionSerializer import xyz.quaver.hitomi.Reader import xyz.quaver.pupil.Pupil @@ -199,6 +196,7 @@ class ReaderActivity : AppCompatActivity() { super.onDestroy() timer.cancel() + (reader_recyclerview.adapter as ReaderAdapter).timer.cancel() if (!Cache(this).isDownloading(galleryID)) DownloadWorker.getInstance(this@ReaderActivity).cancel(galleryID) @@ -327,10 +325,6 @@ class ReaderActivity : AppCompatActivity() { animateDownloadFAB(false) } else { - CoroutineScope(Dispatchers.IO).launch { - Cache(context).moveToDownload(galleryID) - } - Cache(context).setDownloading(galleryID, true) animateDownloadFAB(true) } diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt index 7c0e8721..93a6ec73 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/download/Cache.kt @@ -21,8 +21,6 @@ package xyz.quaver.pupil.util.download import android.content.Context import android.content.ContextWrapper import android.util.Base64 -import android.util.Log -import android.util.SparseArray import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import kotlinx.coroutines.* @@ -175,15 +173,13 @@ class Cache(context: Context) : ContextWrapper(context) { } } - fun getImages(galleryID: Int): SparseArray? { + fun getImages(galleryID: Int): List? { val gallery = getCachedGallery(galleryID) ?: return null + val reader = getReaderOrNull(galleryID) ?: return null + val images = gallery.listFiles() ?: return null - return SparseArray().apply { - gallery.listFiles { file -> - file.nameWithoutExtension.toIntOrNull() != null - }?.forEach { - put(it.nameWithoutExtension.toInt(), it) - } + return reader.galleryInfo.indices.map { index -> + images.firstOrNull { file -> file.nameWithoutExtension.toIntOrNull() == index } } } @@ -209,7 +205,7 @@ class Cache(context: Context) : ContextWrapper(context) { val cache = getCachedGallery(galleryID) if (cache != null) { - val download = getDownloadDirectory(this) + val download = File(getDownloadDirectory(this), galleryID.toString()) if (!download.isParentOf(cache)) { cache.copyRecursively(download, true) @@ -222,7 +218,6 @@ class Cache(context: Context) : ContextWrapper(context) { fun isDownloading(galleryID: Int) = getCachedMetadata(galleryID)?.isDownloading == true fun setDownloading(galleryID: Int, isDownloading: Boolean) { - Log.i("PUPILD", "$galleryID $isDownloading") setCachedMetadata(galleryID, Metadata(getCachedMetadata(galleryID), isDownloading = isDownloading)) } diff --git a/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt b/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt index 0e020604..b9bd9a46 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/download/DownloadWorker.kt @@ -18,10 +18,15 @@ package xyz.quaver.pupil.util.download +import android.app.PendingIntent import android.content.Context import android.content.ContextWrapper +import android.content.Intent import android.content.SharedPreferences import android.util.SparseArray +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.app.TaskStackBuilder import androidx.preference.PreferenceManager import com.crashlytics.android.Crashlytics import io.fabric.sdk.android.Fabric @@ -34,6 +39,8 @@ import xyz.quaver.hitomi.urlFromUrlFromHash import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.createImgList import xyz.quaver.hiyobi.user_agent +import xyz.quaver.pupil.R +import xyz.quaver.pupil.ui.ReaderActivity import java.io.IOException import java.util.concurrent.Executors import java.util.concurrent.LinkedBlockingQueue @@ -66,7 +73,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont private var bufferedSource : BufferedSource? = null override fun contentLength() = responseBody.contentLength() - override fun contentType() = responseBody.contentType() + override fun contentType() = responseBody.contentType() ?: null override fun source(): BufferedSource { if (bufferedSource == null) @@ -104,6 +111,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont } //endregion + val notificationManager = NotificationManagerCompat.from(context) + val queue = LinkedBlockingQueue() /* @@ -131,6 +140,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont * null -> Download in progress / Loading */ val exception = SparseArray?>() + val notification = SparseArray() private val loop = loop() private val worker = SparseArray() @@ -169,6 +179,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont progress.clear() exception.clear() + notification.clear() + notificationManager.cancelAll() nRunners = 0 @@ -187,15 +199,19 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont it.cancel() } - if (progress.indexOfKey(galleryID) >= 0) { - progress.remove(galleryID) - exception.remove(galleryID) + progress.remove(galleryID) + exception.remove(galleryID) + notification.remove(galleryID) + notificationManager.cancel(galleryID) + if (progress.indexOfKey(galleryID) >= 0) { Cache(this@DownloadWorker).setDownloading(galleryID, false) nRunners-- } } + fun isCompleted(galleryID: Int) = progress[galleryID]?.all { !it.isFinite() } == true + private fun queueDownload(galleryID: Int, reader: Reader, index: Int, callback: Callback) { val cache = Cache(this@DownloadWorker).getImages(galleryID) val lowQuality = preferences.getBoolean("low_quality", false) @@ -204,6 +220,18 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont cache?.get(index)?.let { progress[galleryID]?.set(index, Float.POSITIVE_INFINITY) + notify(galleryID) + + if (isCompleted(galleryID)) { + with(Cache(this@DownloadWorker)) { + if (isDownloading(galleryID)) { + moveToDownload(galleryID) + setDownloading(galleryID, false) + } + } + nRunners-- + } + return } @@ -250,6 +278,9 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont progress.put(galleryID, reader.galleryInfo.map { 0F }.toMutableList()) exception.put(galleryID, reader.galleryInfo.map { null }.toMutableList()) + notification[galleryID].setContentTitle(reader.title) + notify(galleryID) + for (i in reader.galleryInfo.indices) { val callback = object : Callback { override fun onFailure(call: Call, e: IOException) { @@ -259,11 +290,14 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont progress[galleryID]?.set(i, Float.NaN) exception[galleryID]?.set(i, e) - if (progress[galleryID]?.all { !it.isFinite() } == true) { - progress.remove(galleryID) - exception.remove(galleryID) + notify(galleryID) - Cache(this@DownloadWorker).setDownloading(galleryID, false) + if (isCompleted(galleryID)) { + val cache = Cache(this@DownloadWorker) + if (cache.isDownloading(galleryID)) { + cache.moveToDownload(galleryID) + cache.setDownloading(galleryID, false) + } nRunners-- } } @@ -278,11 +312,14 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont progress[galleryID]?.set(i, Float.POSITIVE_INFINITY) } - if (progress[galleryID]?.all { !it.isFinite() } == true) { - progress.remove(galleryID) - exception.remove(galleryID) + notify(galleryID) - Cache(this@DownloadWorker).setDownloading(galleryID, false) + if (isCompleted(galleryID)) { + val cache = Cache(this@DownloadWorker) + if (cache.isDownloading(galleryID)) { + cache.moveToDownload(galleryID) + cache.setDownloading(galleryID, false) + } nRunners-- } } @@ -292,6 +329,43 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont } } + private fun notify(galleryID: Int) { + val max = progress[galleryID]?.size ?: 0 + val progress = progress[galleryID]?.count { !it.isFinite() } ?: 0 + + if (isCompleted(galleryID)) + notification[galleryID] + .setContentText(getString(R.string.reader_notification_complete)) + .setProgress(0, 0, false) + else + notification[galleryID] + .setProgress(max, progress, false) + .setContentText("$progress/$max") + + if (Cache(this).isDownloading(galleryID)) + notificationManager.notify(galleryID, notification[galleryID].build()) + else + notificationManager.cancel(galleryID) + } + + private fun initNotification(galleryID: Int) { + val intent = Intent(this, ReaderActivity::class.java).apply { + putExtra("galleryID", galleryID) + } + val pendingIntent = TaskStackBuilder.create(this).run { + addNextIntentWithParentStack(intent) + getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) + } + + notification.put(galleryID, NotificationCompat.Builder(this, "download").apply { + setContentTitle(getString(R.string.reader_loading)) + setContentText(getString(R.string.reader_notification_text)) + setSmallIcon(android.R.drawable.stat_sys_download) // had to use this because old android doesn't support VectorDrawable on Notification :P + setContentIntent(pendingIntent) + setProgress(0, 0, true) + }) + } + private fun loop() = CoroutineScope(Dispatchers.Default).launch { while (true) { if (queue.isEmpty() || nRunners > preferences.getInt("max_download", 4)) @@ -299,6 +373,12 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont val galleryID = queue.poll() ?: continue + if (progress.indexOfKey(galleryID) >= 0) // Gallery already downloading! + continue + + initNotification(galleryID) + if (Cache(this@DownloadWorker).isDownloading(galleryID)) + notificationManager.notify(galleryID, notification[galleryID].build()) worker.put(galleryID, download(galleryID)) nRunners++ } diff --git a/app/src/main/res/menu/reader.xml b/app/src/main/res/menu/reader.xml index 3ac32581..77ca883b 100644 --- a/app/src/main/res/menu/reader.xml +++ b/app/src/main/res/menu/reader.xml @@ -27,8 +27,7 @@ + app:showAsAction="ifRoom"/>