Download added
This commit is contained in:
@@ -218,7 +218,9 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
with(main_recyclerview) {
|
||||
adapter = GalleryBlockAdapter(galleries)
|
||||
adapter = GalleryBlockAdapter(galleries).apply {
|
||||
|
||||
}
|
||||
addOnScrollListener(
|
||||
object: RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
@@ -235,8 +237,7 @@ class MainActivity : AppCompatActivity() {
|
||||
ItemClickSupport.addTo(this).setOnItemClickListener { _, position, _ ->
|
||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||
val gallery = galleries[position].first
|
||||
intent.putExtra("GALLERY_ID", gallery.id)
|
||||
intent.putExtra("GALLERY_TITLE", gallery.title)
|
||||
intent.putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery))
|
||||
|
||||
//TODO: Maybe sprinke some transitions will be nice :D
|
||||
startActivity(intent)
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Process
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.SparseArray
|
||||
import com.finotes.android.finotescore.Fn
|
||||
import com.finotes.android.finotescore.ObservableApplication
|
||||
import com.finotes.android.finotescore.Severity
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
class Pupil : ObservableApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
super.onCreate()
|
||||
Fn.init(this)
|
||||
|
||||
Fn.enableFrameDetection()
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
||||
Fn.reportException(t, Exception(e), Severity.FATAL)
|
||||
if (!preference.getBoolean("channel_created", false)) {
|
||||
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 {
|
||||
description = getString(R.string.channel_download_description)
|
||||
enableLights(false)
|
||||
enableVibration(false)
|
||||
lockscreenVisibility = Notification.VISIBILITY_SECRET
|
||||
}
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
preference.edit().putBoolean("channel_created", true).apply()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -10,37 +10,33 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import androidx.recyclerview.widget.PagerSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
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.*
|
||||
import kotlinx.io.IOException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.list
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.ReaderItem
|
||||
import xyz.quaver.hitomi.getReader
|
||||
import xyz.quaver.hitomi.getReferer
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||
import xyz.quaver.pupil.util.GalleryDownloader
|
||||
import xyz.quaver.pupil.util.ItemClickSupport
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URL
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
private val images = ArrayList<String>()
|
||||
private var galleryID = 0
|
||||
private var gallerySize: Int = 0
|
||||
private var currentPage: Int = 0
|
||||
private lateinit var reader: Deferred<Reader>
|
||||
private var loadJob: Job? = null
|
||||
private lateinit var galleryBlock: GalleryBlock
|
||||
private var gallerySize = 0
|
||||
private var currentPage = 0
|
||||
|
||||
private var isScroll = true
|
||||
private var isFullscreen = false
|
||||
|
||||
private lateinit var downloader: GalleryDownloader
|
||||
|
||||
private val snapHelper = PagerSnapHelper()
|
||||
|
||||
private var menu: Menu? = null
|
||||
@@ -54,54 +50,20 @@ class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
setContentView(R.layout.activity_reader)
|
||||
|
||||
supportActionBar?.title = intent.getStringExtra("GALLERY_TITLE")
|
||||
galleryBlock = Json(JsonConfiguration.Stable).parse(
|
||||
GalleryBlock.serializer(),
|
||||
intent.getStringExtra("galleryblock")
|
||||
)
|
||||
|
||||
galleryID = intent.getIntExtra("GALLERY_ID", 0)
|
||||
reader = CoroutineScope(Dispatchers.IO).async {
|
||||
val json = Json(JsonConfiguration.Stable)
|
||||
val serializer = ReaderItem.serializer().list
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(this@ReaderActivity)
|
||||
val isHiyobi = preference.getBoolean("use_hiyobi", false)
|
||||
supportActionBar?.title = galleryBlock.title
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||
|
||||
val cache = when {
|
||||
isHiyobi -> File(cacheDir, "imageCache/$galleryID/reader-hiyobi.json")
|
||||
else -> File(cacheDir, "imageCache/$galleryID/reader.json")
|
||||
}
|
||||
|
||||
if (cache.exists()) {
|
||||
val cached = json.parse(serializer, cache.readText())
|
||||
|
||||
if (cached.isNotEmpty())
|
||||
return@async cached
|
||||
}
|
||||
|
||||
val reader = when {
|
||||
isHiyobi -> {
|
||||
xyz.quaver.hiyobi.getReader(galleryID).let {
|
||||
when {
|
||||
it.isEmpty() -> getReader(galleryID)
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
getReader(galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
if (reader.isEmpty())
|
||||
finish()
|
||||
|
||||
if (!cache.parentFile.exists())
|
||||
cache.parentFile.mkdirs()
|
||||
|
||||
cache.writeText(json.stringify(serializer, reader))
|
||||
|
||||
reader
|
||||
}
|
||||
initDownloader()
|
||||
|
||||
initView()
|
||||
loadImages()
|
||||
|
||||
if (!downloader.notify)
|
||||
downloader.start()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -149,6 +111,9 @@ class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
if (!downloader.notify)
|
||||
downloader.cancel()
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
@@ -166,6 +131,85 @@ class ReaderActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initDownloader() {
|
||||
var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id)
|
||||
|
||||
if (d == null) {
|
||||
d = GalleryDownloader(this, galleryBlock)
|
||||
}
|
||||
|
||||
downloader = d.apply {
|
||||
onReaderLoadedHandler = {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
with(reader_progressbar) {
|
||||
max = it.size
|
||||
progress = 0
|
||||
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
gallerySize = it.size
|
||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.size}"
|
||||
}
|
||||
}
|
||||
onProgressHandler = {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
reader_progressbar.progress = it
|
||||
}
|
||||
}
|
||||
onDownloadedHandler = {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
if (images.isEmpty()) {
|
||||
images.addAll(it)
|
||||
reader_recyclerview.adapter?.notifyDataSetChanged()
|
||||
} else {
|
||||
images.add(it.last())
|
||||
reader_recyclerview.adapter?.notifyItemInserted(images.size-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
onErrorHandler = {
|
||||
downloader.notify = false
|
||||
}
|
||||
onCompleteHandler = {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
reader_progressbar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
onNotifyChangedHandler = { notify ->
|
||||
val fab = reader_fab_download
|
||||
|
||||
if (notify) {
|
||||
val icon = AnimatedVectorDrawableCompat.create(this, R.drawable.ic_downloading)
|
||||
icon?.registerAnimationCallback(object: Animatable2Compat.AnimationCallback() {
|
||||
override fun onAnimationEnd(drawable: Drawable?) {
|
||||
if (downloader.notify)
|
||||
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 {
|
||||
fab.setImageResource(R.drawable.ic_download)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (downloader.notify) {
|
||||
downloader.invokeOnReaderLoaded()
|
||||
downloader.invokeOnNotifyChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
with(reader_recyclerview) {
|
||||
adapter = ReaderAdapter(images)
|
||||
@@ -208,6 +252,13 @@ class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
reader_fab.close(true)
|
||||
}
|
||||
|
||||
reader_fab_download.setOnClickListener {
|
||||
downloader.notify = !downloader.notify
|
||||
|
||||
if (!downloader.notify)
|
||||
downloader.clearNotification()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fullscreen(isFullscreen: Boolean) {
|
||||
@@ -237,68 +288,4 @@ class ReaderActivity : AppCompatActivity() {
|
||||
|
||||
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
|
||||
}
|
||||
|
||||
private fun loadImages() {
|
||||
fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
|
||||
|
||||
loadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||
val reader = reader.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
with(reader_progressbar) {
|
||||
max = reader.size
|
||||
progress = 0
|
||||
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
gallerySize = reader.size
|
||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/$gallerySize"
|
||||
}
|
||||
|
||||
reader.chunked(4).forEach { chunked ->
|
||||
chunked.map {
|
||||
async(Dispatchers.IO) {
|
||||
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
||||
|
||||
val fileName: String
|
||||
|
||||
with(url) {
|
||||
fileName = substring(lastIndexOf('/')+1)
|
||||
}
|
||||
|
||||
val cache = File(cacheDir, "/imageCache/$galleryID/$fileName")
|
||||
|
||||
if (!cache.exists())
|
||||
try {
|
||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
||||
setRequestProperty("Referer", getReferer(galleryID))
|
||||
|
||||
if (!cache.parentFile.exists())
|
||||
cache.parentFile.mkdirs()
|
||||
|
||||
inputStream.copyTo(FileOutputStream(cache))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
cache.delete()
|
||||
}
|
||||
|
||||
cache.absolutePath
|
||||
}
|
||||
}.forEach {
|
||||
val cache = it.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
images.add(cache)
|
||||
reader_recyclerview.adapter?.notifyItemInserted(images.size - 1)
|
||||
reader_progressbar.progress++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
reader_progressbar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.SparseArray
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -14,9 +15,17 @@ 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 kotlinx.serialization.list
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.ReaderItem
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
@@ -35,9 +44,12 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
||||
}
|
||||
|
||||
var noMore = false
|
||||
private val refreshTasks = SparseArray<TimerTask>()
|
||||
|
||||
class ViewHolder(val view: CardView) : RecyclerView.ViewHolder(view)
|
||||
class ProgressViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
|
||||
private var onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
|
||||
|
||||
class ViewHolder(val view: CardView, var galleryID: Int? = null) : RecyclerView.ViewHolder(view)
|
||||
class ProgressViewHolder(val view: LinearLayout) : RecyclerView.ViewHolder(view)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
when(viewType) {
|
||||
@@ -71,16 +83,62 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
||||
}.toMap()
|
||||
val (gallery, thumbnail) = galleries[position]
|
||||
|
||||
holder.galleryID = gallery.id
|
||||
|
||||
val artists = gallery.artists
|
||||
val series = gallery.series
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val cache = thumbnail.await()
|
||||
|
||||
if (!File(cache).exists())
|
||||
return@launch
|
||||
|
||||
val bitmap = BitmapFactory.decodeFile(thumbnail.await())
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
galleryblock_thumbnail.setImageBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
//Check cache
|
||||
val readerCache = File(context.cacheDir, "imageCache/${gallery.id}/reader.json")
|
||||
val imageCache = File(context.cacheDir, "imageCache/${gallery.id}/images")
|
||||
|
||||
if (readerCache.exists()) {
|
||||
val reader = Json(JsonConfiguration.Stable)
|
||||
.parse(ReaderItem.serializer().list, readerCache.readText())
|
||||
|
||||
with(galleryblock_progressbar) {
|
||||
max = reader.size
|
||||
progress = imageCache.list()?.size ?: 0
|
||||
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
galleryblock_progressbar.visibility = View.GONE
|
||||
}
|
||||
|
||||
if (refreshTasks.get(gallery.id) == null) {
|
||||
val refresh = Timer(false).schedule(0, 1000) {
|
||||
this@with.post {
|
||||
val size = imageCache.list()?.size ?: return@post
|
||||
|
||||
with(galleryblock_progressbar) {
|
||||
progress = size
|
||||
if (visibility == View.GONE) {
|
||||
val reader = Json(JsonConfiguration.Stable)
|
||||
.parse(ReaderItem.serializer().list, readerCache.readText())
|
||||
max = reader.size
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshTasks.put(gallery.id, refresh)
|
||||
}
|
||||
|
||||
galleryblock_title.text = gallery.title
|
||||
with(galleryblock_artist) {
|
||||
text = artists.joinToString(", ") { it.wordCapitalize() }
|
||||
@@ -112,8 +170,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
||||
galleryblock_tag_group.removeAllViews()
|
||||
gallery.relatedTags.forEach {
|
||||
val tag = Tag.parse(it)
|
||||
val chip = LayoutInflater
|
||||
.from(context)
|
||||
|
||||
val chip = LayoutInflater.from(context)
|
||||
.inflate(R.layout.tag_chip, holder.view, false) as Chip
|
||||
|
||||
val icon = when(tag.area) {
|
||||
@@ -131,15 +189,37 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
||||
}
|
||||
|
||||
chip.chipIcon = icon
|
||||
chip.text = Tag.parse(it).tag.wordCapitalize()
|
||||
chip.text = tag.tag.wordCapitalize()
|
||||
chip.setOnClickListener {
|
||||
for (callback in onChipClickedHandler)
|
||||
callback.invoke(tag)
|
||||
}
|
||||
|
||||
galleryblock_tag_group.addView(chip)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (holder is ProgressViewHolder) {
|
||||
holder.view.visibility = when(noMore) {
|
||||
true -> View.GONE
|
||||
false -> View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = if (galleries.isEmpty()) 0 else galleries.size+(if (noMore) 0 else 1)
|
||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
|
||||
if (holder is ViewHolder) {
|
||||
val galleryID = holder.galleryID ?: return
|
||||
val task = refreshTasks.get(galleryID) ?: return
|
||||
|
||||
refreshTasks.remove(galleryID)
|
||||
task.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = if (galleries.isEmpty()) 0 else galleries.size+1
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when {
|
||||
|
||||
232
app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
Normal file
232
app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
Normal file
@@ -0,0 +1,232 @@
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.util.SparseArray
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.TaskStackBuilder
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.io.IOException
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.list
|
||||
import xyz.quaver.hitomi.*
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.ReaderActivity
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URL
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
class GalleryDownloader(
|
||||
base: Context,
|
||||
private val galleryBlock: GalleryBlock
|
||||
) : ContextWrapper(base) {
|
||||
|
||||
var notify: Boolean = false
|
||||
set(value) {
|
||||
if (value) {
|
||||
field = true
|
||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
||||
|
||||
if (downloadJob?.isActive != true)
|
||||
field = false
|
||||
} else {
|
||||
field = false
|
||||
}
|
||||
|
||||
onNotifyChangedHandler?.invoke(value)
|
||||
}
|
||||
|
||||
private val reader: Deferred<Reader>
|
||||
private var downloadJob: Job? = null
|
||||
|
||||
private lateinit var notificationBuilder: NotificationCompat.Builder
|
||||
private lateinit var notificationManager: NotificationManagerCompat
|
||||
|
||||
var onReaderLoadedHandler: ((Reader) -> Unit)? = null
|
||||
var onProgressHandler: ((Int) -> Unit)? = null
|
||||
var onDownloadedHandler: ((List<String>) -> Unit)? = null
|
||||
var onErrorHandler: (() -> Unit)? = null
|
||||
var onCompleteHandler: (() -> Unit)? = null
|
||||
var onNotifyChangedHandler: ((Boolean) -> Unit)? = null
|
||||
|
||||
companion object : SparseArray<GalleryDownloader>()
|
||||
|
||||
init {
|
||||
put(galleryBlock.id, this)
|
||||
|
||||
initNotification()
|
||||
|
||||
reader = CoroutineScope(Dispatchers.IO).async {
|
||||
val json = Json(JsonConfiguration.Stable)
|
||||
val serializer = ReaderItem.serializer().list
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryDownloader)
|
||||
val useHiyobi = preference.getBoolean("use_hiyobi", false)
|
||||
|
||||
//Check cache
|
||||
val cache = File(cacheDir, "imageCache/${galleryBlock.id}/reader.json")
|
||||
|
||||
if (cache.exists()) {
|
||||
val cached = json.parse(serializer, cache.readText())
|
||||
|
||||
if (cached.isNotEmpty())
|
||||
return@async cached
|
||||
}
|
||||
|
||||
//Cache doesn't exist. Load from internet
|
||||
val reader = when {
|
||||
useHiyobi -> {
|
||||
xyz.quaver.hiyobi.getReader(galleryBlock.id).let {
|
||||
when {
|
||||
it.isEmpty() -> getReader(galleryBlock.id)
|
||||
else -> it
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
getReader(galleryBlock.id)
|
||||
}
|
||||
}
|
||||
|
||||
//Could not retrieve reader
|
||||
if (reader.isEmpty())
|
||||
throw IOException("Can't retrieve Reader")
|
||||
|
||||
//Save cache
|
||||
if (!cache.parentFile.exists())
|
||||
cache.parentFile.mkdirs()
|
||||
|
||||
cache.writeText(json.stringify(serializer, reader))
|
||||
|
||||
reader
|
||||
}
|
||||
}
|
||||
|
||||
private fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
|
||||
|
||||
fun start() {
|
||||
downloadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||
val reader = reader.await()
|
||||
|
||||
val list = ArrayList<String>()
|
||||
|
||||
onReaderLoadedHandler?.invoke(reader)
|
||||
|
||||
notificationBuilder
|
||||
.setProgress(reader.size, 0, false)
|
||||
.setContentText("0/${reader.size}")
|
||||
|
||||
reader.chunked(4).forEachIndexed { chunkIndex, chunked ->
|
||||
chunked.mapIndexed { i, it ->
|
||||
val index = chunkIndex*4+i
|
||||
|
||||
onProgressHandler?.invoke(index)
|
||||
|
||||
notificationBuilder
|
||||
.setProgress(reader.size, index, false)
|
||||
.setContentText("$index/${reader.size}")
|
||||
|
||||
if (notify)
|
||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
||||
|
||||
async(Dispatchers.IO) {
|
||||
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
||||
|
||||
val name = "$index".padStart(4, '0')
|
||||
val ext = url.split('.').last()
|
||||
|
||||
val cache = File(cacheDir, "/imageCache/${galleryBlock.id}/images/$name.$ext")
|
||||
|
||||
if (!cache.exists())
|
||||
try {
|
||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
||||
setRequestProperty("Referer", getReferer(galleryBlock.id))
|
||||
|
||||
if (!cache.parentFile.exists())
|
||||
cache.parentFile.mkdirs()
|
||||
|
||||
inputStream.copyTo(FileOutputStream(cache))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
cache.delete()
|
||||
|
||||
onErrorHandler?.invoke()
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle(galleryBlock.title)
|
||||
.setContentText(getString(R.string.reader_notification_error))
|
||||
.setProgress(0, 0, false)
|
||||
|
||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
||||
}
|
||||
|
||||
cache.absolutePath
|
||||
}
|
||||
}.forEach {
|
||||
list.add(it.await())
|
||||
onDownloadedHandler?.invoke(list)
|
||||
}
|
||||
}
|
||||
|
||||
onCompleteHandler?.invoke()
|
||||
|
||||
notificationBuilder
|
||||
.setContentTitle(galleryBlock.title)
|
||||
.setContentText(getString(R.string.reader_notification_complete))
|
||||
.setProgress(0, 0, false)
|
||||
|
||||
if (notify)
|
||||
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
||||
|
||||
notify = false
|
||||
|
||||
remove(galleryBlock.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
downloadJob?.cancel()
|
||||
|
||||
remove(galleryBlock.id)
|
||||
}
|
||||
|
||||
fun invokeOnReaderLoaded() {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
onReaderLoadedHandler?.invoke(reader.await())
|
||||
}
|
||||
}
|
||||
|
||||
fun clearNotification() {
|
||||
notificationManager.cancel(galleryBlock.id)
|
||||
}
|
||||
|
||||
fun invokeOnNotifyChanged() {
|
||||
onNotifyChangedHandler?.invoke(notify)
|
||||
}
|
||||
|
||||
private fun initNotification() {
|
||||
val intent = Intent(this, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), galleryBlock))
|
||||
}
|
||||
val pendingIntent = TaskStackBuilder.create(this).run {
|
||||
addNextIntentWithParentStack(intent)
|
||||
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
|
||||
setContentTitle(galleryBlock.title)
|
||||
setContentText(getString(R.string.reader_notification_text))
|
||||
setSmallIcon(R.drawable.ic_download)
|
||||
setContentIntent(pendingIntent)
|
||||
setProgress(0, 0, true)
|
||||
priority = NotificationCompat.PRIORITY_LOW
|
||||
}
|
||||
notificationManager = NotificationManagerCompat.from(this)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
import kotlinx.io.IOException
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
@@ -8,7 +7,6 @@ import kotlinx.serialization.parseList
|
||||
import kotlinx.serialization.stringify
|
||||
import java.io.File
|
||||
|
||||
|
||||
class Histories(private val file: File) : ArrayList<Int>() {
|
||||
|
||||
init {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
Reference in New Issue
Block a user