Applying changed Download routines

This commit is contained in:
Pupil
2020-01-31 10:12:44 +09:00
parent 615b52c4fa
commit 48f90faf4e
18 changed files with 428 additions and 341 deletions

View File

@@ -18,7 +18,9 @@
package xyz.quaver.pupil.adapters
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.Base64
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.View
@@ -29,7 +31,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.RequestManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.daimajia.swipe.SwipeLayout
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
@@ -37,28 +39,22 @@ import com.daimajia.swipe.interfaces.SwipeAdapterInterface
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.item_galleryblock.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.getCachedGallery
import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.download.DownloadWorker
import xyz.quaver.pupil.util.wordCapitalize
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.concurrent.schedule
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
class GalleryBlockAdapter(context: Context, private val galleries: List<GalleryBlock>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
enum class ViewType {
NEXT,
@@ -66,10 +62,13 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
PREV
}
private val glide = Glide.with(context)
private lateinit var favorites: Histories
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
var timerTask: TimerTask? = null
fun bind(galleryBlock: GalleryBlock) {
with(view) {
val resources = context.resources
val languages = resources.getStringArray(R.array.languages).map {
@@ -78,16 +77,14 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
}
}.toMap()
val (galleryBlock: GalleryBlock, thumbnail: Deferred<String>) = item
val artists = galleryBlock.artists
val series = galleryBlock.series
CoroutineScope(Dispatchers.Main).launch {
val cache = thumbnail.await()
val thumbnail = Base64.decode(Cache(context).getThumbnail(galleryBlock.id), Base64.DEFAULT)
glide
.load(cache)
.load(thumbnail)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(R.drawable.image_broken_variant)
@@ -99,75 +96,67 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
}
//Check cache
val readerCache = { File(getCachedGallery(context, galleryBlock.id), "reader.json") }
val imageCache = { File(getCachedGallery(context, galleryBlock.id), "images") }
val cache = Cache(context).getCachedGallery(galleryBlock.id)
val reader = Cache(context).getReaderOrNull(galleryBlock.id)
try {
Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
} catch(e: Exception) {
readerCache.invoke().delete()
}
if (readerCache.invoke().exists()) {
val reader = Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
if (cache != null && reader != null) {
val count = cache.listFiles { file ->
file.nameWithoutExtension.toIntOrNull() != null
}?.size ?: 0
with(galleryblock_progressbar) {
max = reader.galleryInfo.size
progress = imageCache.invoke().list()?.size ?: 0
progress = count
visibility = View.VISIBLE
}
} else {
} else
galleryblock_progressbar.visibility = View.GONE
}
if (refreshTasks[this@GalleryViewHolder] == null) {
val refresh = Timer(false).schedule(0, 1000) {
post {
if (timerTask == null)
timerTask = Timer().schedule(0, 1000) {
CoroutineScope(Dispatchers.Main).launch {
val _cache = Cache(context).getCachedGallery(galleryBlock.id)
val _reader = Cache(context).getReaderOrNull(galleryBlock.id)
if (_reader == null) {
view.galleryblock_progressbar.visibility = View.GONE
view.galleryblock_progress_complete.visibility = View.GONE
return@launch
}
with(view.galleryblock_progressbar) {
progress = imageCache.invoke().list()?.size ?: 0
if (!readerCache.invoke().exists()) {
visibility = View.GONE
max = 0
progress = 0
progress = _cache?.listFiles { file ->
file.nameWithoutExtension.toIntOrNull() != null
}?.size ?: 0
view.galleryblock_progress_complete.visibility = View.INVISIBLE
} else {
if (visibility == View.GONE) {
val reader = Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
max = reader.galleryInfo.size
visibility = View.VISIBLE
}
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
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
}
}
}
refreshTasks[this@GalleryViewHolder] = refresh
}
galleryblock_title.text = galleryBlock.title
with(galleryblock_artist) {
text = artists.joinToString(", ") { it.wordCapitalize() }
@@ -277,7 +266,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
}
}
private val refreshTasks = HashMap<GalleryViewHolder, TimerTask>()
val completeFlag = SparseBooleanArray()
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
@@ -336,10 +324,11 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
override fun onStartOpen(layout: SwipeLayout?) {
mItemManger.closeAllExcept(layout)
holder.view.galleryblock_download.text = when(GalleryDownloader.get(gallery.first.id)) {
null -> holder.view.context.getString(R.string.main_download)
else -> holder.view.context.getString(android.R.string.cancel)
}
holder.view.galleryblock_download.text =
if (DownloadWorker.getInstance(holder.view.context).progress.indexOfKey(gallery.id) < 0)
holder.view.context.getString(R.string.main_download)
else
holder.view.context.getString(android.R.string.cancel)
}
override fun onClose(layout: SwipeLayout?) {}
@@ -354,12 +343,8 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
if (holder is GalleryViewHolder) {
val task = refreshTasks[holder] ?: return
task.cancel()
refreshTasks.remove(holder)
}
if (holder is GalleryViewHolder)
holder.timerTask?.cancel()
}
override fun getItemCount() =

View File

@@ -18,30 +18,47 @@
package xyz.quaver.pupil.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.item_reader.view.*
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.getCachedGallery
import java.io.File
import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.download.DownloadWorker
import java.util.*
import kotlin.concurrent.schedule
import kotlin.math.roundToInt
class ReaderAdapter(private val glide: RequestManager,
private val galleryID: Int,
private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
class ReaderAdapter(private val context: Context,
private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
var isFullScreen = false
var reader: Reader? = null
private val glide = Glide.with(context)
var onItemClickListener : ((Int) -> (Unit))? = null
init {
CoroutineScope(Dispatchers.IO).launch {
reader = Cache(context).getReader(galleryID)
launch(Dispatchers.Main) {
notifyDataSetChanged()
}
}
}
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -60,38 +77,62 @@ class ReaderAdapter(private val glide: RequestManager,
else
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
var reader: Reader? = null
with (GalleryDownloader[galleryID]?.reader) {
if (reader == null && this?.isCompleted == true)
runBlocking {
reader = await()
}
}
holder.view.image.setOnPhotoTapListener { _, _, _ ->
onItemClickListener?.invoke(position)
}
glide
.load(File(getCachedGallery(holder.view.context, galleryID), images[position]))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.error(R.drawable.image_broken_variant)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
else {
val galleryInfo = reader?.galleryInfo?.get(position)
(holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
.dimensionRatio = "${reader!!.galleryInfo[position].width}:${reader!!.galleryInfo[position].height}"
if (galleryInfo != null) {
(holder.view.image.layoutParams as ConstraintLayout.LayoutParams)
.dimensionRatio = "${galleryInfo.width}:${galleryInfo.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()
}
}
.into(holder.view.image)
} else {
holder.view.image.setImageDrawable(null)
Timer().schedule(1000) {
CoroutineScope(Dispatchers.Main).launch {
notifyItemChanged(position)
}
}
}
}
override fun getItemCount() = images.size
override fun getItemCount() = reader?.galleryInfo?.size ?: 0
}

View File

@@ -45,7 +45,6 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView
import com.bumptech.glide.Glide
import com.google.android.material.appbar.AppBarLayout
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main_content.*
@@ -64,11 +63,10 @@ import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.ui.dialog.GalleryDialog
import xyz.quaver.pupil.util.*
import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.download.DownloadWorker
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.ceil
@@ -89,7 +87,7 @@ class MainActivity : AppCompatActivity() {
POPULAR
}
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
private val galleries = ArrayList<GalleryBlock>()
private var query = ""
set(value) {
@@ -386,7 +384,7 @@ class MainActivity : AppCompatActivity() {
private fun setupRecyclerView() {
with(main_recyclerview) {
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply {
adapter = GalleryBlockAdapter(this@MainActivity, galleries).apply {
onChipClickedHandler.add {
runOnUiThread {
query = it.toQuery()
@@ -399,20 +397,18 @@ class MainActivity : AppCompatActivity() {
}
}
onDownloadClickedHandler = { position ->
val galleryID = galleries[position].first.id
val galleryID = galleries[position].id
if (!completeFlag.get(galleryID, false)) {
val downloader = GalleryDownloader.get(galleryID)
val worker = DownloadWorker.getInstance(context)
if (downloader == null)
GalleryDownloader(
context,
galleryID,
true
).start()
if (worker.progress.indexOfKey(galleryID) >= 0) //download in progress
worker.cancel(galleryID)
else {
downloader.cancel()
downloader.clearNotification()
Cache(context).moveToDownload(galleryID)
if (!worker.queue.contains(galleryID))
worker.queue.add(galleryID)
}
}
@@ -420,39 +416,28 @@ class MainActivity : AppCompatActivity() {
}
onDeleteClickedHandler = { position ->
val galleryID = galleries[position].first.id
val galleryID = galleries[position].id
CoroutineScope(Dispatchers.Default).launch {
with(GalleryDownloader[galleryID]) {
this?.cancelAndJoin()
this?.clearNotification()
DownloadWorker.getInstance(context).cancel(galleryID)
var cache = Cache(context).getCachedGallery(galleryID)
while (cache != null) {
cache.deleteRecursively()
cache = Cache(context).getCachedGallery(galleryID)
}
val cache = File(cacheDir, "imageCache/${galleryID}")
val data = getCachedGallery(context, galleryID)
cache.deleteRecursively()
data.deleteRecursively()
downloads.remove(galleryID)
if (this@MainActivity.mode == Mode.DOWNLOAD) {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
histories.remove(galleryID)
if (this@MainActivity.mode == Mode.HISTORY) {
if (this@MainActivity.mode != Mode.SEARCH)
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
completeFlag.put(galleryID, false)
}
@@ -466,7 +451,7 @@ class MainActivity : AppCompatActivity() {
return@setOnItemClickListener
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
val gallery = galleries[position].first
val gallery = galleries[position]
intent.putExtra("galleryID", gallery.id)
//TODO: Maybe sprinkling some transitions will be nice :D
@@ -478,7 +463,7 @@ class MainActivity : AppCompatActivity() {
if (v !is CardView)
return@setOnItemLongClickListener true
val galleryID = galleries[position].first.id
val galleryID = galleries[position].id
GalleryDialog(
this@MainActivity,
@@ -1030,41 +1015,21 @@ class MainActivity : AppCompatActivity() {
val json = Json(JsonConfiguration.Stable)
val serializer = GalleryBlock.serializer()
val galleryBlock =
File(getCachedGallery(this@MainActivity, galleryID), "galleryBlock.json").let { cache ->
when {
cache.exists() -> json.parse(serializer, cache.readText())
else -> {
getGalleryBlock(galleryID).apply {
this ?: return@apply
File(getCachedGallery(this@MainActivity, galleryID), "galleryBlock.json").let { cache ->
when {
cache.exists() -> json.parse(serializer, cache.readText())
else -> {
getGalleryBlock(galleryID).apply {
this ?: return@apply
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, this))
}
cache.writeText(json.stringify(serializer, this))
}
}
} ?: return@async null
val thumbnail = async {
val ext = galleryBlock.thumbnails[0].split('.').last()
File(getCachedGallery(this@MainActivity, galleryBlock.id), "thumbnail.$ext").apply {
if (!exists())
try {
with(URL(galleryBlock.thumbnails[0]).openConnection() as HttpsURLConnection) {
if (this@apply.parentFile?.exists() == false)
this@apply.parentFile!!.mkdirs()
inputStream.copyTo(FileOutputStream(this@apply))
}
} catch (e: Exception) {
delete()
}
}.absolutePath
}
Pair(galleryBlock, thumbnail)
}
} ?: return@async null
} catch (e: Exception) {
null
}

View File

@@ -21,39 +21,39 @@ package xyz.quaver.pupil.ui
import android.content.Intent
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.crashlytics.android.Crashlytics
import com.google.android.material.snackbar.Snackbar
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
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.download.DownloadWorker
import xyz.quaver.pupil.util.getDownloadDirectory
import xyz.quaver.pupil.util.isParentOf
import java.util.*
import kotlin.concurrent.schedule
class ReaderActivity : AppCompatActivity() {
private var galleryID = 0
private val images = ArrayList<String>()
private var gallerySize = 0
private var currentPage = 0
private var isScroll = true
@@ -69,7 +69,7 @@ class ReaderActivity : AppCompatActivity() {
}
}
private lateinit var downloader: GalleryDownloader
private val timer = Timer()
private val snapHelper = PagerSnapHelper()
@@ -101,12 +101,8 @@ class ReaderActivity : AppCompatActivity() {
return
}
initDownloader()
initView()
if (!downloader.download)
downloader.start()
initDownloader()
}
override fun onNewIntent(intent: Intent) {
@@ -168,7 +164,7 @@ class ReaderActivity : AppCompatActivity() {
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
with(view.dialog_number_picker) {
minValue=1
maxValue=gallerySize
maxValue=reader_recyclerview.adapter?.itemCount ?: 0
value=currentPage
}
val dialog = AlertDialog.Builder(this).apply {
@@ -201,8 +197,10 @@ class ReaderActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
if (::downloader.isInitialized && !downloader.download)
downloader.cancel()
timer.cancel()
if (!getDownloadDirectory(this).isParentOf(Cache(this).getCachedGallery(galleryID)))
DownloadWorker.getInstance(this).cancel(galleryID)
}
override fun onBackPressed() {
@@ -238,101 +236,54 @@ class ReaderActivity : AppCompatActivity() {
}
private fun initDownloader() {
var d: GalleryDownloader? = GalleryDownloader.get(galleryID)
if (d == null)
d = GalleryDownloader(this, galleryID)
downloader = d.apply {
onReaderLoadedHandler = {
CoroutineScope(Dispatchers.Main).launch {
title = it.title
with(reader_download_progressbar) {
max = it.galleryInfo.size
progress = 0
}
with(reader_progressbar) {
max = it.galleryInfo.size
progress = 0
}
gallerySize = it.galleryInfo.size
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.galleryInfo.size}"
}
}
onProgressHandler = {
CoroutineScope(Dispatchers.Main).launch {
reader_download_progressbar.progress = it
menu?.findItem(R.id.reader_menu_use_hiyobi)?.isVisible = downloader.useHiyobi
}
}
onDownloadedHandler = {
val item = it.toList()
CoroutineScope(Dispatchers.Main).launch {
if (images.isEmpty()) {
images.addAll(item)
reader_recyclerview.adapter?.notifyDataSetChanged()
} else {
images.add(item.last())
reader_recyclerview.adapter?.notifyItemInserted(images.size-1)
}
}
}
onErrorHandler = {
Snackbar
.make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.reader_help) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.error_help))))
}
.show()
downloader.download = false
}
onCompleteHandler = {
CoroutineScope(Dispatchers.Main).launch {
reader_download_progressbar.visibility = View.GONE
}
}
onNotifyChangedHandler = { notify ->
val fab = reader_fab_download
runOnUiThread {
if (notify) {
val icon = AnimatedVectorDrawableCompat.create(this, R.drawable.ic_downloading)
icon?.registerAnimationCallback(object: Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
if (downloader.download)
fab.post {
icon.start()
fab.labelText = getString(R.string.reader_fab_download_cancel)
}
else
fab.post {
fab.setImageResource(R.drawable.ic_download)
fab.labelText = getString(R.string.reader_fab_download)
}
}
})
fab.setImageDrawable(icon)
icon?.start()
} else {
runOnUiThread {
fab.setImageResource(R.drawable.ic_download)
}
}
}
}
val worker = DownloadWorker.getInstance(this).apply {
queue.add(galleryID)
}
if (downloader.download) {
downloader.invokeOnReaderLoaded()
downloader.invokeOnNotifyChanged()
timer.schedule(0, 1000) {
if (worker.progress.indexOfKey(galleryID) < 0) //loading
return@schedule
if (worker.progress[galleryID] == null) { //Gallery not found
timer.cancel()
Snackbar
.make(reader_layout, R.string.reader_failed_to_find_gallery, Snackbar.LENGTH_INDEFINITE)
.show()
}
runOnUiThread {
reader_download_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
reader_download_progressbar.progress = worker.progress[galleryID]!!.count { !it.isFinite() }
reader_progressbar.max = reader_recyclerview.adapter?.itemCount ?: 0
if (title == getString(R.string.reader_loading)) {
val reader = (reader_recyclerview.adapter as ReaderAdapter).reader
if (reader != null) {
title = reader.title
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${reader.galleryInfo.size}"
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(this@ReaderActivity,
when (reader.code) {
Reader.Code.HITOMI -> R.drawable.hitomi
Reader.Code.HIYOBI -> R.drawable.ic_hiyobi
else -> android.R.color.transparent
})
}
}
if (worker.progress[galleryID]!!.all { !it.isFinite() }) { //Download finished
reader_download_progressbar.visibility = View.GONE
animateDownloadFAB(false)
}
}
}
}
private fun initView() {
with(reader_recyclerview) {
adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID, images).apply {
adapter = ReaderAdapter(this@ReaderActivity, galleryID).apply {
onItemClickListener = {
if (isScroll) {
isScroll = false
@@ -360,19 +311,19 @@ class ReaderActivity : AppCompatActivity() {
if (layoutManager.findFirstVisibleItemPosition() == -1)
return
currentPage = layoutManager.findFirstVisibleItemPosition()+1
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/$gallerySize"
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${recyclerView.adapter!!.itemCount}"
this@ReaderActivity.reader_progressbar.progress = currentPage
}
})
}
with(reader_fab_download) {
setImageResource(R.drawable.ic_download)
setOnClickListener {
downloader.download = !downloader.download
animateDownloadFAB(getDownloadDirectory(context).isParentOf(Cache(context).getCachedGallery(galleryID))) //If download in progress, animate button
if (!downloader.download)
downloader.clearNotification()
setOnClickListener {
Cache(context).moveToDownload(galleryID)
animateDownloadFAB(true)
}
}
@@ -414,4 +365,32 @@ class ReaderActivity : AppCompatActivity() {
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
}
private fun animateDownloadFAB(animate: Boolean) {
with(reader_fab_download) {
if (animate) {
val icon = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_downloading)
icon?.registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
val worker = DownloadWorker.getInstance(context)
if (worker.progress[galleryID]?.all { !it.isFinite() } == true) // If download is finished, stop animating
post {
setImageResource(R.drawable.ic_download)
labelText = getString(R.string.reader_fab_download)
}
else // Or continue animating
post {
icon.start()
labelText = getString(R.string.reader_fab_download_cancel)
}
}
})
setImageDrawable(icon)
icon?.start()
} else
setImageResource(R.drawable.ic_download)
}
}
}

View File

@@ -35,7 +35,10 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_gallery.*
import kotlinx.android.synthetic.main.gallery_details.view.*
import kotlinx.android.synthetic.main.item_gallery_details.view.*
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import xyz.quaver.hitomi.Gallery
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.getGallery
@@ -221,9 +224,9 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
private fun addRelated(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
val galleries = ArrayList<GalleryBlock>()
val adapter = GalleryBlockAdapter(glide, galleries).apply {
val adapter = GalleryBlockAdapter(context, galleries).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
@@ -238,7 +241,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
}.let {
val galleryBlock = it.await() ?: return@let
galleries.add(Pair(galleryBlock, GlobalScope.async { galleryBlock.thumbnails.first() }))
galleries.add(galleryBlock)
adapter.notifyItemInserted(i)
}
}
@@ -254,14 +257,14 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, _ ->
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleries[position].first.id)
putExtra("galleryID", galleries[position].id)
})
(context.applicationContext as Pupil).histories.add(galleries[position].first.id)
(context.applicationContext as Pupil).histories.add(galleries[position].id)
}
.setOnItemLongClickListener { _, position, _ ->
GalleryDialog(
context,
galleries[position].first.id
galleries[position].id
).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }

View File

@@ -78,16 +78,14 @@ class Cache(context: Context) : ContextWrapper(context) {
@UseExperimental(ImplicitReflectionSerializer::class)
fun setCachedMetadata(galleryID: Int, metadata: Metadata) {
val file = File(getCachedGallery(galleryID), ".metadata")
val file = File(getCachedGallery(galleryID) ?: File(cacheDir, "imageCache/$galleryID"), ".metadata")
if (!file.exists())
return
if (file.parentFile?.exists() != true)
file.parentFile?.mkdirs()
try {
file.writeText(Json.stringify(metadata))
} catch (e: Exception) {
file.createNewFile()
}
file.writeText(Json.stringify(metadata))
}
suspend fun getThumbnail(galleryID: Int): String? {
@@ -135,6 +133,16 @@ class Cache(context: Context) : ContextWrapper(context) {
return galleryBlock
}
fun getReaderOrNull(galleryID: Int): Reader? {
val metadata = getCachedMetadata(galleryID)
val mirrors = preference.getString("mirrors", "")!!.split('>')
return metadata?.readers?.firstOrNull {
mirrors.contains(it.code.name)
}
}
suspend fun getReader(galleryID: Int): Reader? {
val metadata = getCachedMetadata(galleryID)
@@ -173,7 +181,7 @@ class Cache(context: Context) : ContextWrapper(context) {
gallery.listFiles { file ->
file.nameWithoutExtension.toIntOrNull() != null
}?.forEach {
append(it.nameWithoutExtension.toInt(), it)
put(it.nameWithoutExtension.toInt(), it)
}
}
}
@@ -197,14 +205,17 @@ class Cache(context: Context) : ContextWrapper(context) {
}
fun moveToDownload(galleryID: Int) {
val cache = getCachedGallery(galleryID) ?: File(cacheDir, "imageCache/$galleryID")
val cache = getCachedGallery(galleryID)
val download = getDownloadDirectory(this)
if (cache != null) {
val download = getDownloadDirectory(this)
if (!download.isParentOf(cache)) {
cache.copyRecursively(download)
cache.deleteRecursively()
}
if (!download.isParentOf(cache)) {
cache.copyRecursively(download, true)
cache.deleteRecursively()
}
} else
File(getDownloadDirectory(this), galleryID.toString()).mkdirs()
}
}

View File

@@ -49,8 +49,8 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
override fun update(tag: Any?, bytesRead: Long, contentLength: Long, done: Boolean) {
val (galleryID, index) = (tag as? Pair<Int, Int>) ?: return
if (!done && progress[galleryID]!![index] != Float.POSITIVE_INFINITY)
progress[galleryID]!![index] = bytesRead * 100F / contentLength
if (!done && progress[galleryID]?.get(index)?.isFinite() == true)
progress[galleryID]?.set(index, bytesRead * 100F / contentLength)
}
}
@@ -156,14 +156,20 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
.build()
fun stop() {
queue.clear()
loop.cancel()
for (i in 0..worker.size())
worker[worker.keyAt(i)]?.cancel()
client.dispatcher.cancelAll()
progress.clear()
exception.clear()
}
fun cancel(galleryID: Int) {
queue.remove(galleryID)
worker[galleryID]?.cancel()
client.dispatcher.queuedCalls()
@@ -171,6 +177,13 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
.forEach {
it.cancel()
}
if (progress.indexOfKey(galleryID) >= 0) {
progress.remove(galleryID)
exception.remove(galleryID)
nRunners--
}
}
private fun queueDownload(galleryID: Int, reader: Reader, index: Int, callback: Callback) {
@@ -179,7 +192,7 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
//Cache exists :P
cache?.get(index)?.let {
progress[galleryID]!![index] = Float.POSITIVE_INFINITY
progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
return
}
@@ -231,11 +244,15 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
if (Fabric.isInitialized())
Crashlytics.logException(e)
progress[galleryID]!![i] = Float.NaN
exception[galleryID]!![i] = e
progress[galleryID]?.set(i, Float.NaN)
exception[galleryID]?.set(i, e)
if (progress[galleryID]?.all { !it.isFinite() } == true) {
progress.remove(galleryID)
exception.remove(galleryID)
if (progress[galleryID]!!.all { !it.isFinite() })
nRunners--
}
}
override fun onResponse(call: Call, response: Response) {
@@ -245,11 +262,15 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
call.request().url.encodedPath.split('.').last()
Cache(this@DownloadWorker).putImage(galleryID, "$i.$ext", res)
progress[galleryID]!![i] = Float.POSITIVE_INFINITY
progress[galleryID]?.set(i, Float.POSITIVE_INFINITY)
}
if (progress[galleryID]!!.all { !it.isFinite() })
if (progress[galleryID]?.all { !it.isFinite() } == true) {
progress.remove(galleryID)
exception.remove(galleryID)
nRunners--
}
}
}

View File

@@ -64,4 +64,4 @@ fun URL.download(to: File, onDownloadProgress: ((Long, Long) -> Unit)? = null) {
}
}
fun File.isParentOf(file: File) = file.absolutePath.startsWith(this.absolutePath)
fun File.isParentOf(file: File?) = file?.absolutePath?.startsWith(this.absolutePath) ?: false