Rebase source onto dev
This commit is contained in:
@@ -123,7 +123,7 @@ dependencies {
|
||||
|
||||
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:floatingsearchview:1.0.7"
|
||||
|
||||
|
||||
@@ -51,9 +51,9 @@ import kotlin.reflect.KClass
|
||||
|
||||
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
||||
|
||||
lateinit var histories: SavedSet<Int>
|
||||
lateinit var histories: SavedSet<String>
|
||||
private set
|
||||
lateinit var favorites: SavedSet<Int>
|
||||
lateinit var favorites: SavedSet<String>
|
||||
private set
|
||||
lateinit var favoriteTags: SavedSet<Tag>
|
||||
private set
|
||||
@@ -108,8 +108,6 @@ class Pupil : Application() {
|
||||
|
||||
if (!FileX(this, it).canWrite())
|
||||
throw Exception()
|
||||
|
||||
DownloadManager.getInstance(this).migrate()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Preferences.remove("download_folder")
|
||||
@@ -120,8 +118,8 @@ class Pupil : Application() {
|
||||
Preferences["reset_secure"] = true
|
||||
}
|
||||
|
||||
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
|
||||
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
|
||||
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), "")
|
||||
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), "")
|
||||
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
|
||||
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.ImageViewFactory
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.GalleryInfo
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.databinding.ReaderItemBinding
|
||||
import xyz.quaver.pupil.ui.ReaderActivity
|
||||
@@ -50,9 +50,9 @@ import kotlin.math.roundToInt
|
||||
|
||||
class ReaderAdapter(
|
||||
private val activity: ReaderActivity,
|
||||
private val galleryID: Int
|
||||
private val galleryID: String
|
||||
) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
|
||||
var reader: Reader? = null
|
||||
var reader: GalleryInfo? = null
|
||||
|
||||
var isFullScreen = false
|
||||
|
||||
@@ -101,7 +101,7 @@ class ReaderAdapter(
|
||||
binding.image.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||
height = 0
|
||||
dimensionRatio =
|
||||
"${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
||||
"${reader!!.files[position].width}:${reader!!.files[position].height}"
|
||||
}
|
||||
} else {
|
||||
binding.root.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
@@ -158,7 +158,7 @@ class ReaderAdapter(
|
||||
holder.bind(position)
|
||||
}
|
||||
|
||||
override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0
|
||||
override fun getItemCount() = reader?.files?.size ?: 0
|
||||
|
||||
override fun onViewRecycled(holder: ViewHolder) {
|
||||
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
|
||||
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
|
||||
private val notificationManager by lazy {
|
||||
@@ -66,15 +66,15 @@ class DownloadService : Service() {
|
||||
.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)
|
||||
.putExtra("galleryID", galleryID)
|
||||
|
||||
val pendingIntent = TaskStackBuilder.create(this).run {
|
||||
addNextIntentWithParentStack(intent)
|
||||
getPendingIntent(galleryID, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
getPendingIntent(galleryID.hashCode(), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
val action =
|
||||
NotificationCompat.Action.Builder(0, getText(android.R.string.cancel),
|
||||
@@ -101,7 +101,7 @@ class DownloadService : Service() {
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private fun notify(galleryID: Int) {
|
||||
private fun notify(galleryID: String) {
|
||||
val max = progress[galleryID]?.size ?: 0
|
||||
val progress = progress[galleryID]?.count { it == Float.POSITIVE_INFINITY } ?: 0
|
||||
|
||||
@@ -114,16 +114,16 @@ class DownloadService : Service() {
|
||||
.setOngoing(false)
|
||||
.mActions.clear()
|
||||
|
||||
notificationManager.cancel(galleryID)
|
||||
notificationManager.cancel(galleryID.hashCode())
|
||||
} else
|
||||
notification
|
||||
.setProgress(max, progress, false)
|
||||
.setContentText("$progress/$max")
|
||||
|
||||
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
|
||||
notificationManager.cancel(galleryID)
|
||||
notificationManager.cancel(galleryID.hashCode())
|
||||
}
|
||||
//endregion
|
||||
|
||||
@@ -194,10 +194,10 @@ class DownloadService : Service() {
|
||||
* 0 <= value < 100 -> Download in progress
|
||||
* Float.POSITIVE_INFINITY -> Download completed
|
||||
*/
|
||||
val progress = ConcurrentHashMap<Int, MutableList<Float>>()
|
||||
var priority = 0
|
||||
val progress = ConcurrentHashMap<String, MutableList<Float>>()
|
||||
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 {
|
||||
|
||||
@@ -266,7 +266,7 @@ class DownloadService : Service() {
|
||||
startId?.let { stopSelf(it) }
|
||||
}
|
||||
|
||||
fun cancel(galleryID: Int, startId: Int? = null) {
|
||||
fun cancel(galleryID: String, startId: Int? = null) {
|
||||
client.dispatcher().queuedCalls().filter {
|
||||
(it.request().tag() as? Tag)?.galleryID == galleryID
|
||||
}.forEach {
|
||||
@@ -282,12 +282,12 @@ class DownloadService : Service() {
|
||||
|
||||
progress.remove(galleryID)
|
||||
notification.remove(galleryID)
|
||||
notificationManager.cancel(galleryID)
|
||||
notificationManager.cancel(galleryID.hashCode())
|
||||
|
||||
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)
|
||||
DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID)
|
||||
Cache.delete(this@DownloadService, galleryID)
|
||||
@@ -295,7 +295,7 @@ class DownloadService : Service() {
|
||||
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))
|
||||
return@launch
|
||||
|
||||
@@ -316,7 +316,7 @@ class DownloadService : Service() {
|
||||
|
||||
histories.add(galleryID)
|
||||
|
||||
progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 0F }
|
||||
progress[galleryID] = MutableList(reader.files.size) { 0F }
|
||||
|
||||
cache.metadata.imageList?.let {
|
||||
it.forEachIndexed { index, image ->
|
||||
@@ -329,15 +329,15 @@ class DownloadService : Service() {
|
||||
.getDownloadFolder(galleryID) != null )
|
||||
Cache.getInstance(this@DownloadService, galleryID).moveToDownload()
|
||||
|
||||
notificationManager.cancel(galleryID)
|
||||
notificationManager.cancel(galleryID.hashCode())
|
||||
startId?.let { stopSelf(it) }
|
||||
return@launch
|
||||
}
|
||||
|
||||
notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30))
|
||||
notification[galleryID]?.setContentTitle(reader.title?.ellipsize(30))
|
||||
notify(galleryID)
|
||||
|
||||
val queued = mutableSetOf<Int>()
|
||||
val queued = mutableSetOf<String>()
|
||||
|
||||
if (priority) {
|
||||
client.dispatcher().queuedCalls().forEach {
|
||||
@@ -372,7 +372,7 @@ class DownloadService : Service() {
|
||||
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) {
|
||||
putExtra(KEY_COMMAND, COMMAND_DOWNLOAD)
|
||||
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) {
|
||||
putExtra(KEY_COMMAND, COMMAND_CANCEL)
|
||||
galleryID?.let { putExtra(KEY_ID, it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(context: Context, galleryID: Int) {
|
||||
fun delete(context: Context, galleryID: String) {
|
||||
command(context) {
|
||||
putExtra(KEY_COMMAND, COMMAND_DELETE)
|
||||
putExtra(KEY_ID, galleryID)
|
||||
@@ -399,11 +399,11 @@ class DownloadService : Service() {
|
||||
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
||||
|
||||
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)
|
||||
}
|
||||
COMMAND_CANCEL -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) cancel(it, startId) else cancel(startId = startId) }
|
||||
COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it, startId) }
|
||||
COMMAND_CANCEL -> intent.getStringExtra(KEY_ID).let { if (!it.isNullOrEmpty()) cancel(it, startId) else cancel(startId = startId) }
|
||||
COMMAND_DELETE -> intent.getStringExtra(KEY_ID).let { if (!it.isNullOrEmpty()) delete(it, startId) }
|
||||
}
|
||||
|
||||
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.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.floatingsearchview.FloatingSearchView
|
||||
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import xyz.quaver.floatingsearchview.util.view.MenuView
|
||||
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.pupil.*
|
||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||
import xyz.quaver.pupil.adapters.SearchResultsAdapter
|
||||
import xyz.quaver.pupil.databinding.MainActivityBinding
|
||||
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.ui.dialog.DownloadLocationDialogFragment
|
||||
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
||||
import xyz.quaver.pupil.ui.view.MainView
|
||||
import xyz.quaver.pupil.ui.view.ProgressCard
|
||||
import xyz.quaver.pupil.ui.view.ProgressCardView
|
||||
import xyz.quaver.pupil.ui.view.SwipePageTurnView
|
||||
import xyz.quaver.pupil.util.ItemClickSupport
|
||||
import xyz.quaver.pupil.util.Preferences
|
||||
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.restore
|
||||
import java.util.regex.Pattern
|
||||
@@ -69,20 +67,7 @@ class MainActivity :
|
||||
BaseActivity(),
|
||||
NavigationView.OnNavigationItemSelectedListener
|
||||
{
|
||||
|
||||
enum class Mode {
|
||||
SEARCH,
|
||||
HISTORY,
|
||||
DOWNLOAD,
|
||||
FAVORITE
|
||||
}
|
||||
|
||||
enum class SortMode {
|
||||
NEWEST,
|
||||
POPULAR
|
||||
}
|
||||
|
||||
private val galleries = ArrayList<Int>()
|
||||
private val searchResults = mutableListOf<SearchResult>()
|
||||
|
||||
private var query = ""
|
||||
set(value) {
|
||||
@@ -94,13 +79,13 @@ class MainActivity :
|
||||
}
|
||||
private var queryStack = mutableListOf<String>()
|
||||
|
||||
private var mode = Mode.SEARCH
|
||||
private var sortMode = SortMode.NEWEST
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
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 loadingJob: Job? = null
|
||||
private var currentPage = 0
|
||||
private var currentPage = 1
|
||||
|
||||
private lateinit var binding: MainActivityBinding
|
||||
|
||||
@@ -136,35 +121,23 @@ class MainActivity :
|
||||
queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread {
|
||||
query = queryStack.last()
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
query()
|
||||
}
|
||||
else -> super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
(binding.contents.recyclerview.adapter as? GalleryBlockAdapter)?.updateAll = false
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
val perPage = Preferences["per_page", "25"].toInt()
|
||||
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||
|
||||
return when(keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
if (currentPage > 0) {
|
||||
if (currentPage > 1) {
|
||||
runOnUiThread {
|
||||
currentPage--
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
query()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,10 +148,7 @@ class MainActivity :
|
||||
runOnUiThread {
|
||||
currentPage++
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
query()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,18 +199,14 @@ class MainActivity :
|
||||
setTitle(R.string.main_jump_title)
|
||||
setMessage(getString(
|
||||
R.string.main_jump_message,
|
||||
currentPage+1,
|
||||
currentPage,
|
||||
ceil(totalItems / perPage.toDouble()).roundToInt()
|
||||
))
|
||||
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
|
||||
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)
|
||||
|
||||
runOnUiThread {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
loadBlocks()
|
||||
}
|
||||
query()
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
@@ -251,23 +217,18 @@ class MainActivity :
|
||||
setOnClickListener {
|
||||
runBlocking {
|
||||
withTimeoutOrNull(100) {
|
||||
galleryIDs?.await()
|
||||
searchJob?.await()
|
||||
}
|
||||
}.let {
|
||||
if (it?.isEmpty() == false) {
|
||||
val galleryID = it.random()
|
||||
if (it?.first?.isEmpty() == false) {
|
||||
val random = it.first.random()
|
||||
|
||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||
GalleryDialog(this@MainActivity, random.id).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
currentPage = 0
|
||||
query = it.toQuery()
|
||||
currentPage = 1
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
query()
|
||||
dismiss()
|
||||
}
|
||||
}.show()
|
||||
@@ -288,19 +249,14 @@ class MainActivity :
|
||||
setTitle(R.string.main_open_gallery_by_id)
|
||||
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton
|
||||
val galleryID = editText.text.toString()
|
||||
|
||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
currentPage = 0
|
||||
query = it.toQuery()
|
||||
currentPage = 1
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
query()
|
||||
dismiss()
|
||||
}
|
||||
}.show()
|
||||
@@ -309,8 +265,8 @@ class MainActivity :
|
||||
}
|
||||
}
|
||||
|
||||
with(binding.contents.view) {
|
||||
setOnPageTurnListener(object: MainView.OnPageTurnListener {
|
||||
with(binding.contents.swipePageTurnView) {
|
||||
setOnPageTurnListener(object: SwipePageTurnView.OnPageTurnListener {
|
||||
override fun onPrev(page: Int) {
|
||||
currentPage--
|
||||
|
||||
@@ -322,10 +278,7 @@ class MainActivity :
|
||||
.setInterpolator(DecelerateInterpolator())
|
||||
.translationY(0F)
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
query()
|
||||
}
|
||||
|
||||
override fun onNext(page: Int) {
|
||||
@@ -339,95 +292,70 @@ class MainActivity :
|
||||
.setInterpolator(DecelerateInterpolator())
|
||||
.translationY(0F)
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
query()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setupSearchBar()
|
||||
setupRecyclerView()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
query()
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun setupRecyclerView() {
|
||||
with(binding.contents.recyclerview) {
|
||||
adapter = GalleryBlockAdapter(galleries).apply {
|
||||
adapter = SearchResultsAdapter(searchResults).apply {
|
||||
onChipClickedHandler.add {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
currentPage = 0
|
||||
query = it.toQuery()
|
||||
currentPage = 0
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
query()
|
||||
}
|
||||
onDownloadClickedHandler = { position ->
|
||||
val galleryID = galleries[position]
|
||||
|
||||
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress
|
||||
DownloadService.cancel(this@MainActivity, galleryID)
|
||||
onDownloadClickedHandler = { id ->
|
||||
if (DownloadManager.getInstance(context).isDownloading(id)) { //download in progress
|
||||
DownloadService.cancel(this@MainActivity, id)
|
||||
}
|
||||
else {
|
||||
DownloadManager.getInstance(context).addDownloadFolder(galleryID)
|
||||
DownloadService.download(this@MainActivity, galleryID)
|
||||
DownloadManager.getInstance(context).addDownloadFolder(id)
|
||||
DownloadService.download(this@MainActivity, id)
|
||||
}
|
||||
|
||||
closeAllItems()
|
||||
}
|
||||
|
||||
onDeleteClickedHandler = { position ->
|
||||
val galleryID = galleries[position]
|
||||
DownloadService.delete(this@MainActivity, galleryID)
|
||||
onDeleteClickedHandler = { id ->
|
||||
DownloadService.delete(this@MainActivity, id)
|
||||
|
||||
histories.remove(galleryID)
|
||||
|
||||
if (this@MainActivity.mode != Mode.SEARCH)
|
||||
runOnUiThread {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
histories.remove(id)
|
||||
|
||||
closeAllItems()
|
||||
}
|
||||
}
|
||||
ItemClickSupport.addTo(this).apply {
|
||||
onItemClickListener = listener@{ _, position, v ->
|
||||
if (v !is ProgressCard)
|
||||
if (v !is ProgressCardView)
|
||||
return@listener
|
||||
|
||||
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
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
onItemLongClickListener = listener@{ _, position, v ->
|
||||
if (v !is ProgressCard)
|
||||
if (v !is ProgressCardView)
|
||||
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 {
|
||||
runOnUiThread {
|
||||
query = it.toQuery()
|
||||
currentPage = 0
|
||||
query = it.toQuery()
|
||||
currentPage = 0
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
query()
|
||||
dismiss()
|
||||
}
|
||||
}.show()
|
||||
@@ -458,7 +386,7 @@ class MainActivity :
|
||||
with(binding.contents.searchview) {
|
||||
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
|
||||
override fun onMenuOpened() {
|
||||
(binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
||||
(binding.contents.recyclerview.adapter as SearchResultsAdapter).closeAllItems()
|
||||
}
|
||||
|
||||
override fun onMenuClosed() {
|
||||
@@ -532,13 +460,8 @@ class MainActivity :
|
||||
override fun onFocusCleared() {
|
||||
suggestionJob?.cancel()
|
||||
|
||||
runOnUiThread {
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
currentPage = 0
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
}
|
||||
currentPage = 1
|
||||
query()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,43 +473,26 @@ class MainActivity :
|
||||
when(item?.itemId) {
|
||||
R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
|
||||
R.id.main_menu_thin -> {
|
||||
val thin = !item.isChecked
|
||||
|
||||
item.isChecked = thin
|
||||
binding.contents.recyclerview.apply {
|
||||
(adapter as GalleryBlockAdapter).apply {
|
||||
this.thin = thin
|
||||
|
||||
Preferences["thin"] = thin
|
||||
}
|
||||
|
||||
adapter = adapter // Force to redraw
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
R.id.main_menu_sort_newest -> {
|
||||
sortMode = SortMode.NEWEST
|
||||
sortMode = Hitomi.SortMode.NEWEST
|
||||
item.isChecked = true
|
||||
|
||||
runOnUiThread {
|
||||
currentPage = 0
|
||||
currentPage = 1
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
query()
|
||||
}
|
||||
}
|
||||
R.id.main_menu_sort_popular -> {
|
||||
sortMode = SortMode.POPULAR
|
||||
sortMode = Hitomi.SortMode.POPULAR
|
||||
item.isChecked = true
|
||||
|
||||
runOnUiThread {
|
||||
currentPage = 0
|
||||
currentPage = 1
|
||||
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
fetchGalleries(query, sortMode)
|
||||
loadBlocks()
|
||||
query()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,46 +503,6 @@ class MainActivity :
|
||||
binding.drawer.closeDrawers()
|
||||
|
||||
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 -> {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
||||
}
|
||||
@@ -659,161 +525,47 @@ class MainActivity :
|
||||
}
|
||||
|
||||
private fun cancelFetch() {
|
||||
galleryIDs?.cancel()
|
||||
loadingJob?.cancel()
|
||||
searchJob?.cancel()
|
||||
}
|
||||
|
||||
private fun clearGalleries() = CoroutineScope(Dispatchers.Main).launch {
|
||||
galleries.clear()
|
||||
searchResults.clear()
|
||||
|
||||
with(binding.contents.recyclerview.adapter as GalleryBlockAdapter?) {
|
||||
this ?: return@with
|
||||
|
||||
this.notifyDataSetChanged()
|
||||
}
|
||||
binding.contents.recyclerview.adapter?.notifyDataSetChanged()
|
||||
|
||||
binding.contents.noresult.visibility = View.INVISIBLE
|
||||
binding.contents.progressbar.show()
|
||||
}
|
||||
|
||||
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() {
|
||||
private fun query() {
|
||||
val perPage = Preferences["per_page", "25"].toInt()
|
||||
|
||||
loadingJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
val galleryIDs = try {
|
||||
galleryIDs!!.await().also {
|
||||
if (it.isEmpty())
|
||||
throw Exception("No result")
|
||||
cancelFetch()
|
||||
clearGalleries()
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
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")
|
||||
FirebaseCrashlytics.getInstance().recordException(e)
|
||||
binding.contents.progressbar.hide()
|
||||
binding.contents.swipePageTurnView.setCurrentPage(currentPage, totalItems > currentPage*perPage)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
if (results.isEmpty()) {
|
||||
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.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import xyz.quaver.Code
|
||||
import xyz.quaver.pupil.R
|
||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||
import xyz.quaver.pupil.databinding.NumberpickerDialogBinding
|
||||
@@ -65,7 +64,7 @@ import xyz.quaver.pupil.util.startCamera
|
||||
|
||||
class ReaderActivity : BaseActivity() {
|
||||
|
||||
private var galleryID = 0
|
||||
private var galleryID = ""
|
||||
private var currentPage = 0
|
||||
|
||||
private var isScroll = true
|
||||
@@ -81,7 +80,7 @@ class ReaderActivity : BaseActivity() {
|
||||
private val conn = object: ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
downloader = (service as DownloadService.Binder).service.also {
|
||||
it.priority = 0
|
||||
it.priority = ""
|
||||
|
||||
if (!it.progress.containsKey(galleryID))
|
||||
DownloadService.download(this@ReaderActivity, galleryID, true)
|
||||
@@ -130,7 +129,7 @@ class ReaderActivity : BaseActivity() {
|
||||
cache = Cache.getInstance(this, galleryID)
|
||||
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
||||
|
||||
if (galleryID == 0) {
|
||||
if (galleryID.isEmpty()) {
|
||||
onBackPressed()
|
||||
return
|
||||
}
|
||||
@@ -151,14 +150,14 @@ class ReaderActivity : BaseActivity() {
|
||||
if (uri != null && lastPathSegment != null) {
|
||||
galleryID = when (uri.host) {
|
||||
"hitomi.la" ->
|
||||
Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1)?.toIntOrNull() ?: 0
|
||||
"hiyobi.me" -> lastPathSegment.toInt()
|
||||
"e-hentai.org" -> uri.pathSegments[1].toInt()
|
||||
else -> 0
|
||||
Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1) ?: ""
|
||||
"hiyobi.me" -> lastPathSegment
|
||||
"e-hentai.org" -> uri.pathSegments[1]
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
galleryID = intent.getIntExtra("galleryID", 0)
|
||||
galleryID = intent.getStringExtra("galleryID") ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +183,7 @@ class ReaderActivity : BaseActivity() {
|
||||
|
||||
with(binding.numberPicker) {
|
||||
minValue = 1
|
||||
maxValue = cache.metadata.reader?.galleryInfo?.files?.size ?: 0
|
||||
maxValue = cache.metadata.reader?.files?.size ?: 0
|
||||
value = currentPage
|
||||
}
|
||||
val dialog = AlertDialog.Builder(this).apply {
|
||||
@@ -307,17 +306,19 @@ class ReaderActivity : BaseActivity() {
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
title = reader.galleryInfo.title
|
||||
title = reader.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(
|
||||
this@ReaderActivity,
|
||||
R.drawable.hitomi
|
||||
/*
|
||||
when (reader.code) {
|
||||
Code.HITOMI -> R.drawable.hitomi
|
||||
Code.HIYOBI -> R.drawable.ic_hiyobi
|
||||
else -> android.R.color.transparent
|
||||
}
|
||||
}*/
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class DownloadFolderNameDialogFragment : DialogFragment() {
|
||||
}
|
||||
|
||||
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 {
|
||||
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.byteToString
|
||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||
import xyz.quaver.pupil.util.migrate
|
||||
import java.io.File
|
||||
|
||||
class DownloadLocationDialogFragment : DialogFragment() {
|
||||
@@ -181,8 +180,6 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
||||
setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ ->
|
||||
if (Preferences["download_folder", ""].isEmpty())
|
||||
Preferences["download_folder"] = context.getExternalFilesDir(null)?.toUri()?.toString() ?: ""
|
||||
|
||||
DownloadManager.getInstance(requireContext()).migrate()
|
||||
}
|
||||
|
||||
isCancelable = false
|
||||
|
||||
@@ -38,10 +38,12 @@ import kotlinx.coroutines.withContext
|
||||
import xyz.quaver.hitomi.Gallery
|
||||
import xyz.quaver.hitomi.getGallery
|
||||
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.databinding.*
|
||||
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.ui.ReaderActivity
|
||||
import xyz.quaver.pupil.ui.view.TagChip
|
||||
@@ -51,7 +53,7 @@ import xyz.quaver.pupil.util.wordCapitalize
|
||||
import java.util.*
|
||||
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))>()
|
||||
|
||||
@@ -80,7 +82,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val gallery = getGallery(galleryID)
|
||||
val gallery = getGallery(galleryID.toInt())
|
||||
|
||||
launch (Dispatchers.Main) {
|
||||
binding.progressbar.visibility = View.GONE
|
||||
@@ -203,9 +205,9 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
|
||||
}
|
||||
|
||||
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 ->
|
||||
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||
handler.invoke(tag)
|
||||
@@ -223,11 +225,11 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(
|
||||
ItemClickSupport.addTo(this).apply {
|
||||
onItemClickListener = { _, position, _ ->
|
||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryID", galleries[position])
|
||||
putExtra("galleryID", galleries[position].id)
|
||||
})
|
||||
}
|
||||
onItemLongClickListener = { _, position, _ ->
|
||||
GalleryDialog(context, galleries[position]).apply {
|
||||
GalleryDialog(context, galleries[position].id).apply {
|
||||
onChipClickedHandler.add { 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 {
|
||||
gallery.related.forEach { galleryID ->
|
||||
Cache.getInstance(context, galleryID).getGalleryBlock()?.let {
|
||||
galleries.add(galleryID)
|
||||
Cache.getInstance(context, galleryID.toString()).getGalleryBlock()?.let {
|
||||
galleries.add(
|
||||
Hitomi.SearchResult(
|
||||
it.id.toString(),
|
||||
it.title,
|
||||
it.thumbnails.first(),
|
||||
it.artists
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
adapter.notifyDataSetChanged()
|
||||
withContext(Dispatchers.Main) {
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import androidx.core.graphics.drawable.DrawableCompat
|
||||
import xyz.quaver.pupil.R
|
||||
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 {
|
||||
LOADING,
|
||||
@@ -35,7 +35,6 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.NestedScrollingChild;
|
||||
@@ -48,7 +47,7 @@ import androidx.core.widget.TextViewCompat;
|
||||
import xyz.quaver.pupil.R;
|
||||
|
||||
@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_ANIM_DURATION = 500;
|
||||
@@ -84,15 +83,15 @@ public class MainView extends ViewGroup implements NestedScrollingChild, NestedS
|
||||
|
||||
private OnPageTurnListener mOnPageTurnListener;
|
||||
|
||||
public MainView(@NonNull Context context) {
|
||||
public SwipePageTurnView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public MainView(@NonNull Context context, AttributeSet attr) {
|
||||
public SwipePageTurnView(@NonNull Context context, AttributeSet attr) {
|
||||
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);
|
||||
|
||||
setWillNotDraw(false);
|
||||
@@ -32,9 +32,8 @@ import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Request
|
||||
import xyz.quaver.Code
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.GalleryInfo
|
||||
import xyz.quaver.io.FileX
|
||||
import xyz.quaver.io.util.*
|
||||
import xyz.quaver.pupil.client
|
||||
@@ -46,24 +45,24 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
@Serializable
|
||||
data class Metadata(
|
||||
var galleryBlock: GalleryBlock? = null,
|
||||
var reader: Reader? = null,
|
||||
var reader: GalleryInfo? = null,
|
||||
var imageList: MutableList<String?>? = null
|
||||
) {
|
||||
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 {
|
||||
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] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
||||
instances[galleryID] ?: Cache(context, galleryID).also { instances[galleryID] = it }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun delete(context: Context, galleryID: Int) {
|
||||
fun delete(context: Context, galleryID: String) {
|
||||
File(context.cacheDir, "imageCache/$galleryID").deleteRecursively()
|
||||
instances.remove(galleryID)
|
||||
}
|
||||
@@ -111,8 +110,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
|
||||
suspend fun getGalleryBlock(): GalleryBlock? {
|
||||
val sources = listOf(
|
||||
{ xyz.quaver.hitomi.getGalleryBlock(galleryID) },
|
||||
{ xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
|
||||
{ xyz.quaver.hitomi.getGalleryBlock(galleryID.toInt()) }
|
||||
// { xyz.quaver.hiyobi.getGalleryBlock(galleryID) }
|
||||
)
|
||||
|
||||
return metadata.galleryBlock
|
||||
@@ -154,22 +153,17 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
}.getOrNull()?.uri }
|
||||
} } ?: 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 sources = mapOf(
|
||||
Code.HITOMI to { xyz.quaver.hitomi.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
|
||||
}
|
||||
"hitomi" to { xyz.quaver.hitomi.getGalleryInfo(galleryID.toInt()) },
|
||||
//Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) }
|
||||
)
|
||||
|
||||
return metadata.reader
|
||||
?: withContext(Dispatchers.IO) {
|
||||
var reader: Reader? = null
|
||||
var reader: GalleryInfo? = null
|
||||
|
||||
for (source in sources) {
|
||||
reader = try {
|
||||
@@ -187,7 +181,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
||||
metadata.reader = it
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
private val lock = ConcurrentHashMap<Int, Mutex>()
|
||||
private val lock = ConcurrentHashMap<String, Mutex>()
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch {
|
||||
val downloadFolder = downloadFolder ?: return@launch
|
||||
|
||||
@@ -57,8 +57,8 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
||||
}.invoke()
|
||||
|
||||
private var prevDownloadFolder: FileX? = null
|
||||
private var downloadFolderMapInstance: MutableMap<Int, String>? = null
|
||||
val downloadFolderMap: MutableMap<Int, String>
|
||||
private var downloadFolderMapInstance: MutableMap<String, String>? = null
|
||||
val downloadFolderMap: MutableMap<String, String>
|
||||
@Synchronized
|
||||
get() {
|
||||
if (prevDownloadFolder != downloadFolder) {
|
||||
@@ -68,14 +68,14 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
||||
|
||||
val data = if (file.exists())
|
||||
kotlin.runCatching {
|
||||
file.readText()?.let { Json.decodeFromString<MutableMap<Int, String>>(it) }
|
||||
file.readText()?.let { Json.decodeFromString<MutableMap<String, String>>(it) }
|
||||
}.onFailure { file.delete() }.getOrNull()
|
||||
else
|
||||
null
|
||||
|
||||
data ?: {
|
||||
file.createNewFile()
|
||||
mutableMapOf<Int, String>()
|
||||
mutableMapOf<String, String>()
|
||||
}.invoke()
|
||||
}.invoke()
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
||||
|
||||
|
||||
@Synchronized
|
||||
fun isDownloading(galleryID: Int): Boolean {
|
||||
fun isDownloading(galleryID: String): Boolean {
|
||||
val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID }
|
||||
|
||||
return downloadFolderMap.containsKey(galleryID)
|
||||
@@ -93,11 +93,11 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getDownloadFolder(galleryID: Int): FileX? =
|
||||
fun getDownloadFolder(galleryID: String): FileX? =
|
||||
downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) }
|
||||
|
||||
@Synchronized
|
||||
fun addDownloadFolder(galleryID: Int) {
|
||||
fun addDownloadFolder(galleryID: String) {
|
||||
val name = runBlocking {
|
||||
Cache.getInstance(this@DownloadManager, galleryID).getGalleryBlock()
|
||||
}?.formatDownloadFolder() ?: return
|
||||
@@ -116,7 +116,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun deleteDownloadFolder(galleryID: Int) {
|
||||
fun deleteDownloadFolder(galleryID: String) {
|
||||
downloadFolderMap[galleryID]?.let {
|
||||
kotlin.runCatching {
|
||||
downloadFolder.getChild(it).deleteRecursively()
|
||||
|
||||
@@ -26,9 +26,8 @@ import androidx.core.content.ContextCompat
|
||||
import kotlinx.serialization.json.*
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import xyz.quaver.Code
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.GalleryInfo
|
||||
import xyz.quaver.hitomi.getReferer
|
||||
import xyz.quaver.hitomi.imageUrlFromImage
|
||||
import xyz.quaver.hiyobi.createImgList
|
||||
@@ -103,11 +102,17 @@ fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
||||
}
|
||||
}.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127)
|
||||
|
||||
val Reader.requestBuilders: List<Request.Builder>
|
||||
val GalleryInfo.requestBuilders: List<Request.Builder>
|
||||
get() {
|
||||
val galleryID = this.galleryInfo.id ?: 0
|
||||
val galleryID = this.id ?: 0
|
||||
val lowQuality = Preferences["low_quality", true]
|
||||
|
||||
return this.files.map {
|
||||
Request.Builder()
|
||||
.url(imageUrlFromImage(galleryID, it, !lowQuality))
|
||||
.header("Referer", getReferer(galleryID))
|
||||
}
|
||||
/*
|
||||
return when(code) {
|
||||
Code.HITOMI -> {
|
||||
this.galleryInfo.files.map {
|
||||
@@ -122,9 +127,8 @@ val Reader.requestBuilders: List<Request.Builder>
|
||||
.url(it.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
fun String.ellipsize(n: Int): String =
|
||||
if (this.length > n)
|
||||
this.slice(0 until n) + "…"
|
||||
|
||||
@@ -46,7 +46,6 @@ import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import ru.noties.markwon.Markwon
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.getGalleryBlock
|
||||
import xyz.quaver.hitomi.getReader
|
||||
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)) {
|
||||
onFailure?.invoke(IllegalArgumentException())
|
||||
return
|
||||
@@ -214,7 +213,7 @@ fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
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)
|
||||
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"
|
||||
tools:context=".ui.MainActivity">
|
||||
|
||||
<xyz.quaver.pupil.ui.view.MainView
|
||||
android:id="@+id/view"
|
||||
<xyz.quaver.pupil.ui.view.SwipePageTurnView
|
||||
android:id="@+id/swipe_page_turn_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||
|
||||
</xyz.quaver.pupil.ui.view.MainView>
|
||||
</xyz.quaver.pupil.ui.view.SwipePageTurnView>
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
style="?android:attr/progressBarStyle"
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
~ 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:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/galleryblock_card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="true"
|
||||
@@ -36,12 +35,13 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.github.piasy.biv.view.BigImageView
|
||||
android:id="@+id/galleryblock_thumbnail"
|
||||
android:id="@+id/thumbnail"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/galleryblock_thumbnail_description"
|
||||
android:adjustViewBounds="true"
|
||||
android:clickable="false"
|
||||
android:duplicateParentState="true"
|
||||
app:layout_constraintHeight_default="spread"
|
||||
app:layout_constraintHeight_min="200dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
@@ -50,61 +50,61 @@
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.AppCompat.Headline"
|
||||
android:id="@+id/galleryblock_title"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="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_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.AppCompat.Medium"
|
||||
android:id="@+id/galleryblock_artist"
|
||||
android:id="@+id/artist"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||
app:layout_constraintLeft_toRightOf="@id/thumbnail"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_title" />
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/galleryblock_series"
|
||||
android:id="@+id/series"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
|
||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||
app:layout_constraintTop_toBottomOf="@id/artist"
|
||||
app:layout_constraintLeft_toRightOf="@id/thumbnail"
|
||||
app:layout_constraintRight_toRightOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/galleryblock_type"
|
||||
android:id="@+id/type"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
|
||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" />
|
||||
app:layout_constraintTop_toBottomOf="@id/series"
|
||||
app:layout_constraintLeft_toRightOf="@id/thumbnail" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/galleryblock_language"
|
||||
android:id="@+id/language"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
|
||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" />
|
||||
app:layout_constraintTop_toBottomOf="@id/type"
|
||||
app:layout_constraintLeft_toRightOf="@id/thumbnail" />
|
||||
|
||||
<xyz.quaver.pupil.ui.view.TagChipGroup
|
||||
android:id="@+id/galleryblock_tag_group"
|
||||
android:id="@+id/tag_group"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:chipSpacing="4dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
|
||||
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
|
||||
app:layout_constraintTop_toBottomOf="@id/language"
|
||||
app:layout_constraintLeft_toRightOf="@id/thumbnail"
|
||||
app:layout_constraintRight_toRightOf="parent"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
@@ -112,7 +112,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="galleryblock_thumbnail, galleryblock_tag_group"/>
|
||||
app:constraint_referenced_ids="thumbnail, tag_group"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
@@ -123,7 +123,7 @@
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/galleryblock_id"
|
||||
android:id="@+id/id_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
@@ -132,7 +132,7 @@
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/galleryblock_pagecount"
|
||||
android:id="@+id/pagecount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
@@ -143,7 +143,7 @@
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/galleryblock_favorite"
|
||||
android:id="@+id/favorite"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
@@ -157,4 +157,4 @@
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</xyz.quaver.pupil.ui.view.ProgressCard>
|
||||
</xyz.quaver.pupil.ui.view.ProgressCardView>
|
||||
Reference in New Issue
Block a user