Rebase source onto dev
This commit is contained in:
@@ -123,7 +123,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation "ru.noties.markwon:core:3.1.0"
|
implementation "ru.noties.markwon:core:3.1.0"
|
||||||
|
|
||||||
implementation "xyz.quaver:libpupil:1.8.16"
|
implementation "xyz.quaver:libpupil:1.9.0"
|
||||||
implementation "xyz.quaver:documentfilex:0.4-alpha02"
|
implementation "xyz.quaver:documentfilex:0.4-alpha02"
|
||||||
implementation "xyz.quaver:floatingsearchview:1.0.7"
|
implementation "xyz.quaver:floatingsearchview:1.0.7"
|
||||||
|
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
||||||
|
|
||||||
lateinit var histories: SavedSet<Int>
|
lateinit var histories: SavedSet<String>
|
||||||
private set
|
private set
|
||||||
lateinit var favorites: SavedSet<Int>
|
lateinit var favorites: SavedSet<String>
|
||||||
private set
|
private set
|
||||||
lateinit var favoriteTags: SavedSet<Tag>
|
lateinit var favoriteTags: SavedSet<Tag>
|
||||||
private set
|
private set
|
||||||
@@ -108,8 +108,6 @@ class Pupil : Application() {
|
|||||||
|
|
||||||
if (!FileX(this, it).canWrite())
|
if (!FileX(this, it).canWrite())
|
||||||
throw Exception()
|
throw Exception()
|
||||||
|
|
||||||
DownloadManager.getInstance(this).migrate()
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Preferences.remove("download_folder")
|
Preferences.remove("download_folder")
|
||||||
@@ -120,8 +118,8 @@ class Pupil : Application() {
|
|||||||
Preferences["reset_secure"] = true
|
Preferences["reset_secure"] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
|
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), "")
|
||||||
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
|
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), "")
|
||||||
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
|
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
|
||||||
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
|
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
|
||||||
|
|
||||||
|
|||||||
@@ -1,323 +0,0 @@
|
|||||||
/*
|
|
||||||
* Pupil, Hitomi.la viewer for Android
|
|
||||||
* Copyright (C) 2019 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
|
||||||
|
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
|
||||||
import com.daimajia.swipe.SwipeLayout
|
|
||||||
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
|
||||||
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
|
||||||
import com.github.piasy.biv.loader.ImageLoader
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import xyz.quaver.hitomi.getGallery
|
|
||||||
import xyz.quaver.hitomi.getReader
|
|
||||||
import xyz.quaver.io.util.getChild
|
|
||||||
import xyz.quaver.pupil.R
|
|
||||||
import xyz.quaver.pupil.databinding.GalleryblockItemBinding
|
|
||||||
import xyz.quaver.pupil.favoriteTags
|
|
||||||
import xyz.quaver.pupil.favorites
|
|
||||||
import xyz.quaver.pupil.types.Tag
|
|
||||||
import xyz.quaver.pupil.ui.view.ProgressCard
|
|
||||||
import xyz.quaver.pupil.util.Preferences
|
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class GalleryBlockAdapter(private val galleries: List<Int>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
|
|
||||||
|
|
||||||
var updateAll = true
|
|
||||||
var thin: Boolean = Preferences["thin"]
|
|
||||||
|
|
||||||
inner class GalleryViewHolder(val binding: GalleryblockItemBinding) : RecyclerView.ViewHolder(binding.root) {
|
|
||||||
private var galleryID: Int = 0
|
|
||||||
|
|
||||||
init {
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
while (updateAll) {
|
|
||||||
updateProgress(itemView.context)
|
|
||||||
delay(1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateProgress(context: Context) = CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
with(binding.galleryblockCard) {
|
|
||||||
val imageList = Cache.getInstance(context, galleryID).metadata.imageList
|
|
||||||
|
|
||||||
if (imageList == null) {
|
|
||||||
max = 0
|
|
||||||
return@with
|
|
||||||
}
|
|
||||||
|
|
||||||
progress = imageList.count { it != null }
|
|
||||||
max = imageList.size
|
|
||||||
|
|
||||||
this@GalleryViewHolder.binding.galleryblockId.setOnClickListener {
|
|
||||||
(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
|
|
||||||
ClipData.newPlainText("gallery_id", galleryID.toString())
|
|
||||||
)
|
|
||||||
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
type = if (!imageList.contains(null)) {
|
|
||||||
val downloadManager = DownloadManager.getInstance(context)
|
|
||||||
|
|
||||||
if (downloadManager.getDownloadFolder(galleryID) == null)
|
|
||||||
ProgressCard.Type.CACHE
|
|
||||||
else
|
|
||||||
ProgressCard.Type.DOWNLOAD
|
|
||||||
} else
|
|
||||||
ProgressCard.Type.LOADING
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bind(galleryID: Int) {
|
|
||||||
this.galleryID = galleryID
|
|
||||||
updateProgress(itemView.context)
|
|
||||||
|
|
||||||
val cache = Cache.getInstance(itemView.context, galleryID)
|
|
||||||
|
|
||||||
val galleryBlock = runBlocking {
|
|
||||||
cache.getGalleryBlock()
|
|
||||||
} ?: return
|
|
||||||
|
|
||||||
val resources = itemView.context.resources
|
|
||||||
val languages = resources.getStringArray(R.array.languages).map {
|
|
||||||
it.split("|").let { split ->
|
|
||||||
Pair(split[0], split[1])
|
|
||||||
}
|
|
||||||
}.toMap()
|
|
||||||
|
|
||||||
val artists = galleryBlock.artists
|
|
||||||
val series = galleryBlock.series
|
|
||||||
|
|
||||||
binding.galleryblockThumbnail.apply {
|
|
||||||
setOnClickListener {
|
|
||||||
itemView.performClick()
|
|
||||||
}
|
|
||||||
setOnLongClickListener {
|
|
||||||
itemView.performLongClick()
|
|
||||||
}
|
|
||||||
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
|
||||||
setImageLoaderCallback(object: ImageLoader.Callback {
|
|
||||||
override fun onFail(error: Exception?) {
|
|
||||||
Cache.getInstance(context, galleryID).let { cache ->
|
|
||||||
cache.cacheFolder.getChild(".thumbnail").let { if (it.exists()) it.delete() }
|
|
||||||
cache.downloadFolder?.getChild(".thumbnail")?.let { if (it.exists()) it.delete() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCacheHit(imageType: Int, image: File?) {}
|
|
||||||
override fun onCacheMiss(imageType: Int, image: File?) {}
|
|
||||||
override fun onFinish() {}
|
|
||||||
override fun onProgress(progress: Int) {}
|
|
||||||
override fun onStart() {}
|
|
||||||
override fun onSuccess(image: File?) {}
|
|
||||||
})
|
|
||||||
ssiv?.recycle()
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
cache.getThumbnail().let { launch(Dispatchers.Main) {
|
|
||||||
showImage(it)
|
|
||||||
} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.galleryblockTitle.text = galleryBlock.title
|
|
||||||
with(binding.galleryblockArtist) {
|
|
||||||
text = artists.joinToString { it.wordCapitalize() }
|
|
||||||
visibility = when {
|
|
||||||
artists.isNotEmpty() -> View.VISIBLE
|
|
||||||
else -> View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val gallery = runCatching {
|
|
||||||
getGallery(galleryID)
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
if (gallery?.groups?.isNotEmpty() != true)
|
|
||||||
return@launch
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
text = context.getString(
|
|
||||||
R.string.galleryblock_artist_with_group,
|
|
||||||
artists.joinToString { it.wordCapitalize() },
|
|
||||||
gallery.groups.joinToString { it.wordCapitalize() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with(binding.galleryblockSeries) {
|
|
||||||
text =
|
|
||||||
resources.getString(
|
|
||||||
R.string.galleryblock_series,
|
|
||||||
series.joinToString(", ") { it.wordCapitalize() })
|
|
||||||
visibility = when {
|
|
||||||
series.isNotEmpty() -> View.VISIBLE
|
|
||||||
else -> View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.galleryblockType.text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize()
|
|
||||||
with(binding.galleryblockLanguage) {
|
|
||||||
text =
|
|
||||||
resources.getString(R.string.galleryblock_language, languages[galleryBlock.language])
|
|
||||||
visibility = when {
|
|
||||||
galleryBlock.language.isNotEmpty() -> View.VISIBLE
|
|
||||||
else -> View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with(binding.galleryblockTagGroup) {
|
|
||||||
onClickListener = {
|
|
||||||
onChipClickedHandler.forEach { callback ->
|
|
||||||
callback.invoke(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tags.clear()
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
tags.addAll(
|
|
||||||
galleryBlock.relatedTags.sortedBy {
|
|
||||||
val tag = Tag.parse(it)
|
|
||||||
|
|
||||||
if (favoriteTags.contains(tag))
|
|
||||||
-1
|
|
||||||
else
|
|
||||||
when(Tag.parse(it).area) {
|
|
||||||
"female" -> 0
|
|
||||||
"male" -> 1
|
|
||||||
else -> 2
|
|
||||||
}
|
|
||||||
}.map {
|
|
||||||
Tag.parse(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.galleryblockId.text = galleryBlock.id.toString()
|
|
||||||
binding.galleryblockPagecount.text = "-"
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val pageCount = kotlin.runCatching {
|
|
||||||
getReader(galleryBlock.id).galleryInfo.files.size
|
|
||||||
}.getOrNull() ?: return@launch
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
binding.galleryblockPagecount.text = itemView.context.getString(R.string.galleryblock_pagecount, pageCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with(binding.galleryblockFavorite) {
|
|
||||||
setImageResource(if (favorites.contains(galleryBlock.id)) R.drawable.ic_star_filled else R.drawable.ic_star_empty)
|
|
||||||
setOnClickListener {
|
|
||||||
when {
|
|
||||||
favorites.contains(galleryBlock.id) -> {
|
|
||||||
favorites.remove(galleryBlock.id)
|
|
||||||
|
|
||||||
setImageResource(R.drawable.ic_star_empty)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
favorites.add(galleryBlock.id)
|
|
||||||
|
|
||||||
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star).apply {
|
|
||||||
this ?: return@apply
|
|
||||||
|
|
||||||
registerAnimationCallback(object: Animatable2Compat.AnimationCallback() {
|
|
||||||
override fun onAnimationEnd(drawable: Drawable?) {
|
|
||||||
setImageResource(R.drawable.ic_star_filled)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
start()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Make some views invisible to make it thinner
|
|
||||||
if (thin) {
|
|
||||||
binding.galleryblockTagGroup.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
|
|
||||||
var onDownloadClickedHandler: ((Int) -> Unit)? = null
|
|
||||||
var onDeleteClickedHandler: ((Int) -> Unit)? = null
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return GalleryViewHolder(GalleryblockItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
if (holder is GalleryViewHolder) {
|
|
||||||
val galleryID = galleries[position]
|
|
||||||
|
|
||||||
holder.bind(galleryID)
|
|
||||||
|
|
||||||
holder.binding.galleryblockCard.binding.download.setOnClickListener {
|
|
||||||
onDownloadClickedHandler?.invoke(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.binding.galleryblockCard.binding.delete.setOnClickListener {
|
|
||||||
onDeleteClickedHandler?.invoke(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
mItemManger.bindView(holder.binding.root, position)
|
|
||||||
|
|
||||||
holder.binding.galleryblockCard.binding.swipeLayout.addSwipeListener(object: SwipeLayout.SwipeListener {
|
|
||||||
override fun onStartOpen(layout: SwipeLayout?) {
|
|
||||||
mItemManger.closeAllExcept(layout)
|
|
||||||
|
|
||||||
holder.binding.galleryblockCard.binding.download.text =
|
|
||||||
if (DownloadManager.getInstance(holder.binding.root.context).isDownloading(galleryID))
|
|
||||||
holder.binding.root.context.getString(android.R.string.cancel)
|
|
||||||
else
|
|
||||||
holder.binding.root.context.getString(R.string.main_download)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClose(layout: SwipeLayout?) {}
|
|
||||||
override fun onHandRelease(layout: SwipeLayout?, xvel: Float, yvel: Float) {}
|
|
||||||
override fun onOpen(layout: SwipeLayout?) {}
|
|
||||||
override fun onStartClose(layout: SwipeLayout?) {}
|
|
||||||
override fun onUpdate(layout: SwipeLayout?, leftOffset: Int, topOffset: Int) {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = galleries.size
|
|
||||||
|
|
||||||
override fun getSwipeLayoutResourceId(position: Int) = R.id.swipe_layout
|
|
||||||
}
|
|
||||||
@@ -40,7 +40,7 @@ import com.github.piasy.biv.view.BigImageView
|
|||||||
import com.github.piasy.biv.view.ImageShownCallback
|
import com.github.piasy.biv.view.ImageShownCallback
|
||||||
import com.github.piasy.biv.view.ImageViewFactory
|
import com.github.piasy.biv.view.ImageViewFactory
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.GalleryInfo
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.databinding.ReaderItemBinding
|
import xyz.quaver.pupil.databinding.ReaderItemBinding
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
@@ -50,9 +50,9 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
class ReaderAdapter(
|
class ReaderAdapter(
|
||||||
private val activity: ReaderActivity,
|
private val activity: ReaderActivity,
|
||||||
private val galleryID: Int
|
private val galleryID: String
|
||||||
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||||
var reader: Reader? = null
|
var reader: GalleryInfo? = null
|
||||||
|
|
||||||
var isFullScreen = false
|
var isFullScreen = false
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ class ReaderAdapter(
|
|||||||
binding.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
binding.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
height = 0
|
height = 0
|
||||||
dimensionRatio =
|
dimensionRatio =
|
||||||
"${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
"${reader!!.files[position].width}:${reader!!.files[position].height}"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.root.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
binding.root.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
@@ -158,7 +158,7 @@ class ReaderAdapter(
|
|||||||
holder.bind(position)
|
holder.bind(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
|
override fun getItemCount() = reader?.files?.size ?: 0
|
||||||
|
|
||||||
override fun onViewRecycled(holder: ViewHolder) {
|
override fun onViewRecycled(holder: ViewHolder) {
|
||||||
holder.clear()
|
holder.clear()
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.daimajia.swipe.SwipeLayout
|
||||||
|
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
||||||
|
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.databinding.SearchResultItemBinding
|
||||||
|
import xyz.quaver.pupil.sources.SearchResult
|
||||||
|
import xyz.quaver.pupil.types.Tag
|
||||||
|
import xyz.quaver.pupil.ui.view.ProgressCardView
|
||||||
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
|
||||||
|
class SearchResultsAdapter(private val results: List<SearchResult>) : RecyclerSwipeAdapter<SearchResultsAdapter.ViewHolder>(), SwipeAdapterInterface {
|
||||||
|
|
||||||
|
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
|
||||||
|
var onDownloadClickedHandler: ((String) -> Unit)? = null
|
||||||
|
var onDeleteClickedHandler: ((String) -> Unit)? = null
|
||||||
|
|
||||||
|
inner class ViewHolder(private val binding: SearchResultItemBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
var itemID: String = ""
|
||||||
|
var update = true
|
||||||
|
|
||||||
|
init {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
while (update) {
|
||||||
|
updateProgress()
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.root.binding.download.setOnClickListener {
|
||||||
|
onDownloadClickedHandler?.invoke(itemID)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.root.binding.delete.setOnClickListener {
|
||||||
|
onDeleteClickedHandler?.invoke(itemID)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.idView.setOnClickListener {
|
||||||
|
(itemView.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(
|
||||||
|
ClipData.newPlainText("item_id", itemID)
|
||||||
|
)
|
||||||
|
Toast.makeText(itemView.context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.root.binding.swipeLayout.addSwipeListener(object: SwipeLayout.SwipeListener {
|
||||||
|
override fun onStartOpen(layout: SwipeLayout?) {
|
||||||
|
mItemManger.closeAllExcept(layout)
|
||||||
|
|
||||||
|
binding.root.binding.download.text =
|
||||||
|
if (DownloadManager.getInstance(itemView.context).isDownloading(itemID))
|
||||||
|
itemView.context.getString(android.R.string.cancel)
|
||||||
|
else
|
||||||
|
itemView.context.getString(R.string.main_download)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpen(layout: SwipeLayout?) {}
|
||||||
|
override fun onStartClose(layout: SwipeLayout?) {}
|
||||||
|
override fun onClose(layout: SwipeLayout?) {}
|
||||||
|
override fun onHandRelease(layout: SwipeLayout?, xvel: Float, yvel: Float) {}
|
||||||
|
override fun onUpdate(layout: SwipeLayout?, leftOffset: Int, topOffset: Int) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateProgress() = CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
with (itemView as ProgressCardView) {
|
||||||
|
val imageList = Cache.getInstance(context, itemID).metadata.imageList
|
||||||
|
|
||||||
|
if (imageList == null) {
|
||||||
|
max = 0
|
||||||
|
return@with
|
||||||
|
}
|
||||||
|
|
||||||
|
progress = imageList.count { it != null }
|
||||||
|
max = imageList.size
|
||||||
|
|
||||||
|
type = if (!imageList.contains(null)) {
|
||||||
|
val downloadManager = DownloadManager.getInstance(context)
|
||||||
|
|
||||||
|
if (downloadManager.getDownloadFolder(itemID) == null)
|
||||||
|
ProgressCardView.Type.CACHE
|
||||||
|
else
|
||||||
|
ProgressCardView.Type.DOWNLOAD
|
||||||
|
} else
|
||||||
|
ProgressCardView.Type.LOADING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(result: SearchResult) {
|
||||||
|
itemID = result.id
|
||||||
|
|
||||||
|
binding.thumbnail.ssiv?.recycle()
|
||||||
|
binding.thumbnail.showImage(Uri.parse(result.thumbnail))
|
||||||
|
|
||||||
|
updateProgress()
|
||||||
|
|
||||||
|
binding.title.text = result.title
|
||||||
|
binding.idView.text = result.id
|
||||||
|
binding.artist.text = result.artists.joinToString { it.wordCapitalize() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
|
||||||
|
ViewHolder(SearchResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
mItemManger.bindView(holder.itemView, position)
|
||||||
|
holder.bind(results[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(holder: ViewHolder) {
|
||||||
|
holder.update = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = results.size
|
||||||
|
|
||||||
|
override fun getSwipeLayoutResourceId(position: Int): Int = R.id.swipe_layout
|
||||||
|
|
||||||
|
}
|
||||||
@@ -51,7 +51,7 @@ import kotlin.math.log10
|
|||||||
|
|
||||||
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
||||||
class DownloadService : Service() {
|
class DownloadService : Service() {
|
||||||
data class Tag(val galleryID: Int, val index: Int, val startId: Int? = null)
|
data class Tag(val galleryID: String, val index: Int, val startId: Int? = null)
|
||||||
|
|
||||||
//region Notification
|
//region Notification
|
||||||
private val notificationManager by lazy {
|
private val notificationManager by lazy {
|
||||||
@@ -66,15 +66,15 @@ class DownloadService : Service() {
|
|||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val notification = ConcurrentHashMap<Int, NotificationCompat.Builder?>()
|
private val notification = ConcurrentHashMap<String, NotificationCompat.Builder?>()
|
||||||
|
|
||||||
private fun initNotification(galleryID: Int) {
|
private fun initNotification(galleryID: String) {
|
||||||
val intent = Intent(this, ReaderActivity::class.java)
|
val intent = Intent(this, ReaderActivity::class.java)
|
||||||
.putExtra("galleryID", galleryID)
|
.putExtra("galleryID", galleryID)
|
||||||
|
|
||||||
val pendingIntent = TaskStackBuilder.create(this).run {
|
val pendingIntent = TaskStackBuilder.create(this).run {
|
||||||
addNextIntentWithParentStack(intent)
|
addNextIntentWithParentStack(intent)
|
||||||
getPendingIntent(galleryID, PendingIntent.FLAG_UPDATE_CURRENT)
|
getPendingIntent(galleryID.hashCode(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
val action =
|
val action =
|
||||||
NotificationCompat.Action.Builder(0, getText(android.R.string.cancel),
|
NotificationCompat.Action.Builder(0, getText(android.R.string.cancel),
|
||||||
@@ -101,7 +101,7 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
private fun notify(galleryID: Int) {
|
private fun notify(galleryID: String) {
|
||||||
val max = progress[galleryID]?.size ?: 0
|
val max = progress[galleryID]?.size ?: 0
|
||||||
val progress = progress[galleryID]?.count { it == Float.POSITIVE_INFINITY } ?: 0
|
val progress = progress[galleryID]?.count { it == Float.POSITIVE_INFINITY } ?: 0
|
||||||
|
|
||||||
@@ -114,16 +114,16 @@ class DownloadService : Service() {
|
|||||||
.setOngoing(false)
|
.setOngoing(false)
|
||||||
.mActions.clear()
|
.mActions.clear()
|
||||||
|
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID.hashCode())
|
||||||
} else
|
} else
|
||||||
notification
|
notification
|
||||||
.setProgress(max, progress, false)
|
.setProgress(max, progress, false)
|
||||||
.setContentText("$progress/$max")
|
.setContentText("$progress/$max")
|
||||||
|
|
||||||
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null || galleryID == priority)
|
if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null || galleryID == priority)
|
||||||
notification.let { notificationManager.notify(galleryID, it.build()) }
|
notification.let { notificationManager.notify(galleryID.hashCode(), it.build()) }
|
||||||
else
|
else
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID.hashCode())
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -194,10 +194,10 @@ class DownloadService : Service() {
|
|||||||
* 0 <= value < 100 -> Download in progress
|
* 0 <= value < 100 -> Download in progress
|
||||||
* Float.POSITIVE_INFINITY -> Download completed
|
* Float.POSITIVE_INFINITY -> Download completed
|
||||||
*/
|
*/
|
||||||
val progress = ConcurrentHashMap<Int, MutableList<Float>>()
|
val progress = ConcurrentHashMap<String, MutableList<Float>>()
|
||||||
var priority = 0
|
var priority = ""
|
||||||
|
|
||||||
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
|
fun isCompleted(galleryID: String) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
|
||||||
|
|
||||||
private val callback = object: Callback {
|
private val callback = object: Callback {
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ class DownloadService : Service() {
|
|||||||
startId?.let { stopSelf(it) }
|
startId?.let { stopSelf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancel(galleryID: Int, startId: Int? = null) {
|
fun cancel(galleryID: String, startId: Int? = null) {
|
||||||
client.dispatcher().queuedCalls().filter {
|
client.dispatcher().queuedCalls().filter {
|
||||||
(it.request().tag() as? Tag)?.galleryID == galleryID
|
(it.request().tag() as? Tag)?.galleryID == galleryID
|
||||||
}.forEach {
|
}.forEach {
|
||||||
@@ -282,12 +282,12 @@ class DownloadService : Service() {
|
|||||||
|
|
||||||
progress.remove(galleryID)
|
progress.remove(galleryID)
|
||||||
notification.remove(galleryID)
|
notification.remove(galleryID)
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID.hashCode())
|
||||||
|
|
||||||
startId?.let { stopSelf(it) }
|
startId?.let { stopSelf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
|
fun delete(galleryID: String, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch {
|
||||||
cancel(galleryID)
|
cancel(galleryID)
|
||||||
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
||||||
Cache.delete(this@DownloadService, galleryID)
|
Cache.delete(this@DownloadService, galleryID)
|
||||||
@@ -295,7 +295,7 @@ class DownloadService : Service() {
|
|||||||
startId?.let { stopSelf(it) }
|
startId?.let { stopSelf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
|
fun download(galleryID: String, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (DownloadManager.getInstance(this@DownloadService).isDownloading(galleryID))
|
if (DownloadManager.getInstance(this@DownloadService).isDownloading(galleryID))
|
||||||
return@launch
|
return@launch
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ class DownloadService : Service() {
|
|||||||
|
|
||||||
histories.add(galleryID)
|
histories.add(galleryID)
|
||||||
|
|
||||||
progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 0F }
|
progress[galleryID] = MutableList(reader.files.size) { 0F }
|
||||||
|
|
||||||
cache.metadata.imageList?.let {
|
cache.metadata.imageList?.let {
|
||||||
it.forEachIndexed { index, image ->
|
it.forEachIndexed { index, image ->
|
||||||
@@ -329,15 +329,15 @@ class DownloadService : Service() {
|
|||||||
.getDownloadFolder(galleryID) != null )
|
.getDownloadFolder(galleryID) != null )
|
||||||
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
||||||
|
|
||||||
notificationManager.cancel(galleryID)
|
notificationManager.cancel(galleryID.hashCode())
|
||||||
startId?.let { stopSelf(it) }
|
startId?.let { stopSelf(it) }
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30))
|
notification[galleryID]?.setContentTitle(reader.title?.ellipsize(30))
|
||||||
notify(galleryID)
|
notify(galleryID)
|
||||||
|
|
||||||
val queued = mutableSetOf<Int>()
|
val queued = mutableSetOf<String>()
|
||||||
|
|
||||||
if (priority) {
|
if (priority) {
|
||||||
client.dispatcher().queuedCalls().forEach {
|
client.dispatcher().queuedCalls().forEach {
|
||||||
@@ -372,7 +372,7 @@ class DownloadService : Service() {
|
|||||||
ContextCompat.startForegroundService(context, Intent(context, DownloadService::class.java).apply(extras))
|
ContextCompat.startForegroundService(context, Intent(context, DownloadService::class.java).apply(extras))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(context: Context, galleryID: Int, priority: Boolean = false) {
|
fun download(context: Context, galleryID: String, priority: Boolean = false) {
|
||||||
command(context) {
|
command(context) {
|
||||||
putExtra(KEY_COMMAND, COMMAND_DOWNLOAD)
|
putExtra(KEY_COMMAND, COMMAND_DOWNLOAD)
|
||||||
putExtra(KEY_PRIORITY, priority)
|
putExtra(KEY_PRIORITY, priority)
|
||||||
@@ -380,14 +380,14 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancel(context: Context, galleryID: Int? = null) {
|
fun cancel(context: Context, galleryID: String? = null) {
|
||||||
command(context) {
|
command(context) {
|
||||||
putExtra(KEY_COMMAND, COMMAND_CANCEL)
|
putExtra(KEY_COMMAND, COMMAND_CANCEL)
|
||||||
galleryID?.let { putExtra(KEY_ID, it) }
|
galleryID?.let { putExtra(KEY_ID, it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(context: Context, galleryID: Int) {
|
fun delete(context: Context, galleryID: String) {
|
||||||
command(context) {
|
command(context) {
|
||||||
putExtra(KEY_COMMAND, COMMAND_DELETE)
|
putExtra(KEY_COMMAND, COMMAND_DELETE)
|
||||||
putExtra(KEY_ID, galleryID)
|
putExtra(KEY_ID, galleryID)
|
||||||
@@ -399,11 +399,11 @@ class DownloadService : Service() {
|
|||||||
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
||||||
|
|
||||||
when (intent?.getStringExtra(KEY_COMMAND)) {
|
when (intent?.getStringExtra(KEY_COMMAND)) {
|
||||||
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
COMMAND_DOWNLOAD -> intent.getStringExtra(KEY_ID).let { if (!it.isNullOrEmpty())
|
||||||
download(it, intent.getBooleanExtra(KEY_PRIORITY, false), startId)
|
download(it, intent.getBooleanExtra(KEY_PRIORITY, false), startId)
|
||||||
}
|
}
|
||||||
COMMAND_CANCEL -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) cancel(it, startId) else cancel(startId = startId) }
|
COMMAND_CANCEL -> intent.getStringExtra(KEY_ID).let { if (!it.isNullOrEmpty()) cancel(it, startId) else cancel(startId = startId) }
|
||||||
COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it, startId) }
|
COMMAND_DELETE -> intent.getStringExtra(KEY_ID).let { if (!it.isNullOrEmpty()) delete(it, startId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
|
|||||||
37
app/src/main/java/xyz/quaver/pupil/sources/Common.kt
Normal file
37
app/src/main/java/xyz/quaver/pupil/sources/Common.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.sources
|
||||||
|
|
||||||
|
import com.google.android.gms.vision.L
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
interface SearchResult {
|
||||||
|
val id: String
|
||||||
|
val title: String
|
||||||
|
val thumbnail: String
|
||||||
|
val artists: List<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Might be better to use channel on Query_Result
|
||||||
|
interface Source<Query_SortMode: Enum<*>, Query_Result: SearchResult> {
|
||||||
|
val querySortModeClass: KClass<Query_SortMode>
|
||||||
|
val queryResultClass: KClass<Query_Result>
|
||||||
|
|
||||||
|
suspend fun query(query: String, range: IntRange, sortMode: Query_SortMode? = null) : Pair<List<Query_Result>, Int>
|
||||||
|
}
|
||||||
73
app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt
Normal file
73
app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.sources.hitomi
|
||||||
|
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
|
import xyz.quaver.hitomi.doSearch
|
||||||
|
import xyz.quaver.hitomi.getGalleryBlock
|
||||||
|
import xyz.quaver.pupil.sources.Source
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
class Hitomi : Source<Hitomi.SortMode, Hitomi.SearchResult> {
|
||||||
|
|
||||||
|
override val querySortModeClass = SortMode::class
|
||||||
|
override val queryResultClass = SearchResult::class
|
||||||
|
|
||||||
|
enum class SortMode {
|
||||||
|
NEWEST,
|
||||||
|
POPULAR
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SearchResult(
|
||||||
|
override val id: String,
|
||||||
|
override val title: String,
|
||||||
|
override val thumbnail: String,
|
||||||
|
override val artists: List<String>,
|
||||||
|
) : xyz.quaver.pupil.sources.SearchResult
|
||||||
|
|
||||||
|
var cachedQuery: String? = null
|
||||||
|
val cache = mutableListOf<Int>()
|
||||||
|
|
||||||
|
override suspend fun query(query: String, range: IntRange, sortMode: SortMode?): Pair<List<SearchResult>, Int> {
|
||||||
|
if (cachedQuery != query) {
|
||||||
|
cachedQuery = null
|
||||||
|
cache.clear()
|
||||||
|
yield()
|
||||||
|
doSearch(query, sortMode == SortMode.POPULAR).let {
|
||||||
|
yield()
|
||||||
|
cache.addAll(it)
|
||||||
|
}
|
||||||
|
cachedQuery = query
|
||||||
|
}
|
||||||
|
|
||||||
|
val sanitizedRange = max(0, range.first) .. min(range.last, cache.size-1)
|
||||||
|
return Pair(cache.slice(sanitizedRange).map {
|
||||||
|
getGalleryBlock(it).let { gallery ->
|
||||||
|
SearchResult(
|
||||||
|
gallery.id.toString(),
|
||||||
|
gallery.title,
|
||||||
|
gallery.thumbnails.first(),
|
||||||
|
gallery.artists
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, cache.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,35 +31,33 @@ import android.view.animation.DecelerateInterpolator
|
|||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import xyz.quaver.floatingsearchview.FloatingSearchView
|
import xyz.quaver.floatingsearchview.FloatingSearchView
|
||||||
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import xyz.quaver.floatingsearchview.util.view.MenuView
|
import xyz.quaver.floatingsearchview.util.view.MenuView
|
||||||
import xyz.quaver.floatingsearchview.util.view.SearchInputView
|
import xyz.quaver.floatingsearchview.util.view.SearchInputView
|
||||||
import xyz.quaver.hitomi.doSearch
|
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
|
||||||
import xyz.quaver.hitomi.getSuggestionsForQuery
|
import xyz.quaver.hitomi.getSuggestionsForQuery
|
||||||
import xyz.quaver.pupil.*
|
import xyz.quaver.pupil.*
|
||||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.SearchResultsAdapter
|
||||||
import xyz.quaver.pupil.databinding.MainActivityBinding
|
import xyz.quaver.pupil.databinding.MainActivityBinding
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
|
import xyz.quaver.pupil.sources.SearchResult
|
||||||
|
import xyz.quaver.pupil.sources.Source
|
||||||
|
import xyz.quaver.pupil.sources.hitomi.Hitomi
|
||||||
import xyz.quaver.pupil.types.*
|
import xyz.quaver.pupil.types.*
|
||||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
||||||
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
||||||
import xyz.quaver.pupil.ui.view.MainView
|
import xyz.quaver.pupil.ui.view.ProgressCardView
|
||||||
import xyz.quaver.pupil.ui.view.ProgressCard
|
import xyz.quaver.pupil.ui.view.SwipePageTurnView
|
||||||
import xyz.quaver.pupil.util.ItemClickSupport
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.checkUpdate
|
import xyz.quaver.pupil.util.checkUpdate
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.restore
|
import xyz.quaver.pupil.util.restore
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
@@ -69,20 +67,7 @@ class MainActivity :
|
|||||||
BaseActivity(),
|
BaseActivity(),
|
||||||
NavigationView.OnNavigationItemSelectedListener
|
NavigationView.OnNavigationItemSelectedListener
|
||||||
{
|
{
|
||||||
|
private val searchResults = mutableListOf<SearchResult>()
|
||||||
enum class Mode {
|
|
||||||
SEARCH,
|
|
||||||
HISTORY,
|
|
||||||
DOWNLOAD,
|
|
||||||
FAVORITE
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class SortMode {
|
|
||||||
NEWEST,
|
|
||||||
POPULAR
|
|
||||||
}
|
|
||||||
|
|
||||||
private val galleries = ArrayList<Int>()
|
|
||||||
|
|
||||||
private var query = ""
|
private var query = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -94,13 +79,13 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
private var queryStack = mutableListOf<String>()
|
private var queryStack = mutableListOf<String>()
|
||||||
|
|
||||||
private var mode = Mode.SEARCH
|
@Suppress("UNCHECKED_CAST")
|
||||||
private var sortMode = SortMode.NEWEST
|
private var source: Source<Enum<*>, SearchResult> = Hitomi() as Source<Enum<*>, SearchResult>
|
||||||
|
private var sortMode = Hitomi.SortMode.NEWEST
|
||||||
|
|
||||||
private var galleryIDs: Deferred<List<Int>>? = null
|
private var searchJob: Deferred<Pair<List<SearchResult>, Int>>? = null
|
||||||
private var totalItems = 0
|
private var totalItems = 0
|
||||||
private var loadingJob: Job? = null
|
private var currentPage = 1
|
||||||
private var currentPage = 0
|
|
||||||
|
|
||||||
private lateinit var binding: MainActivityBinding
|
private lateinit var binding: MainActivityBinding
|
||||||
|
|
||||||
@@ -136,35 +121,23 @@ class MainActivity :
|
|||||||
queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread {
|
queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread {
|
||||||
query = queryStack.last()
|
query = queryStack.last()
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
}
|
||||||
else -> super.onBackPressed()
|
else -> super.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
|
|
||||||
(binding.contents.recyclerview.adapter as? GalleryBlockAdapter)?.updateAll = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
val perPage = Preferences["per_page", "25"].toInt()
|
val perPage = Preferences["per_page", "25"].toInt()
|
||||||
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
|
|
||||||
return when(keyCode) {
|
return when(keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
if (currentPage > 0) {
|
if (currentPage > 1) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
currentPage--
|
currentPage--
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,10 +148,7 @@ class MainActivity :
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
currentPage++
|
currentPage++
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,18 +199,14 @@ class MainActivity :
|
|||||||
setTitle(R.string.main_jump_title)
|
setTitle(R.string.main_jump_title)
|
||||||
setMessage(getString(
|
setMessage(getString(
|
||||||
R.string.main_jump_message,
|
R.string.main_jump_message,
|
||||||
currentPage+1,
|
currentPage,
|
||||||
ceil(totalItems / perPage.toDouble()).roundToInt()
|
ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||||
))
|
))
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
|
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)
|
||||||
|
|
||||||
runOnUiThread {
|
query()
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
@@ -251,23 +217,18 @@ class MainActivity :
|
|||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
withTimeoutOrNull(100) {
|
withTimeoutOrNull(100) {
|
||||||
galleryIDs?.await()
|
searchJob?.await()
|
||||||
}
|
}
|
||||||
}.let {
|
}.let {
|
||||||
if (it?.isEmpty() == false) {
|
if (it?.first?.isEmpty() == false) {
|
||||||
val galleryID = it.random()
|
val random = it.first.random()
|
||||||
|
|
||||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
GalleryDialog(this@MainActivity, random.id).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
query = it.toQuery()
|
||||||
query = it.toQuery()
|
currentPage = 1
|
||||||
currentPage = 0
|
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
@@ -288,19 +249,14 @@ class MainActivity :
|
|||||||
setTitle(R.string.main_open_gallery_by_id)
|
setTitle(R.string.main_open_gallery_by_id)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
val galleryID = editText.text.toString()
|
||||||
|
|
||||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
query = it.toQuery()
|
||||||
query = it.toQuery()
|
currentPage = 1
|
||||||
currentPage = 0
|
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
@@ -309,8 +265,8 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
with(binding.contents.view) {
|
with(binding.contents.swipePageTurnView) {
|
||||||
setOnPageTurnListener(object: MainView.OnPageTurnListener {
|
setOnPageTurnListener(object: SwipePageTurnView.OnPageTurnListener {
|
||||||
override fun onPrev(page: Int) {
|
override fun onPrev(page: Int) {
|
||||||
currentPage--
|
currentPage--
|
||||||
|
|
||||||
@@ -322,10 +278,7 @@ class MainActivity :
|
|||||||
.setInterpolator(DecelerateInterpolator())
|
.setInterpolator(DecelerateInterpolator())
|
||||||
.translationY(0F)
|
.translationY(0F)
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNext(page: Int) {
|
override fun onNext(page: Int) {
|
||||||
@@ -339,95 +292,70 @@ class MainActivity :
|
|||||||
.setInterpolator(DecelerateInterpolator())
|
.setInterpolator(DecelerateInterpolator())
|
||||||
.translationY(0F)
|
.translationY(0F)
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSearchBar()
|
setupSearchBar()
|
||||||
setupRecyclerView()
|
setupRecyclerView()
|
||||||
fetchGalleries(query, sortMode)
|
query()
|
||||||
loadBlocks()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
with(binding.contents.recyclerview) {
|
with(binding.contents.recyclerview) {
|
||||||
adapter = GalleryBlockAdapter(galleries).apply {
|
adapter = SearchResultsAdapter(searchResults).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
query = it.toQuery()
|
||||||
query = it.toQuery()
|
currentPage = 0
|
||||||
currentPage = 0
|
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onDownloadClickedHandler = { position ->
|
onDownloadClickedHandler = { id ->
|
||||||
val galleryID = galleries[position]
|
if (DownloadManager.getInstance(context).isDownloading(id)) { //download in progress
|
||||||
|
DownloadService.cancel(this@MainActivity, id)
|
||||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
|
||||||
DownloadService.cancel(this@MainActivity, galleryID)
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
DownloadManager.getInstance(context).addDownloadFolder(id)
|
||||||
DownloadService.download(this@MainActivity, galleryID)
|
DownloadService.download(this@MainActivity, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
closeAllItems()
|
closeAllItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeleteClickedHandler = { position ->
|
onDeleteClickedHandler = { id ->
|
||||||
val galleryID = galleries[position]
|
DownloadService.delete(this@MainActivity, id)
|
||||||
DownloadService.delete(this@MainActivity, galleryID)
|
|
||||||
|
|
||||||
histories.remove(galleryID)
|
histories.remove(id)
|
||||||
|
|
||||||
if (this@MainActivity.mode != Mode.SEARCH)
|
|
||||||
runOnUiThread {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
|
|
||||||
closeAllItems()
|
closeAllItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ItemClickSupport.addTo(this).apply {
|
ItemClickSupport.addTo(this).apply {
|
||||||
onItemClickListener = listener@{ _, position, v ->
|
onItemClickListener = listener@{ _, position, v ->
|
||||||
if (v !is ProgressCard)
|
if (v !is ProgressCardView)
|
||||||
return@listener
|
return@listener
|
||||||
|
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||||
intent.putExtra("galleryID", galleries[position])
|
intent.putExtra("galleryID", searchResults[position].id)
|
||||||
|
|
||||||
//TODO: Maybe sprinkling some transitions will be nice :D
|
//TODO: Maybe sprinkling some transitions will be nice :D
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
onItemLongClickListener = listener@{ _, position, v ->
|
onItemLongClickListener = listener@{ _, position, v ->
|
||||||
if (v !is ProgressCard)
|
if (v !is ProgressCardView)
|
||||||
return@listener false
|
return@listener false
|
||||||
|
|
||||||
val galleryID = galleries.getOrNull(position) ?: return@listener true
|
val result = searchResults.getOrNull(position) ?: return@listener true
|
||||||
|
|
||||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
GalleryDialog(this@MainActivity, result.id).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
runOnUiThread {
|
query = it.toQuery()
|
||||||
query = it.toQuery()
|
currentPage = 0
|
||||||
currentPage = 0
|
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
@@ -458,7 +386,7 @@ class MainActivity :
|
|||||||
with(binding.contents.searchview) {
|
with(binding.contents.searchview) {
|
||||||
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
|
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
|
||||||
override fun onMenuOpened() {
|
override fun onMenuOpened() {
|
||||||
(binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
(binding.contents.recyclerview.adapter as SearchResultsAdapter).closeAllItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMenuClosed() {
|
override fun onMenuClosed() {
|
||||||
@@ -532,13 +460,8 @@ class MainActivity :
|
|||||||
override fun onFocusCleared() {
|
override fun onFocusCleared() {
|
||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
|
|
||||||
runOnUiThread {
|
currentPage = 1
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,43 +473,26 @@ class MainActivity :
|
|||||||
when(item?.itemId) {
|
when(item?.itemId) {
|
||||||
R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
|
R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
|
||||||
R.id.main_menu_thin -> {
|
R.id.main_menu_thin -> {
|
||||||
val thin = !item.isChecked
|
// TODO
|
||||||
|
|
||||||
item.isChecked = thin
|
|
||||||
binding.contents.recyclerview.apply {
|
|
||||||
(adapter as GalleryBlockAdapter).apply {
|
|
||||||
this.thin = thin
|
|
||||||
|
|
||||||
Preferences["thin"] = thin
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter = adapter // Force to redraw
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
R.id.main_menu_sort_newest -> {
|
R.id.main_menu_sort_newest -> {
|
||||||
sortMode = SortMode.NEWEST
|
sortMode = Hitomi.SortMode.NEWEST
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
currentPage = 0
|
currentPage = 1
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.main_menu_sort_popular -> {
|
R.id.main_menu_sort_popular -> {
|
||||||
sortMode = SortMode.POPULAR
|
sortMode = Hitomi.SortMode.POPULAR
|
||||||
item.isChecked = true
|
item.isChecked = true
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
currentPage = 0
|
currentPage = 1
|
||||||
|
|
||||||
cancelFetch()
|
query()
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -597,46 +503,6 @@ class MainActivity :
|
|||||||
binding.drawer.closeDrawers()
|
binding.drawer.closeDrawers()
|
||||||
|
|
||||||
when(item.itemId) {
|
when(item.itemId) {
|
||||||
R.id.main_drawer_home -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.SEARCH
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_history -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.HISTORY
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_downloads -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.DOWNLOAD
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_favorite -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.FAVORITE
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_help -> {
|
R.id.main_drawer_help -> {
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
||||||
}
|
}
|
||||||
@@ -659,161 +525,47 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelFetch() {
|
private fun cancelFetch() {
|
||||||
galleryIDs?.cancel()
|
searchJob?.cancel()
|
||||||
loadingJob?.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearGalleries() = CoroutineScope(Dispatchers.Main).launch {
|
private fun clearGalleries() = CoroutineScope(Dispatchers.Main).launch {
|
||||||
galleries.clear()
|
searchResults.clear()
|
||||||
|
|
||||||
with(binding.contents.recyclerview.adapter as GalleryBlockAdapter?) {
|
binding.contents.recyclerview.adapter?.notifyDataSetChanged()
|
||||||
this ?: return@with
|
|
||||||
|
|
||||||
this.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.contents.noresult.visibility = View.INVISIBLE
|
binding.contents.noresult.visibility = View.INVISIBLE
|
||||||
binding.contents.progressbar.show()
|
binding.contents.progressbar.show()
|
||||||
}
|
}
|
||||||
|
private fun query() {
|
||||||
private fun fetchGalleries(query: String, sortMode: SortMode) {
|
|
||||||
val defaultQuery: String = Preferences["default_query"]
|
|
||||||
|
|
||||||
if (query.isNotBlank())
|
|
||||||
searchHistory.add(query)
|
|
||||||
|
|
||||||
if (query != queryStack.lastOrNull()) {
|
|
||||||
queryStack.remove(query)
|
|
||||||
queryStack.add(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.isNotEmpty() && mode != Mode.SEARCH) {
|
|
||||||
Snackbar.make(binding.contents.recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply {
|
|
||||||
setAction(android.R.string.ok) {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
mode = Mode.SEARCH
|
|
||||||
queryStack.clear()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
galleryIDs = null
|
|
||||||
|
|
||||||
if (galleryIDs?.isActive == true)
|
|
||||||
return
|
|
||||||
|
|
||||||
galleryIDs = CoroutineScope(Dispatchers.IO).async {
|
|
||||||
when(mode) {
|
|
||||||
Mode.SEARCH -> {
|
|
||||||
when {
|
|
||||||
query.isEmpty() and defaultQuery.isEmpty() -> {
|
|
||||||
when(sortMode) {
|
|
||||||
SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all")
|
|
||||||
else -> getGalleryIDsFromNozomi(null, "index", "all")
|
|
||||||
}.also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Mode.HISTORY -> {
|
|
||||||
when {
|
|
||||||
query.isEmpty() -> {
|
|
||||||
histories.reversed().also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val result = doSearch(query).sorted()
|
|
||||||
histories.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Mode.DOWNLOAD -> {
|
|
||||||
val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
|
|
||||||
|
|
||||||
when {
|
|
||||||
query.isEmpty() -> downloads.reversed().also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val result = doSearch(query).sorted()
|
|
||||||
downloads.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Mode.FAVORITE -> {
|
|
||||||
when {
|
|
||||||
query.isEmpty() -> favorites.reversed().also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val result = doSearch(query).sorted()
|
|
||||||
favorites.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
|
||||||
totalItems = it.size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.toList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadBlocks() {
|
|
||||||
val perPage = Preferences["per_page", "25"].toInt()
|
val perPage = Preferences["per_page", "25"].toInt()
|
||||||
|
|
||||||
loadingJob = CoroutineScope(Dispatchers.IO).launch {
|
cancelFetch()
|
||||||
val galleryIDs = try {
|
clearGalleries()
|
||||||
galleryIDs!!.await().also {
|
|
||||||
if (it.isEmpty())
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
throw Exception("No result")
|
searchJob = async(Dispatchers.IO) {
|
||||||
|
source.query(
|
||||||
|
query + Preferences["default_query", ""],
|
||||||
|
(currentPage - 1) * perPage until currentPage * perPage,
|
||||||
|
sortMode
|
||||||
|
)
|
||||||
|
}.also {
|
||||||
|
val results: List<SearchResult>
|
||||||
|
|
||||||
|
it.await().let { r ->
|
||||||
|
results = r.first
|
||||||
|
totalItems = r.second
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
|
|
||||||
if (e.message != "No result")
|
binding.contents.progressbar.hide()
|
||||||
FirebaseCrashlytics.getInstance().recordException(e)
|
binding.contents.swipePageTurnView.setCurrentPage(currentPage, totalItems > currentPage*perPage)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
if (results.isEmpty()) {
|
||||||
binding.contents.noresult.visibility = View.VISIBLE
|
binding.contents.noresult.visibility = View.VISIBLE
|
||||||
binding.contents.progressbar.hide()
|
} else {
|
||||||
|
searchResults.addAll(results)
|
||||||
|
binding.contents.recyclerview.adapter?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
binding.contents.view.setCurrentPage(currentPage + 1, galleryIDs.size > (currentPage+1)*perPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks ->
|
|
||||||
for (chunk in chunks)
|
|
||||||
chunk.map { galleryID ->
|
|
||||||
async {
|
|
||||||
Cache.getInstance(this@MainActivity, galleryID).getGalleryBlock()?.let {
|
|
||||||
galleryID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.forEach {
|
|
||||||
it.await()?.also {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
binding.contents.progressbar.hide()
|
|
||||||
|
|
||||||
galleries.add(it)
|
|
||||||
binding.contents.recyclerview.adapter!!.notifyItemInserted(galleries.size - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import xyz.quaver.Code
|
|
||||||
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.databinding.NumberpickerDialogBinding
|
import xyz.quaver.pupil.databinding.NumberpickerDialogBinding
|
||||||
@@ -65,7 +64,7 @@ import xyz.quaver.pupil.util.startCamera
|
|||||||
|
|
||||||
class ReaderActivity : BaseActivity() {
|
class ReaderActivity : BaseActivity() {
|
||||||
|
|
||||||
private var galleryID = 0
|
private var galleryID = ""
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
|
|
||||||
private var isScroll = true
|
private var isScroll = true
|
||||||
@@ -81,7 +80,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
private val conn = object: ServiceConnection {
|
private val conn = object: ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
downloader = (service as DownloadService.Binder).service.also {
|
downloader = (service as DownloadService.Binder).service.also {
|
||||||
it.priority = 0
|
it.priority = ""
|
||||||
|
|
||||||
if (!it.progress.containsKey(galleryID))
|
if (!it.progress.containsKey(galleryID))
|
||||||
DownloadService.download(this@ReaderActivity, galleryID, true)
|
DownloadService.download(this@ReaderActivity, galleryID, true)
|
||||||
@@ -130,7 +129,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
cache = Cache.getInstance(this, galleryID)
|
cache = Cache.getInstance(this, galleryID)
|
||||||
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
||||||
|
|
||||||
if (galleryID == 0) {
|
if (galleryID.isEmpty()) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -151,14 +150,14 @@ class ReaderActivity : BaseActivity() {
|
|||||||
if (uri != null && lastPathSegment != null) {
|
if (uri != null && lastPathSegment != null) {
|
||||||
galleryID = when (uri.host) {
|
galleryID = when (uri.host) {
|
||||||
"hitomi.la" ->
|
"hitomi.la" ->
|
||||||
Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1)?.toIntOrNull() ?: 0
|
Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1) ?: ""
|
||||||
"hiyobi.me" -> lastPathSegment.toInt()
|
"hiyobi.me" -> lastPathSegment
|
||||||
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
"e-hentai.org" -> uri.pathSegments[1]
|
||||||
else -> 0
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
galleryID = intent.getIntExtra("galleryID", 0)
|
galleryID = intent.getStringExtra("galleryID") ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +183,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
|
|
||||||
with(binding.numberPicker) {
|
with(binding.numberPicker) {
|
||||||
minValue = 1
|
minValue = 1
|
||||||
maxValue = cache.metadata.reader?.galleryInfo?.files?.size ?: 0
|
maxValue = cache.metadata.reader?.files?.size ?: 0
|
||||||
value = currentPage
|
value = currentPage
|
||||||
}
|
}
|
||||||
val dialog = AlertDialog.Builder(this).apply {
|
val dialog = AlertDialog.Builder(this).apply {
|
||||||
@@ -307,17 +306,19 @@ class ReaderActivity : BaseActivity() {
|
|||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
title = reader.galleryInfo.title
|
title = reader.title
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title =
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title =
|
||||||
"$currentPage/${reader.galleryInfo.files.size}"
|
"$currentPage/${reader.files.size}"
|
||||||
|
|
||||||
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(
|
menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable(
|
||||||
this@ReaderActivity,
|
this@ReaderActivity,
|
||||||
|
R.drawable.hitomi
|
||||||
|
/*
|
||||||
when (reader.code) {
|
when (reader.code) {
|
||||||
Code.HITOMI -> R.drawable.hitomi
|
Code.HITOMI -> R.drawable.hitomi
|
||||||
Code.HIYOBI -> R.drawable.ic_hiyobi
|
Code.HIYOBI -> R.drawable.ic_hiyobi
|
||||||
else -> android.R.color.transparent
|
else -> android.R.color.transparent
|
||||||
}
|
}*/
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class DownloadFolderNameDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
val galleryID = Cache.instances.let { if (it.size == 0) 1199708 else it.keys.elementAt((0 until it.size).random()) }
|
val galleryID = Cache.instances.let { if (it.size == 0) "1199708" else it.keys.elementAt((0 until it.size).random()) }
|
||||||
val galleryBlock = runBlocking {
|
val galleryBlock = runBlocking {
|
||||||
Cache.getInstance(requireContext(), galleryID).getGalleryBlock()
|
Cache.getInstance(requireContext(), galleryID).getGalleryBlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import xyz.quaver.pupil.databinding.DownloadLocationItemBinding
|
|||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import xyz.quaver.pupil.util.byteToString
|
import xyz.quaver.pupil.util.byteToString
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.pupil.util.migrate
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class DownloadLocationDialogFragment : DialogFragment() {
|
class DownloadLocationDialogFragment : DialogFragment() {
|
||||||
@@ -181,8 +180,6 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ ->
|
setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ ->
|
||||||
if (Preferences["download_folder", ""].isEmpty())
|
if (Preferences["download_folder", ""].isEmpty())
|
||||||
Preferences["download_folder"] = context.getExternalFilesDir(null)?.toUri()?.toString() ?: ""
|
Preferences["download_folder"] = context.getExternalFilesDir(null)?.toUri()?.toString() ?: ""
|
||||||
|
|
||||||
DownloadManager.getInstance(requireContext()).migrate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isCancelable = false
|
isCancelable = false
|
||||||
|
|||||||
@@ -38,10 +38,12 @@ import kotlinx.coroutines.withContext
|
|||||||
import xyz.quaver.hitomi.Gallery
|
import xyz.quaver.hitomi.Gallery
|
||||||
import xyz.quaver.hitomi.getGallery
|
import xyz.quaver.hitomi.getGallery
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.SearchResultsAdapter
|
||||||
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
|
||||||
import xyz.quaver.pupil.databinding.*
|
import xyz.quaver.pupil.databinding.*
|
||||||
import xyz.quaver.pupil.favoriteTags
|
import xyz.quaver.pupil.favoriteTags
|
||||||
|
import xyz.quaver.pupil.sources.hitomi.Hitomi
|
||||||
|
import xyz.quaver.pupil.sources.SearchResult
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
import xyz.quaver.pupil.ui.view.TagChip
|
import xyz.quaver.pupil.ui.view.TagChip
|
||||||
@@ -51,7 +53,7 @@ import xyz.quaver.pupil.util.wordCapitalize
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(context) {
|
class GalleryDialog(context: Context, private val galleryID: String) : AlertDialog(context) {
|
||||||
|
|
||||||
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
||||||
|
|
||||||
@@ -80,7 +82,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
|
|||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
val gallery = getGallery(galleryID)
|
val gallery = getGallery(galleryID.toInt())
|
||||||
|
|
||||||
launch (Dispatchers.Main) {
|
launch (Dispatchers.Main) {
|
||||||
binding.progressbar.visibility = View.GONE
|
binding.progressbar.visibility = View.GONE
|
||||||
@@ -203,9 +205,9 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addRelated(gallery: Gallery) {
|
private fun addRelated(gallery: Gallery) {
|
||||||
val galleries = ArrayList<Int>()
|
val galleries = mutableListOf<SearchResult>()
|
||||||
|
|
||||||
val adapter = GalleryBlockAdapter(galleries).apply {
|
val adapter = SearchResultsAdapter(galleries).apply {
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||||
handler.invoke(tag)
|
handler.invoke(tag)
|
||||||
@@ -223,11 +225,11 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
|
|||||||
ItemClickSupport.addTo(this).apply {
|
ItemClickSupport.addTo(this).apply {
|
||||||
onItemClickListener = { _, position, _ ->
|
onItemClickListener = { _, position, _ ->
|
||||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||||
putExtra("galleryID", galleries[position])
|
putExtra("galleryID", galleries[position].id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onItemLongClickListener = { _, position, _ ->
|
onItemLongClickListener = { _, position, _ ->
|
||||||
GalleryDialog(context, galleries[position]).apply {
|
GalleryDialog(context, galleries[position].id).apply {
|
||||||
onChipClickedHandler.add { tag ->
|
onChipClickedHandler.add { tag ->
|
||||||
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||||
}
|
}
|
||||||
@@ -240,13 +242,20 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
|
|||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
gallery.related.forEach { galleryID ->
|
gallery.related.forEach { galleryID ->
|
||||||
Cache.getInstance(context, galleryID).getGalleryBlock()?.let {
|
Cache.getInstance(context, galleryID.toString()).getGalleryBlock()?.let {
|
||||||
galleries.add(galleryID)
|
galleries.add(
|
||||||
|
Hitomi.SearchResult(
|
||||||
|
it.id.toString(),
|
||||||
|
it.title,
|
||||||
|
it.thumbnails.first(),
|
||||||
|
it.artists
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import androidx.core.graphics.drawable.DrawableCompat
|
|||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.databinding.ProgressCardViewBinding
|
import xyz.quaver.pupil.databinding.ProgressCardViewBinding
|
||||||
|
|
||||||
class ProgressCard @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, defStyle: Int = R.attr.cardViewStyle) : CardView(context, attr, defStyle) {
|
class ProgressCardView @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, defStyle: Int = R.attr.cardViewStyle) : CardView(context, attr, defStyle) {
|
||||||
|
|
||||||
enum class Type {
|
enum class Type {
|
||||||
LOADING,
|
LOADING,
|
||||||
@@ -35,7 +35,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources;
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.view.NestedScrollingChild;
|
import androidx.core.view.NestedScrollingChild;
|
||||||
@@ -48,7 +47,7 @@ import androidx.core.widget.TextViewCompat;
|
|||||||
import xyz.quaver.pupil.R;
|
import xyz.quaver.pupil.R;
|
||||||
|
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
public class MainView extends ViewGroup implements NestedScrollingChild, NestedScrollingParent {
|
public class SwipePageTurnView extends ViewGroup implements NestedScrollingChild, NestedScrollingParent {
|
||||||
|
|
||||||
private static final int PAGE_TURN_LAYOUT_SIZE = 48;
|
private static final int PAGE_TURN_LAYOUT_SIZE = 48;
|
||||||
private static final int PAGE_TURN_ANIM_DURATION = 500;
|
private static final int PAGE_TURN_ANIM_DURATION = 500;
|
||||||
@@ -84,15 +83,15 @@ public class MainView extends ViewGroup implements NestedScrollingChild, NestedS
|
|||||||
|
|
||||||
private OnPageTurnListener mOnPageTurnListener;
|
private OnPageTurnListener mOnPageTurnListener;
|
||||||
|
|
||||||
public MainView(@NonNull Context context) {
|
public SwipePageTurnView(@NonNull Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MainView(@NonNull Context context, AttributeSet attr) {
|
public SwipePageTurnView(@NonNull Context context, AttributeSet attr) {
|
||||||
this(context, attr, 0);
|
this(context, attr, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MainView(@NonNull Context context, AttributeSet attr, int defStyle) {
|
public SwipePageTurnView(@NonNull Context context, AttributeSet attr, int defStyle) {
|
||||||
super(context, attr, defStyle);
|
super(context, attr, defStyle);
|
||||||
|
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
@@ -32,9 +32,8 @@ import kotlinx.serialization.decodeFromString
|
|||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import xyz.quaver.Code
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.GalleryInfo
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.*
|
import xyz.quaver.io.util.*
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
@@ -46,24 +45,24 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
var galleryBlock: GalleryBlock? = null,
|
var galleryBlock: GalleryBlock? = null,
|
||||||
var reader: Reader? = null,
|
var reader: GalleryInfo? = null,
|
||||||
var imageList: MutableList<String?>? = null
|
var imageList: MutableList<String?>? = null
|
||||||
) {
|
) {
|
||||||
fun copy(): Metadata = Metadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
fun copy(): Metadata = Metadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } })
|
||||||
}
|
}
|
||||||
|
|
||||||
class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) {
|
class Cache private constructor(context: Context, val galleryID: String) : ContextWrapper(context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val instances = ConcurrentHashMap<Int, Cache>()
|
val instances = ConcurrentHashMap<String, Cache>()
|
||||||
|
|
||||||
fun getInstance(context: Context, galleryID: Int) =
|
fun getInstance(context: Context, galleryID: String) =
|
||||||
instances[galleryID] ?: synchronized(this) {
|
instances[galleryID] ?: synchronized(this) {
|
||||||
instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
instances[galleryID] ?: Cache(context, galleryID).also { instances[galleryID] = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun delete(context: Context, galleryID: Int) {
|
fun delete(context: Context, galleryID: String) {
|
||||||
File(context.cacheDir, "imageCache/$galleryID").deleteRecursively()
|
File(context.cacheDir, "imageCache/$galleryID").deleteRecursively()
|
||||||
instances.remove(galleryID)
|
instances.remove(galleryID)
|
||||||
}
|
}
|
||||||
@@ -111,8 +110,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
|
|
||||||
suspend fun getGalleryBlock(): GalleryBlock? {
|
suspend fun getGalleryBlock(): GalleryBlock? {
|
||||||
val sources = listOf(
|
val sources = listOf(
|
||||||
{ xyz.quaver.hitomi.getGalleryBlock(galleryID) },
|
{ xyz.quaver.hitomi.getGalleryBlock(galleryID.toInt()) }
|
||||||
{ xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
|
// { xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
|
||||||
)
|
)
|
||||||
|
|
||||||
return metadata.galleryBlock
|
return metadata.galleryBlock
|
||||||
@@ -154,22 +153,17 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}.getOrNull()?.uri }
|
}.getOrNull()?.uri }
|
||||||
} } ?: Uri.EMPTY
|
} } ?: Uri.EMPTY
|
||||||
|
|
||||||
suspend fun getReader(): Reader? {
|
suspend fun getReader(): GalleryInfo? {
|
||||||
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
val mirrors = Preferences.get<String>("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') }
|
||||||
|
|
||||||
val sources = mapOf(
|
val sources = mapOf(
|
||||||
Code.HITOMI to { xyz.quaver.hitomi.getReader(galleryID) },
|
"hitomi" to { xyz.quaver.hitomi.getGalleryInfo(galleryID.toInt()) },
|
||||||
Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) }
|
//Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) }
|
||||||
).let {
|
)
|
||||||
if (mirrors.isNotEmpty())
|
|
||||||
it.toSortedMap{ o1, o2 -> mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name) }
|
|
||||||
else
|
|
||||||
it
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata.reader
|
return metadata.reader
|
||||||
?: withContext(Dispatchers.IO) {
|
?: withContext(Dispatchers.IO) {
|
||||||
var reader: Reader? = null
|
var reader: GalleryInfo? = null
|
||||||
|
|
||||||
for (source in sources) {
|
for (source in sources) {
|
||||||
reader = try {
|
reader = try {
|
||||||
@@ -187,7 +181,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
metadata.reader = it
|
metadata.reader = it
|
||||||
|
|
||||||
if (metadata.imageList == null)
|
if (metadata.imageList == null)
|
||||||
metadata.imageList = MutableList(reader.galleryInfo.files.size) { null }
|
metadata.imageList = MutableList(reader.files.size) { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +200,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
setMetadata { metadata -> metadata.imageList!![index] = fileName }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val lock = ConcurrentHashMap<Int, Mutex>()
|
private val lock = ConcurrentHashMap<String, Mutex>()
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val downloadFolder = downloadFolder ?: return@launch
|
val downloadFolder = downloadFolder ?: return@launch
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
}.invoke()
|
}.invoke()
|
||||||
|
|
||||||
private var prevDownloadFolder: FileX? = null
|
private var prevDownloadFolder: FileX? = null
|
||||||
private var downloadFolderMapInstance: MutableMap<Int, String>? = null
|
private var downloadFolderMapInstance: MutableMap<String, String>? = null
|
||||||
val downloadFolderMap: MutableMap<Int, String>
|
val downloadFolderMap: MutableMap<String, String>
|
||||||
@Synchronized
|
@Synchronized
|
||||||
get() {
|
get() {
|
||||||
if (prevDownloadFolder != downloadFolder) {
|
if (prevDownloadFolder != downloadFolder) {
|
||||||
@@ -68,14 +68,14 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
val data = if (file.exists())
|
val data = if (file.exists())
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
file.readText()?.let { Json.decodeFromString<MutableMap<Int, String>>(it) }
|
file.readText()?.let { Json.decodeFromString<MutableMap<String, String>>(it) }
|
||||||
}.onFailure { file.delete() }.getOrNull()
|
}.onFailure { file.delete() }.getOrNull()
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
|
|
||||||
data ?: {
|
data ?: {
|
||||||
file.createNewFile()
|
file.createNewFile()
|
||||||
mutableMapOf<Int, String>()
|
mutableMapOf<String, String>()
|
||||||
}.invoke()
|
}.invoke()
|
||||||
}.invoke()
|
}.invoke()
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun isDownloading(galleryID: Int): Boolean {
|
fun isDownloading(galleryID: String): Boolean {
|
||||||
val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID }
|
val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID }
|
||||||
|
|
||||||
return downloadFolderMap.containsKey(galleryID)
|
return downloadFolderMap.containsKey(galleryID)
|
||||||
@@ -93,11 +93,11 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun getDownloadFolder(galleryID: Int): FileX? =
|
fun getDownloadFolder(galleryID: String): FileX? =
|
||||||
downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) }
|
downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) }
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun addDownloadFolder(galleryID: Int) {
|
fun addDownloadFolder(galleryID: String) {
|
||||||
val name = runBlocking {
|
val name = runBlocking {
|
||||||
Cache.getInstance(this@DownloadManager, galleryID).getGalleryBlock()
|
Cache.getInstance(this@DownloadManager, galleryID).getGalleryBlock()
|
||||||
}?.formatDownloadFolder() ?: return
|
}?.formatDownloadFolder() ?: return
|
||||||
@@ -116,7 +116,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun deleteDownloadFolder(galleryID: Int) {
|
fun deleteDownloadFolder(galleryID: String) {
|
||||||
downloadFolderMap[galleryID]?.let {
|
downloadFolderMap[galleryID]?.let {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
downloadFolder.getChild(it).deleteRecursively()
|
downloadFolder.getChild(it).deleteRecursively()
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ import androidx.core.content.ContextCompat
|
|||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import xyz.quaver.Code
|
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.GalleryInfo
|
||||||
import xyz.quaver.hitomi.getReferer
|
import xyz.quaver.hitomi.getReferer
|
||||||
import xyz.quaver.hitomi.imageUrlFromImage
|
import xyz.quaver.hitomi.imageUrlFromImage
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
@@ -103,11 +102,17 @@ fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
|||||||
}
|
}
|
||||||
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||||
|
|
||||||
val Reader.requestBuilders: List<Request.Builder>
|
val GalleryInfo.requestBuilders: List<Request.Builder>
|
||||||
get() {
|
get() {
|
||||||
val galleryID = this.galleryInfo.id ?: 0
|
val galleryID = this.id ?: 0
|
||||||
val lowQuality = Preferences["low_quality", true]
|
val lowQuality = Preferences["low_quality", true]
|
||||||
|
|
||||||
|
return this.files.map {
|
||||||
|
Request.Builder()
|
||||||
|
.url(imageUrlFromImage(galleryID, it, !lowQuality))
|
||||||
|
.header("Referer", getReferer(galleryID))
|
||||||
|
}
|
||||||
|
/*
|
||||||
return when(code) {
|
return when(code) {
|
||||||
Code.HITOMI -> {
|
Code.HITOMI -> {
|
||||||
this.galleryInfo.files.map {
|
this.galleryInfo.files.map {
|
||||||
@@ -122,9 +127,8 @@ val Reader.requestBuilders: List<Request.Builder>
|
|||||||
.url(it.path)
|
.url(it.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.ellipsize(n: Int): String =
|
fun String.ellipsize(n: Int): String =
|
||||||
if (this.length > n)
|
if (this.length > n)
|
||||||
this.slice(0 until n) + "…"
|
this.slice(0 until n) + "…"
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import okhttp3.Request
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import ru.noties.markwon.Markwon
|
import ru.noties.markwon.Markwon
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
|
||||||
import xyz.quaver.hitomi.getGalleryBlock
|
import xyz.quaver.hitomi.getGalleryBlock
|
||||||
import xyz.quaver.hitomi.getReader
|
import xyz.quaver.hitomi.getReader
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
@@ -196,7 +195,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List<Int>) -> Unit)? = null) {
|
fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List<String>) -> Unit)? = null) {
|
||||||
if (!URLUtil.isValidUrl(url)) {
|
if (!URLUtil.isValidUrl(url)) {
|
||||||
onFailure?.invoke(IllegalArgumentException())
|
onFailure?.invoke(IllegalArgumentException())
|
||||||
return
|
return
|
||||||
@@ -214,7 +213,7 @@ fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((
|
|||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
Json.decodeFromString<List<Int>>(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]").let {
|
Json.decodeFromString<List<String>>(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]").let {
|
||||||
favorites.addAll(it)
|
favorites.addAll(it)
|
||||||
onSuccess?.invoke(it)
|
onSuccess?.invoke(it)
|
||||||
}
|
}
|
||||||
@@ -237,111 +236,4 @@ private val receiver = object: BroadcastReceiver() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
|
||||||
registerReceiver(receiver, IntentFilter().apply { addAction(receiver.ACTION_CANCEL) })
|
|
||||||
|
|
||||||
val notificationManager = NotificationManagerCompat.from(this)
|
|
||||||
val action = NotificationCompat.Action.Builder(0, getText(android.R.string.cancel),
|
|
||||||
PendingIntent.getBroadcast(this, R.id.notification_import_cancel_action.normalizeID(), Intent(receiver.ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT)
|
|
||||||
).build()
|
|
||||||
val notification = NotificationCompat.Builder(this, "import")
|
|
||||||
.setContentTitle(getText(R.string.import_old_galleries_notification))
|
|
||||||
.setProgress(0, 0, true)
|
|
||||||
.addAction(action)
|
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
|
||||||
.setOngoing(true)
|
|
||||||
|
|
||||||
DownloadService.cancel(this)
|
|
||||||
|
|
||||||
job?.cancel()
|
|
||||||
job = CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val images = listOf(
|
|
||||||
"jpg",
|
|
||||||
"png",
|
|
||||||
"gif",
|
|
||||||
"webp"
|
|
||||||
)
|
|
||||||
|
|
||||||
val downloadFolders = downloadFolder.listFiles { folder ->
|
|
||||||
folder.isDirectory && !downloadFolderMap.values.contains(folder.name)
|
|
||||||
}?.map {
|
|
||||||
if (it !is FileX)
|
|
||||||
FileX(this@migrate, it)
|
|
||||||
else
|
|
||||||
it
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadFolders.isNullOrEmpty()) return@launch
|
|
||||||
|
|
||||||
downloadFolders.forEachIndexed { index, folder ->
|
|
||||||
notification
|
|
||||||
.setContentText(getString(R.string.import_old_galleries_notification_text, index, downloadFolders.size))
|
|
||||||
.setProgress(index, downloadFolders.size, false)
|
|
||||||
notificationManager.notify(R.id.notification_id_import, notification.build())
|
|
||||||
|
|
||||||
val metadata = kotlin.runCatching {
|
|
||||||
folder.getChild(".metadata").readText()?.let { Json.parseToJsonElement(it) }
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
val galleryID = metadata?.get("reader")?.get("galleryInfo")?.get("id")?.content?.toIntOrNull()
|
|
||||||
?: folder.name.toIntOrNull() ?: return@forEachIndexed
|
|
||||||
|
|
||||||
val galleryBlock: GalleryBlock? = kotlin.runCatching {
|
|
||||||
metadata?.get("galleryBlock")?.let { Json.decodeFromJsonElement<GalleryBlock>(it) }
|
|
||||||
}.getOrNull() ?: kotlin.runCatching {
|
|
||||||
getGalleryBlock(galleryID)
|
|
||||||
}.getOrNull() ?: kotlin.runCatching {
|
|
||||||
xyz.quaver.hiyobi.getGalleryBlock(galleryID)
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
val reader: Reader? = kotlin.runCatching {
|
|
||||||
metadata?.get("reader")?.let { Json.decodeFromJsonElement<Reader>(it) }
|
|
||||||
}.getOrNull() ?: kotlin.runCatching {
|
|
||||||
getReader(galleryID)
|
|
||||||
}.getOrNull() ?: kotlin.runCatching {
|
|
||||||
xyz.quaver.hiyobi.getReader(galleryID)
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
metadata?.get("thumbnail")?.jsonPrimitive?.contentOrNull?.also { thumbnail ->
|
|
||||||
val file = folder.getChild(".thumbnail").also {
|
|
||||||
if (it.exists())
|
|
||||||
it.delete()
|
|
||||||
it.createNewFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
file.writeBytes(Base64.decode(thumbnail, Base64.DEFAULT))
|
|
||||||
}
|
|
||||||
|
|
||||||
val list: MutableList<String?> =
|
|
||||||
MutableList(reader!!.galleryInfo.files.size) { null }
|
|
||||||
|
|
||||||
folder.list { _, name ->
|
|
||||||
name?.substringAfterLast('.') in images
|
|
||||||
}?.sorted()?.take(list.size)?.forEachIndexed { i, name ->
|
|
||||||
list[i] = name
|
|
||||||
}
|
|
||||||
|
|
||||||
folder.getChild(".metadata").also { if (it.exists()) it.delete(); it.createNewFile() }.writeText(
|
|
||||||
Json.encodeToString(Metadata(galleryBlock, reader, list))
|
|
||||||
)
|
|
||||||
|
|
||||||
Cache.delete(this@migrate, galleryID)
|
|
||||||
downloadFolderMap[galleryID] = folder.name
|
|
||||||
|
|
||||||
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile(); it.writeText(Json.encodeToString(downloadFolderMap)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
notification
|
|
||||||
.setContentText(getText(R.string.import_old_galleries_notification_done))
|
|
||||||
.setProgress(0, 0, false)
|
|
||||||
.setOngoing(false)
|
|
||||||
.mActions.clear()
|
|
||||||
notificationManager.notify(R.id.notification_id_import, notification.build())
|
|
||||||
|
|
||||||
kotlin.runCatching {
|
|
||||||
unregisterReceiver(receiver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.MainActivity">
|
tools:context=".ui.MainActivity">
|
||||||
|
|
||||||
<xyz.quaver.pupil.ui.view.MainView
|
<xyz.quaver.pupil.ui.view.SwipePageTurnView
|
||||||
android:id="@+id/view"
|
android:id="@+id/swipe_page_turn_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||||
|
|
||||||
</xyz.quaver.pupil.ui.view.MainView>
|
</xyz.quaver.pupil.ui.view.SwipePageTurnView>
|
||||||
|
|
||||||
<androidx.core.widget.ContentLoadingProgressBar
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
style="?android:attr/progressBarStyle"
|
style="?android:attr/progressBarStyle"
|
||||||
|
|||||||
@@ -17,11 +17,10 @@
|
|||||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<xyz.quaver.pupil.ui.view.ProgressCard
|
<xyz.quaver.pupil.ui.view.ProgressCardView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/galleryblock_card"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clipChildren="true"
|
android:clipChildren="true"
|
||||||
@@ -36,12 +35,13 @@
|
|||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.github.piasy.biv.view.BigImageView
|
<com.github.piasy.biv.view.BigImageView
|
||||||
android:id="@+id/galleryblock_thumbnail"
|
android:id="@+id/thumbnail"
|
||||||
android:layout_width="150dp"
|
android:layout_width="150dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:contentDescription="@string/galleryblock_thumbnail_description"
|
android:contentDescription="@string/galleryblock_thumbnail_description"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
|
android:duplicateParentState="true"
|
||||||
app:layout_constraintHeight_default="spread"
|
app:layout_constraintHeight_default="spread"
|
||||||
app:layout_constraintHeight_min="200dp"
|
app:layout_constraintHeight_min="200dp"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
@@ -50,61 +50,61 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextAppearance.AppCompat.Headline"
|
style="@style/TextAppearance.AppCompat.Headline"
|
||||||
android:id="@+id/galleryblock_title"
|
android:id="@+id/title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/thumbnail"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/TextAppearance.AppCompat.Medium"
|
style="@style/TextAppearance.AppCompat.Medium"
|
||||||
android:id="@+id/galleryblock_artist"
|
android:id="@+id/artist"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/thumbnail"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_title" />
|
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_series"
|
android:id="@+id/series"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
|
app:layout_constraintTop_toBottomOf="@id/artist"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/thumbnail"
|
||||||
app:layout_constraintRight_toRightOf="parent"/>
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_type"
|
android:id="@+id/type"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
|
app:layout_constraintTop_toBottomOf="@id/series"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" />
|
app:layout_constraintLeft_toRightOf="@id/thumbnail" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_language"
|
android:id="@+id/language"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
|
app:layout_constraintTop_toBottomOf="@id/type"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" />
|
app:layout_constraintLeft_toRightOf="@id/thumbnail" />
|
||||||
|
|
||||||
<xyz.quaver.pupil.ui.view.TagChipGroup
|
<xyz.quaver.pupil.ui.view.TagChipGroup
|
||||||
android:id="@+id/galleryblock_tag_group"
|
android:id="@+id/tag_group"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
app:chipSpacing="4dp"
|
app:chipSpacing="4dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
|
app:layout_constraintTop_toBottomOf="@id/language"
|
||||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/thumbnail"
|
||||||
app:layout_constraintRight_toRightOf="parent"/>
|
app:layout_constraintRight_toRightOf="parent"/>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Barrier
|
<androidx.constraintlayout.widget.Barrier
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:barrierDirection="bottom"
|
app:barrierDirection="bottom"
|
||||||
app:constraint_referenced_ids="galleryblock_thumbnail, galleryblock_tag_group"/>
|
app:constraint_referenced_ids="thumbnail, tag_group"/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/divider"
|
android:id="@+id/divider"
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
android:layout_margin="8dp"/>
|
android:layout_margin="8dp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_id"
|
android:id="@+id/id_view"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/galleryblock_pagecount"
|
android:id="@+id/pagecount"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
app:layout_constraintRight_toRightOf="parent" />
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/galleryblock_favorite"
|
android:id="@+id/favorite"
|
||||||
android:contentDescription="@string/app_name"
|
android:contentDescription="@string/app_name"
|
||||||
android:layout_width="32dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="32dp"
|
android:layout_height="32dp"
|
||||||
@@ -157,4 +157,4 @@
|
|||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</xyz.quaver.pupil.ui.view.ProgressCard>
|
</xyz.quaver.pupil.ui.view.ProgressCardView>
|
||||||
Reference in New Issue
Block a user