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

@@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="120" />
<AndroidXmlCodeStyleSettings> <AndroidXmlCodeStyleSettings>
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" /> <option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings> </AndroidXmlCodeStyleSettings>

View File

@@ -25,6 +25,7 @@ import androidx.core.content.ContextCompat
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule import androidx.test.rule.ActivityTestRule
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
@@ -37,6 +38,7 @@ import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.getReader import xyz.quaver.hiyobi.getReader
import xyz.quaver.hiyobi.user_agent import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.ui.LockActivity import xyz.quaver.pupil.ui.LockActivity
import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.download.DownloadWorker import xyz.quaver.pupil.util.download.DownloadWorker
import xyz.quaver.pupil.util.getDownloadDirectory import xyz.quaver.pupil.util.getDownloadDirectory
import xyz.quaver.pupil.util.updateOldReaderGalleries import xyz.quaver.pupil.util.updateOldReaderGalleries
@@ -139,4 +141,17 @@ class ExampleInstrumentedTest {
Log.i("PUPILD", "DONE!!") Log.i("PUPILD", "DONE!!")
} }
@Test
fun test_getReaderOrNull() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val galleryID = 1561552
runBlocking {
Log.i("PUPILD", Cache(context).getReader(galleryID)?.title ?: "null")
}
Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.title ?: "null")
}
} }

View File

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

View File

@@ -18,30 +18,47 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.item_reader.view.* 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.hitomi.Reader
import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.GalleryDownloader import xyz.quaver.pupil.util.download.Cache
import xyz.quaver.pupil.util.getCachedGallery import xyz.quaver.pupil.util.download.DownloadWorker
import java.io.File import java.util.*
import kotlin.concurrent.schedule
import kotlin.math.roundToInt
class ReaderAdapter(private val glide: RequestManager, class ReaderAdapter(private val context: Context,
private val galleryID: Int, private val galleryID: Int) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
var isFullScreen = false var isFullScreen = false
var reader: Reader? = null
private val glide = Glide.with(context)
var onItemClickListener : ((Int) -> (Unit))? = null 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) class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@@ -60,38 +77,62 @@ class ReaderAdapter(private val glide: RequestManager,
else else
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT 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 { _, _, _ -> holder.view.image.setOnPhotoTapListener { _, _, _ ->
onItemClickListener?.invoke(position) onItemClickListener?.invoke(position)
} }
glide (holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
.load(File(getCachedGallery(holder.view.context, galleryID), images[position])) .dimensionRatio = "${reader!!.galleryInfo[position].width}:${reader!!.galleryInfo[position].height}"
.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)
if (galleryInfo != null) { holder.view.reader_item_progressbar.progress = DownloadWorker.getInstance(context).progress[galleryID]?.get(position)?.roundToInt() ?: 0
(holder.view.image.layoutParams as ConstraintLayout.LayoutParams) holder.view.reader_index.text = (position+1).toString()
.dimensionRatio = "${galleryInfo.width}:${galleryInfo.height}"
} 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.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView import com.arlib.floatingsearchview.util.view.SearchInputView
import com.bumptech.glide.Glide
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main_content.* 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.types.Tags
import xyz.quaver.pupil.ui.dialog.GalleryDialog import xyz.quaver.pupil.ui.dialog.GalleryDialog
import xyz.quaver.pupil.util.* 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.File
import java.io.FileOutputStream
import java.net.URL
import java.util.* import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.ceil import kotlin.math.ceil
@@ -89,7 +87,7 @@ class MainActivity : AppCompatActivity() {
POPULAR POPULAR
} }
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>() private val galleries = ArrayList<GalleryBlock>()
private var query = "" private var query = ""
set(value) { set(value) {
@@ -386,7 +384,7 @@ class MainActivity : AppCompatActivity() {
private fun setupRecyclerView() { private fun setupRecyclerView() {
with(main_recyclerview) { with(main_recyclerview) {
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply { adapter = GalleryBlockAdapter(this@MainActivity, galleries).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
runOnUiThread { runOnUiThread {
query = it.toQuery() query = it.toQuery()
@@ -399,20 +397,18 @@ class MainActivity : AppCompatActivity() {
} }
} }
onDownloadClickedHandler = { position -> onDownloadClickedHandler = { position ->
val galleryID = galleries[position].first.id val galleryID = galleries[position].id
if (!completeFlag.get(galleryID, false)) { if (!completeFlag.get(galleryID, false)) {
val downloader = GalleryDownloader.get(galleryID) val worker = DownloadWorker.getInstance(context)
if (downloader == null) if (worker.progress.indexOfKey(galleryID) >= 0) //download in progress
GalleryDownloader( worker.cancel(galleryID)
context,
galleryID,
true
).start()
else { else {
downloader.cancel() Cache(context).moveToDownload(galleryID)
downloader.clearNotification()
if (!worker.queue.contains(galleryID))
worker.queue.add(galleryID)
} }
} }
@@ -420,39 +416,28 @@ class MainActivity : AppCompatActivity() {
} }
onDeleteClickedHandler = { position -> onDeleteClickedHandler = { position ->
val galleryID = galleries[position].first.id val galleryID = galleries[position].id
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
with(GalleryDownloader[galleryID]) { DownloadWorker.getInstance(context).cancel(galleryID)
this?.cancelAndJoin()
this?.clearNotification() 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) downloads.remove(galleryID)
if (this@MainActivity.mode == Mode.DOWNLOAD) {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
histories.remove(galleryID) histories.remove(galleryID)
if (this@MainActivity.mode == Mode.HISTORY) { if (this@MainActivity.mode != Mode.SEARCH)
runOnUiThread { runOnUiThread {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query, sortMode) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
}
completeFlag.put(galleryID, false) completeFlag.put(galleryID, false)
} }
@@ -466,7 +451,7 @@ class MainActivity : AppCompatActivity() {
return@setOnItemClickListener return@setOnItemClickListener
val intent = Intent(this@MainActivity, ReaderActivity::class.java) val intent = Intent(this@MainActivity, ReaderActivity::class.java)
val gallery = galleries[position].first val gallery = galleries[position]
intent.putExtra("galleryID", gallery.id) intent.putExtra("galleryID", gallery.id)
//TODO: Maybe sprinkling some transitions will be nice :D //TODO: Maybe sprinkling some transitions will be nice :D
@@ -478,7 +463,7 @@ class MainActivity : AppCompatActivity() {
if (v !is CardView) if (v !is CardView)
return@setOnItemLongClickListener true return@setOnItemLongClickListener true
val galleryID = galleries[position].first.id val galleryID = galleries[position].id
GalleryDialog( GalleryDialog(
this@MainActivity, this@MainActivity,
@@ -1030,41 +1015,21 @@ class MainActivity : AppCompatActivity() {
val json = Json(JsonConfiguration.Stable) val json = Json(JsonConfiguration.Stable)
val serializer = GalleryBlock.serializer() val serializer = GalleryBlock.serializer()
val galleryBlock = File(getCachedGallery(this@MainActivity, galleryID), "galleryBlock.json").let { cache ->
File(getCachedGallery(this@MainActivity, galleryID), "galleryBlock.json").let { cache -> when {
when { cache.exists() -> json.parse(serializer, cache.readText())
cache.exists() -> json.parse(serializer, cache.readText()) else -> {
else -> { getGalleryBlock(galleryID).apply {
getGalleryBlock(galleryID).apply { this ?: return@apply
this ?: return@apply
if (cache.parentFile?.exists() == false) if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs() cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, this)) cache.writeText(json.stringify(serializer, this))
}
} }
} }
} ?: return@async null }
} ?: 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)
} catch (e: Exception) { } catch (e: Exception) {
null null
} }

View File

@@ -21,39 +21,39 @@ package xyz.quaver.pupil.ui
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.*
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import io.fabric.sdk.android.Fabric import io.fabric.sdk.android.Fabric
import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.android.synthetic.main.activity_reader.view.* import kotlinx.android.synthetic.main.activity_reader.view.*
import kotlinx.android.synthetic.main.dialog_numberpicker.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 kotlinx.serialization.ImplicitReflectionSerializer
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories 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() { class ReaderActivity : AppCompatActivity() {
private var galleryID = 0 private var galleryID = 0
private val images = ArrayList<String>()
private var gallerySize = 0
private var currentPage = 0 private var currentPage = 0
private var isScroll = true private var isScroll = true
@@ -69,7 +69,7 @@ class ReaderActivity : AppCompatActivity() {
} }
} }
private lateinit var downloader: GalleryDownloader private val timer = Timer()
private val snapHelper = PagerSnapHelper() private val snapHelper = PagerSnapHelper()
@@ -101,12 +101,8 @@ class ReaderActivity : AppCompatActivity() {
return return
} }
initDownloader()
initView() initView()
initDownloader()
if (!downloader.download)
downloader.start()
} }
override fun onNewIntent(intent: Intent) { 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) val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
with(view.dialog_number_picker) { with(view.dialog_number_picker) {
minValue=1 minValue=1
maxValue=gallerySize maxValue=reader_recyclerview.adapter?.itemCount ?: 0
value=currentPage value=currentPage
} }
val dialog = AlertDialog.Builder(this).apply { val dialog = AlertDialog.Builder(this).apply {
@@ -201,8 +197,10 @@ class ReaderActivity : AppCompatActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (::downloader.isInitialized && !downloader.download) timer.cancel()
downloader.cancel()
if (!getDownloadDirectory(this).isParentOf(Cache(this).getCachedGallery(galleryID)))
DownloadWorker.getInstance(this).cancel(galleryID)
} }
override fun onBackPressed() { override fun onBackPressed() {
@@ -238,101 +236,54 @@ class ReaderActivity : AppCompatActivity() {
} }
private fun initDownloader() { private fun initDownloader() {
var d: GalleryDownloader? = GalleryDownloader.get(galleryID) val worker = DownloadWorker.getInstance(this).apply {
queue.add(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)
}
}
}
}
} }
if (downloader.download) { timer.schedule(0, 1000) {
downloader.invokeOnReaderLoaded() if (worker.progress.indexOfKey(galleryID) < 0) //loading
downloader.invokeOnNotifyChanged() 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() { private fun initView() {
with(reader_recyclerview) { with(reader_recyclerview) {
adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID, images).apply { adapter = ReaderAdapter(this@ReaderActivity, galleryID).apply {
onItemClickListener = { onItemClickListener = {
if (isScroll) { if (isScroll) {
isScroll = false isScroll = false
@@ -360,19 +311,19 @@ class ReaderActivity : AppCompatActivity() {
if (layoutManager.findFirstVisibleItemPosition() == -1) if (layoutManager.findFirstVisibleItemPosition() == -1)
return return
currentPage = layoutManager.findFirstVisibleItemPosition()+1 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 this@ReaderActivity.reader_progressbar.progress = currentPage
} }
}) })
} }
with(reader_fab_download) { with(reader_fab_download) {
setImageResource(R.drawable.ic_download) animateDownloadFAB(getDownloadDirectory(context).isParentOf(Cache(context).getCachedGallery(galleryID))) //If download in progress, animate button
setOnClickListener {
downloader.download = !downloader.download
if (!downloader.download) setOnClickListener {
downloader.clearNotification() Cache(context).moveToDownload(galleryID)
animateDownloadFAB(true)
} }
} }
@@ -414,4 +365,32 @@ class ReaderActivity : AppCompatActivity() {
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0) (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.dialog_gallery.*
import kotlinx.android.synthetic.main.gallery_details.view.* import kotlinx.android.synthetic.main.gallery_details.view.*
import kotlinx.android.synthetic.main.item_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.Gallery
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.getGallery import xyz.quaver.hitomi.getGallery
@@ -221,9 +224,9 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
private fun addRelated(gallery: Gallery) { private fun addRelated(gallery: Gallery) {
val inflater = LayoutInflater.from(context) 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 -> onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler -> this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag) handler.invoke(tag)
@@ -238,7 +241,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
}.let { }.let {
val galleryBlock = it.await() ?: return@let val galleryBlock = it.await() ?: return@let
galleries.add(Pair(galleryBlock, GlobalScope.async { galleryBlock.thumbnails.first() })) galleries.add(galleryBlock)
adapter.notifyItemInserted(i) adapter.notifyItemInserted(i)
} }
} }
@@ -254,14 +257,14 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
ItemClickSupport.addTo(this) ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, _ -> .setOnItemClickListener { _, position, _ ->
context.startActivity(Intent(context, ReaderActivity::class.java).apply { 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, _ -> .setOnItemLongClickListener { _, position, _ ->
GalleryDialog( GalleryDialog(
context, context,
galleries[position].first.id galleries[position].id
).apply { ).apply {
onChipClickedHandler.add { tag -> onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) } this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }

View File

@@ -78,16 +78,14 @@ class Cache(context: Context) : ContextWrapper(context) {
@UseExperimental(ImplicitReflectionSerializer::class) @UseExperimental(ImplicitReflectionSerializer::class)
fun setCachedMetadata(galleryID: Int, metadata: Metadata) { 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()) if (file.parentFile?.exists() != true)
return file.parentFile?.mkdirs()
try { file.createNewFile()
file.writeText(Json.stringify(metadata))
} catch (e: Exception) {
} file.writeText(Json.stringify(metadata))
} }
suspend fun getThumbnail(galleryID: Int): String? { suspend fun getThumbnail(galleryID: Int): String? {
@@ -135,6 +133,16 @@ class Cache(context: Context) : ContextWrapper(context) {
return galleryBlock 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? { suspend fun getReader(galleryID: Int): Reader? {
val metadata = getCachedMetadata(galleryID) val metadata = getCachedMetadata(galleryID)
@@ -173,7 +181,7 @@ class Cache(context: Context) : ContextWrapper(context) {
gallery.listFiles { file -> gallery.listFiles { file ->
file.nameWithoutExtension.toIntOrNull() != null file.nameWithoutExtension.toIntOrNull() != null
}?.forEach { }?.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) { 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)) { if (!download.isParentOf(cache)) {
cache.copyRecursively(download) cache.copyRecursively(download, true)
cache.deleteRecursively() 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) { override fun update(tag: Any?, bytesRead: Long, contentLength: Long, done: Boolean) {
val (galleryID, index) = (tag as? Pair<Int, Int>) ?: return val (galleryID, index) = (tag as? Pair<Int, Int>) ?: return
if (!done && progress[galleryID]!![index] != Float.POSITIVE_INFINITY) if (!done && progress[galleryID]?.get(index)?.isFinite() == true)
progress[galleryID]!![index] = bytesRead * 100F / contentLength progress[galleryID]?.set(index, bytesRead * 100F / contentLength)
} }
} }
@@ -156,14 +156,20 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
.build() .build()
fun stop() { fun stop() {
queue.clear()
loop.cancel() loop.cancel()
for (i in 0..worker.size()) for (i in 0..worker.size())
worker[worker.keyAt(i)]?.cancel() worker[worker.keyAt(i)]?.cancel()
client.dispatcher.cancelAll() client.dispatcher.cancelAll()
progress.clear()
exception.clear()
} }
fun cancel(galleryID: Int) { fun cancel(galleryID: Int) {
queue.remove(galleryID)
worker[galleryID]?.cancel() worker[galleryID]?.cancel()
client.dispatcher.queuedCalls() client.dispatcher.queuedCalls()
@@ -171,6 +177,13 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
.forEach { .forEach {
it.cancel() 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) { 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 exists :P
cache?.get(index)?.let { cache?.get(index)?.let {
progress[galleryID]!![index] = Float.POSITIVE_INFINITY progress[galleryID]?.set(index, Float.POSITIVE_INFINITY)
return return
} }
@@ -231,11 +244,15 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
if (Fabric.isInitialized()) if (Fabric.isInitialized())
Crashlytics.logException(e) Crashlytics.logException(e)
progress[galleryID]!![i] = Float.NaN progress[galleryID]?.set(i, Float.NaN)
exception[galleryID]!![i] = e 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-- nRunners--
}
} }
override fun onResponse(call: Call, response: Response) { 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() call.request().url.encodedPath.split('.').last()
Cache(this@DownloadWorker).putImage(galleryID, "$i.$ext", res) 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-- 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2020 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp">
<shape android:shape="rectangle" >
<stroke
android:width="1dp"
android:color="#555555" />
<solid android:color="@color/transparent" />
</shape>
</item>
</layer-list>

View File

@@ -23,15 +23,49 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<com.github.chrisbanes.photoview.PhotoView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/image" android:id="@+id/container"
android:contentDescription="@string/reader_imageview_description" android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:paddingBottom="8dp"/> android:background="@drawable/reader_item_boundary">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<ProgressBar
android:id="@+id/reader_item_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:progressBarStyleHorizontal"
android:indeterminate="false"
android:progress="0"
android:max="100"
android:visibility="visible"/>
<TextView
android:id="@+id/reader_index"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Caption"/>
</LinearLayout>
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/image"
android:contentDescription="@string/reader_imageview_description"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -25,9 +25,8 @@
android:icon="@drawable/avd_star" android:icon="@drawable/avd_star"
app:showAsAction="always"/> app:showAsAction="always"/>
<item android:id="@+id/reader_menu_use_hiyobi" <item android:id="@+id/reader_type"
android:title="" android:title=""
android:icon="@drawable/ic_hiyobi"
app:showAsAction="ifRoom" app:showAsAction="ifRoom"
android:visible="false"/> android:visible="false"/>

View File

@@ -64,7 +64,7 @@
<string name="settings_mirror_summary">ミラーサーバからイメージをロード</string> <string name="settings_mirror_summary">ミラーサーバからイメージをロード</string>
<string name="main_drawer_favorite">お気に入り</string> <string name="main_drawer_favorite">お気に入り</string>
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string> <string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
<string name="main_open_gallery_by_id_error">エラーが発生しました</string> <string name="reader_failed_to_find_gallery">エラーが発生しました</string>
<string name="settings_storage">ストレージ</string> <string name="settings_storage">ストレージ</string>
<string name="main_drawer_grouop_contact_discord">ディスコード</string> <string name="main_drawer_grouop_contact_discord">ディスコード</string>
<string name="settings_app_lock">アプリロック</string> <string name="settings_app_lock">アプリロック</string>

View File

@@ -62,7 +62,7 @@
<string name="settings_clear_downloads_alert_message">다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까?</string> <string name="settings_clear_downloads_alert_message">다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까?</string>
<string name="main_drawer_favorite">즐겨찾기</string> <string name="main_drawer_favorite">즐겨찾기</string>
<string name="main_open_gallery_by_id">갤러리 번호로 열기</string> <string name="main_open_gallery_by_id">갤러리 번호로 열기</string>
<string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string> <string name="reader_failed_to_find_gallery">갤러리를 찾지 못했습니다</string>
<string name="settings_storage">저장 공간</string> <string name="settings_storage">저장 공간</string>
<string name="main_drawer_grouop_contact_discord">디스코드</string> <string name="main_drawer_grouop_contact_discord">디스코드</string>
<string name="settings_app_lock">앱 잠금</string> <string name="settings_app_lock">앱 잠금</string>

View File

@@ -60,7 +60,7 @@
<string name="main_jump_title">Jump to page</string> <string name="main_jump_title">Jump to page</string>
<string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string> <string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string>
<string name="main_open_gallery_by_id">Open Gallery by ID</string> <string name="main_open_gallery_by_id">Open Gallery by ID</string>
<string name="main_open_gallery_by_id_error">Failed to open gallery</string> <string name="reader_failed_to_find_gallery">Failed to open gallery</string>
<string name="main_move">Move to page %1$d</string> <string name="main_move">Move to page %1$d</string>

View File

@@ -1,18 +1,17 @@
# Project-wide Gradle settings. ## For more details on how to configure your build environment visit
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m # Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
# Kotlin code style for this project: "official" or "obsolete": #Thu Jan 30 12:29:48 KST 2020
kotlin.code.style=official kotlin.code.style=official
android.enableJetifier=true
org.gradle.jvmargs=-Xmx1024M -Dkotlin.daemon.jvm.options\="-Xmx1024M"
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true