Gallery Dialog
This commit is contained in:
@@ -92,19 +92,19 @@ dependencies {
|
||||
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.activity:activity-ktx:1.2.0-rc01"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.0-rc01"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.0-rc02"
|
||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||
implementation "androidx.biometric:biometric:1.0.1"
|
||||
implementation "androidx.work:work-runtime-ktx:2.4.0"
|
||||
implementation "androidx.biometric:biometric:1.1.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.5.0"
|
||||
|
||||
implementation 'org.kodein.di:kodein-di-framework-android-x:7.1.0'
|
||||
|
||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||
|
||||
implementation "com.google.android.material:material:1.3.0-beta01"
|
||||
implementation "com.google.android.material:material:1.3.0-rc01"
|
||||
|
||||
implementation platform("com.google.firebase:firebase-bom:26.1.0")
|
||||
implementation "com.google.firebase:firebase-analytics-ktx"
|
||||
@@ -138,7 +138,9 @@ dependencies {
|
||||
implementation "xyz.quaver:documentfilex:0.4-alpha02"
|
||||
implementation "xyz.quaver:floatingsearchview:1.1.1"
|
||||
|
||||
// debugImplementation"com.squareup.leakcanary:leakcanary-android:2.6"
|
||||
implementation "com.orhanobut:logger:2.2.0"
|
||||
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.6"
|
||||
|
||||
testImplementation "junit:junit:4.13.1"
|
||||
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
||||
|
||||
@@ -38,6 +38,8 @@ import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.orhanobut.logger.AndroidLogAdapter
|
||||
import com.orhanobut.logger.Logger
|
||||
import okhttp3.*
|
||||
import org.kodein.di.*
|
||||
import org.kodein.di.android.x.androidXModule
|
||||
@@ -96,6 +98,8 @@ class Pupil : Application(), DIAware {
|
||||
firebaseAnalytics = Firebase.analytics
|
||||
FirebaseCrashlytics.getInstance().setUserId(userID)
|
||||
|
||||
Logger.addLogAdapter(AndroidLogAdapter())
|
||||
|
||||
val proxyInfo = getProxyInfo()
|
||||
|
||||
clientBuilder = OkHttpClient.Builder()
|
||||
|
||||
@@ -18,29 +18,40 @@
|
||||
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.piasy.biv.view.BigImageView
|
||||
import com.facebook.drawee.drawable.ScalingUtils
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import xyz.quaver.pupil.R
|
||||
|
||||
class ThumbnailAdapter(var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
||||
|
||||
class ViewHolder(val view: BigImageView) : RecyclerView.ViewHolder(view) {
|
||||
class ViewHolder(val view: SimpleDraweeView) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
init {
|
||||
view.hierarchy.actualImageScaleType = ScalingUtils.ScaleType.FIT_CENTER
|
||||
}
|
||||
|
||||
fun bind(image: String) {
|
||||
view.setImageURI(image)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
view.ssiv?.recycle()
|
||||
view.controller = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(BigImageView(parent.context).apply {
|
||||
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
||||
return ViewHolder(SimpleDraweeView(parent.context).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
resources.getDimensionPixelSize(R.dimen.gallery_dialog_preview_height)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.view.showImage(Uri.parse(thumbnails[position]))
|
||||
holder.bind(thumbnails[position])
|
||||
}
|
||||
|
||||
override fun getItemCount() = thumbnails.size
|
||||
|
||||
@@ -29,11 +29,8 @@ class ThumbnailPageAdapter(private val thumbnails: List<String>) : RecyclerView.
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(RecyclerView(parent.context).apply {
|
||||
val layoutManager = GridLayoutManager(parent.context, 3)
|
||||
val adapter = ThumbnailAdapter(listOf())
|
||||
|
||||
this.layoutManager = layoutManager
|
||||
this.adapter = adapter
|
||||
this.layoutManager = GridLayoutManager(parent.context, 3)
|
||||
this.adapter = ThumbnailAdapter(listOf())
|
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
})
|
||||
}
|
||||
@@ -42,8 +39,6 @@ class ThumbnailPageAdapter(private val thumbnails: List<String>) : RecyclerView.
|
||||
(holder.view.adapter as ThumbnailAdapter).apply {
|
||||
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
|
||||
notifyDataSetChanged()
|
||||
|
||||
(holder.view.layoutManager as GridLayoutManager).scrollToPosition(8)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,11 @@ data class ItemInfo(
|
||||
}
|
||||
}
|
||||
|
||||
val isReady: Boolean
|
||||
get() = extra.all { it.value.isCompleted }
|
||||
|
||||
suspend fun awaitAll() = extra.values.awaitAll()
|
||||
|
||||
companion object {
|
||||
val extraTypeMap = mapOf(
|
||||
ExtraType.SERIES to R.string.galleryblock_series,
|
||||
|
||||
@@ -120,7 +120,7 @@ class Hitomi : Source<Hitomi.SortMode, Hitomi.TagSuggestion>() {
|
||||
mapOf(
|
||||
ExtraType.TYPE to async { it.type.wordCapitalize() },
|
||||
ExtraType.GROUP to async { it.groups.joinToString { it.wordCapitalize() } },
|
||||
ExtraType.LANGUAGE to async { languageMap[it.language] ?: it.language },
|
||||
ExtraType.LANGUAGE to async { it.language },
|
||||
ExtraType.SERIES to async { it.series.joinToString { it.wordCapitalize() } },
|
||||
ExtraType.CHARACTER to async { it.characters.joinToString { it.wordCapitalize() } },
|
||||
ExtraType.TAGS to async { it.tags.joinToString() },
|
||||
@@ -227,7 +227,7 @@ class Hitomi : Source<Hitomi.SortMode, Hitomi.TagSuggestion>() {
|
||||
}.getOrDefault("") },
|
||||
ExtraType.SERIES to CoroutineScope(Dispatchers.Unconfined).async { galleryBlock.series.joinToString { it.wordCapitalize() } },
|
||||
ExtraType.TYPE to CoroutineScope(Dispatchers.Unconfined).async { galleryBlock.type.wordCapitalize() },
|
||||
ExtraType.LANGUAGE to CoroutineScope(Dispatchers.Unconfined).async { languageMap[galleryBlock.language] ?: galleryBlock.language },
|
||||
ExtraType.LANGUAGE to CoroutineScope(Dispatchers.Unconfined).async { galleryBlock.language },
|
||||
ExtraType.PAGECOUNT to CoroutineScope(Dispatchers.IO).async { kotlin.runCatching {
|
||||
getGalleryInfo(galleryBlock.id).files.size.toString()
|
||||
}.getOrNull() },
|
||||
|
||||
@@ -56,7 +56,7 @@ import xyz.quaver.pupil.sources.sourceIcons
|
||||
import xyz.quaver.pupil.sources.sources
|
||||
import xyz.quaver.pupil.types.*
|
||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
||||
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
||||
import xyz.quaver.pupil.ui.dialog.GalleryDialogFragment
|
||||
import xyz.quaver.pupil.ui.dialog.SourceSelectDialog
|
||||
import xyz.quaver.pupil.ui.view.ProgressCardView
|
||||
import xyz.quaver.pupil.ui.view.SwipePageTurnView
|
||||
@@ -264,7 +264,7 @@ class MainActivity :
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
setImageResource(R.drawable.shuffle_variant)
|
||||
GalleryDialog(this@MainActivity, randomResult.id).apply {
|
||||
GalleryDialogFragment(source.name, randomResult.id).apply {
|
||||
onChipClickedHandler.add {
|
||||
query = it.toQuery()
|
||||
currentPage = 1
|
||||
@@ -272,7 +272,7 @@ class MainActivity :
|
||||
query()
|
||||
dismiss()
|
||||
}
|
||||
}.show()
|
||||
}.show(supportFragmentManager, "GalleryDialogFragment")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,7 +292,7 @@ class MainActivity :
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val galleryID = editText.text.toString()
|
||||
|
||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
||||
GalleryDialogFragment(source.name, galleryID).apply {
|
||||
onChipClickedHandler.add {
|
||||
query = it.toQuery()
|
||||
currentPage = 1
|
||||
@@ -300,7 +300,7 @@ class MainActivity :
|
||||
query()
|
||||
dismiss()
|
||||
}
|
||||
}.show()
|
||||
}.show(supportFragmentManager, "GalleryDialogFragment")
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
@@ -380,7 +380,7 @@ class MainActivity :
|
||||
|
||||
val result = searchResults.getOrNull(position) ?: return@listener true
|
||||
|
||||
GalleryDialog(this@MainActivity, result.id).apply {
|
||||
GalleryDialogFragment(source.name, result.id).apply {
|
||||
onChipClickedHandler.add {
|
||||
query = it.toQuery()
|
||||
currentPage = 1
|
||||
@@ -388,7 +388,7 @@ class MainActivity :
|
||||
query()
|
||||
dismiss()
|
||||
}
|
||||
}.show()
|
||||
}.show(supportFragmentManager, "GalleryDialogFragment")
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* 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.ui.dialog
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout.LayoutParams
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import xyz.quaver.hitomi.Gallery
|
||||
import xyz.quaver.hitomi.getGallery
|
||||
import xyz.quaver.pupil.R
|
||||
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.ItemInfo
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.ui.ReaderActivity
|
||||
import xyz.quaver.pupil.ui.view.TagChip
|
||||
import xyz.quaver.pupil.util.ItemClickSupport
|
||||
import xyz.quaver.pupil.util.wordCapitalize
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class GalleryDialog(context: Context, private val galleryID: String) : AlertDialog(context) {
|
||||
|
||||
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
||||
|
||||
private lateinit var binding: GalleryDialogBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = GalleryDialogBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
|
||||
with (binding.fab) {
|
||||
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right))
|
||||
setOnClickListener {
|
||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryID", galleryID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val gallery = getGallery(galleryID.toInt())
|
||||
|
||||
launch (Dispatchers.Main) {
|
||||
binding.progressbar.visibility = View.GONE
|
||||
binding.title.text = gallery.title
|
||||
binding.artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
|
||||
|
||||
with (binding.type) {
|
||||
text = gallery.type.wordCapitalize()
|
||||
setOnClickListener {
|
||||
gallery.type.let {
|
||||
when (it) {
|
||||
"artist CG" -> "artistcg"
|
||||
"game CG" -> "gamecg"
|
||||
else -> it
|
||||
}
|
||||
}.let {
|
||||
onChipClickedHandler.forEach { handler ->
|
||||
handler.invoke(Tag("type", it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.cover.showImage(Uri.parse(gallery.cover))
|
||||
|
||||
addDetails(gallery)
|
||||
addThumbnails(gallery)
|
||||
addRelated(gallery)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Snackbar.make(binding.root, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).apply {
|
||||
if (Locale.getDefault().language == "ko")
|
||||
setAction(context.getText(R.string.https_text)) {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.https))))
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addDetails(gallery: Gallery) {
|
||||
GalleryDialogDetailsBinding.inflate(layoutInflater, binding.contents, true).apply {
|
||||
type.setText(R.string.gallery_details)
|
||||
|
||||
listOf(
|
||||
R.string.gallery_artists,
|
||||
R.string.gallery_groups,
|
||||
R.string.gallery_language,
|
||||
R.string.gallery_series,
|
||||
R.string.gallery_characters,
|
||||
R.string.gallery_tags
|
||||
).zip(
|
||||
listOf(
|
||||
gallery.artists.map { Tag("artist", it) },
|
||||
gallery.groups.map { Tag("group", it) },
|
||||
listOf(gallery.language).map { Tag("language", it) },
|
||||
gallery.series.map { Tag("series", it) },
|
||||
gallery.characters.map { Tag("character", it) },
|
||||
gallery.tags.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).let { tag ->
|
||||
when {
|
||||
tag.area != null -> tag
|
||||
else -> Tag("tag", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
).filter {
|
||||
(_, content) -> content.isNotEmpty()
|
||||
}.forEach { (title, content) ->
|
||||
GalleryDialogTagsBinding.inflate(layoutInflater, contents, true).apply {
|
||||
type.setText(title)
|
||||
|
||||
content.forEach { tag ->
|
||||
tags.addView(
|
||||
TagChip(context, tag).apply {
|
||||
setOnClickListener {
|
||||
onChipClickedHandler.forEach { handler ->
|
||||
handler.invoke(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addThumbnails(gallery: Gallery) {
|
||||
GalleryDialogDetailsBinding.inflate(layoutInflater, binding.contents, true).apply {
|
||||
type.setText(R.string.gallery_thumbnails)
|
||||
|
||||
val pager = ViewPager2(context).apply {
|
||||
adapter = ThumbnailPageAdapter(gallery.thumbnails)
|
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
contents.addView(
|
||||
pager,
|
||||
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
)
|
||||
|
||||
// TODO: Change to direct allocation
|
||||
GalleryDialogDotindicatorBinding.inflate(layoutInflater, contents, true).apply {
|
||||
dotindicator.setViewPager2(pager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addRelated(gallery: Gallery) {
|
||||
val galleries = mutableListOf<ItemInfo>()
|
||||
|
||||
val adapter = SearchResultsAdapter(galleries).apply {
|
||||
onChipClickedHandler = { tag ->
|
||||
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
|
||||
handler.invoke(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GalleryDialogDetailsBinding.inflate(layoutInflater, binding.contents, true).apply {
|
||||
type.setText(R.string.gallery_related)
|
||||
|
||||
contents.addView(RecyclerView(context).apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
this.adapter = adapter
|
||||
|
||||
ItemClickSupport.addTo(this).apply {
|
||||
onItemClickListener = { _, position, _ ->
|
||||
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
|
||||
putExtra("galleryID", galleries[position].id)
|
||||
})
|
||||
}
|
||||
onItemLongClickListener = { _, position, _ ->
|
||||
GalleryDialog(context, galleries[position].id).apply {
|
||||
onChipClickedHandler.add { tag ->
|
||||
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||
}
|
||||
}.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
gallery.related.forEach { galleryID ->
|
||||
withContext(Dispatchers.Main) {
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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.ui.dialog
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout.LayoutParams
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.forEach
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.facebook.drawee.backends.pipeline.Fresco
|
||||
import com.facebook.drawee.controller.BaseControllerListener
|
||||
import com.facebook.imagepipeline.image.ImageInfo
|
||||
import com.orhanobut.logger.Logger
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import xyz.quaver.pupil.R
|
||||
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.ItemInfo
|
||||
import xyz.quaver.pupil.types.Tag
|
||||
import xyz.quaver.pupil.ui.ReaderActivity
|
||||
import xyz.quaver.pupil.ui.view.TagChip
|
||||
import xyz.quaver.pupil.ui.viewmodel.GalleryDialogViewModel
|
||||
import xyz.quaver.pupil.util.ItemClickSupport
|
||||
import xyz.quaver.pupil.util.wordCapitalize
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class GalleryDialogFragment(private val source: String, private val itemID: String) : DialogFragment() {
|
||||
|
||||
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
|
||||
|
||||
private var _binding: GalleryDialogBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val viewModel: GalleryDialogViewModel by viewModels()
|
||||
|
||||
private val controllerListener = object: BaseControllerListener<ImageInfo>() {
|
||||
override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
|
||||
imageInfo?.let {
|
||||
binding.cover.aspectRatio = it.width / it.height.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
|
||||
imageInfo?.let {
|
||||
binding.cover.aspectRatio = it.width / it.height.toFloat()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
_binding = GalleryDialogBinding.inflate(layoutInflater)
|
||||
|
||||
with (binding.fab) {
|
||||
setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.arrow_right))
|
||||
setOnClickListener {
|
||||
context?.startActivity(Intent(requireContext(), ReaderActivity::class.java).apply {
|
||||
putExtra("source", source)
|
||||
putExtra("id", itemID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val lilMutex = Mutex(true)
|
||||
viewModel.info.observe(this) {
|
||||
binding.progressbar.visibility = View.GONE
|
||||
binding.title.text = it.title
|
||||
binding.artist.text = it.artists
|
||||
|
||||
binding.cover.controller = Fresco.newDraweeControllerBuilder()
|
||||
.setUri(it.thumbnail)
|
||||
.setControllerListener(controllerListener)
|
||||
.setOldController(binding.cover.controller)
|
||||
.build()
|
||||
|
||||
MainScope().launch {
|
||||
binding.type.text = it.extra[ItemInfo.ExtraType.TYPE]?.await()?.wordCapitalize()
|
||||
addDetails(it)
|
||||
addPreviews(it)
|
||||
|
||||
lilMutex.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.related.observe(this) {
|
||||
if (it != null) {
|
||||
MainScope().launch {
|
||||
lilMutex.withLock {
|
||||
addRelated(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.load(source, itemID)
|
||||
|
||||
return AlertDialog.Builder(requireContext())
|
||||
.setView(binding.root)
|
||||
.create()
|
||||
}
|
||||
|
||||
private suspend fun addDetails(info: ItemInfo) {
|
||||
GalleryDialogDetailsBinding.inflate(layoutInflater, binding.contents, true).apply {
|
||||
type.setText(R.string.gallery_details)
|
||||
|
||||
listOf(
|
||||
R.string.gallery_artists,
|
||||
R.string.gallery_groups,
|
||||
R.string.gallery_language,
|
||||
R.string.gallery_series,
|
||||
R.string.gallery_characters,
|
||||
R.string.gallery_tags
|
||||
).zip(
|
||||
listOf(
|
||||
info.artists.split(", ").map { Tag("artist", it) },
|
||||
info.extra[ItemInfo.ExtraType.GROUP]?.await()?.split(", ")?.filterNot { it.isEmpty() }?.map { Tag("group", it) },
|
||||
info.extra[ItemInfo.ExtraType.LANGUAGE]?.await()?.split(", ")?.filterNot { it.isEmpty() }?.map { Tag("language", it) },
|
||||
info.extra[ItemInfo.ExtraType.SERIES]?.await()?.split(", ")?.filterNot { it.isEmpty() }?.map { Tag("series", it) },
|
||||
info.extra[ItemInfo.ExtraType.CHARACTER]?.await()?.split(", ")?.filterNot { it.isEmpty() }?.map { Tag("character", it) },
|
||||
info.extra[ItemInfo.ExtraType.TAGS]?.await()?.split(", ")?.filterNot { it.isEmpty() }?.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).let { tag ->
|
||||
when {
|
||||
tag.area != null -> tag
|
||||
else -> Tag("tag", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
).filterNot { (_, content) ->
|
||||
content.isNullOrEmpty()
|
||||
}.forEach { (title, content) ->
|
||||
GalleryDialogTagsBinding.inflate(layoutInflater, contents, true).apply {
|
||||
type.setText(title)
|
||||
|
||||
content!!.forEach { tag ->
|
||||
tags.addView(
|
||||
TagChip(requireContext(), tag).apply {
|
||||
setOnClickListener {
|
||||
onChipClickedHandler.forEach { handler ->
|
||||
handler.invoke(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun addPreviews(info: ItemInfo) {
|
||||
val previews = info.extra[ItemInfo.ExtraType.PREVIEW]?.await()?.split(", ") ?: return
|
||||
|
||||
GalleryDialogDetailsBinding.inflate(layoutInflater, binding.contents, true).apply {
|
||||
type.setText(R.string.gallery_thumbnails)
|
||||
|
||||
val pager = ViewPager2(requireContext()).apply {
|
||||
adapter = ThumbnailPageAdapter(previews)
|
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
contents.addView(pager)
|
||||
|
||||
// TODO: Change to direct allocation
|
||||
GalleryDialogDotindicatorBinding.inflate(layoutInflater, contents, true).apply {
|
||||
dotindicator.setViewPager2(pager)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addRelated(relatedItems: List<ItemInfo>) {
|
||||
val adapter = SearchResultsAdapter(relatedItems).apply {
|
||||
onChipClickedHandler = { tag ->
|
||||
this@GalleryDialogFragment.onChipClickedHandler.forEach { handler ->
|
||||
handler.invoke(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GalleryDialogDetailsBinding.inflate(layoutInflater, binding.contents, true).apply {
|
||||
type.setText(R.string.gallery_related)
|
||||
|
||||
contents.addView(RecyclerView(requireContext()).apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
this.adapter = adapter
|
||||
|
||||
ItemClickSupport.addTo(this).apply {
|
||||
onItemClickListener = { _, position, _ ->
|
||||
requireContext().startActivity(Intent(requireContext(), ReaderActivity::class.java).apply {
|
||||
putExtra("source", source)
|
||||
putExtra("id", relatedItems[position].id)
|
||||
})
|
||||
}
|
||||
onItemLongClickListener = { _, position, _ ->
|
||||
GalleryDialogFragment(source, relatedItems[position].id).apply {
|
||||
onChipClickedHandler.add { tag ->
|
||||
this@GalleryDialogFragment.onChipClickedHandler.forEach { it.invoke(tag) }
|
||||
}
|
||||
}.show(parentFragmentManager, "")
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding.contents.forEach { if (it is RecyclerView) ItemClickSupport.removeFrom(it) }
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Pupil, Hitomi.la viewer for Android
|
||||
* Copyright (C) 2021 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.ui.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.*
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.x.di
|
||||
import org.kodein.di.instance
|
||||
import xyz.quaver.pupil.sources.AnySource
|
||||
import xyz.quaver.pupil.sources.ItemInfo
|
||||
|
||||
class GalleryDialogViewModel(app: Application) : AndroidViewModel(app), DIAware {
|
||||
|
||||
override val di by di()
|
||||
|
||||
private val _info = MutableLiveData<ItemInfo>()
|
||||
val info: LiveData<ItemInfo> = _info
|
||||
|
||||
private val _related = MutableLiveData<List<ItemInfo>>()
|
||||
val related: LiveData<List<ItemInfo>> = _related
|
||||
|
||||
fun load(source: String, itemID: String) {
|
||||
val source: AnySource by instance(tag = source)
|
||||
|
||||
viewModelScope.launch {
|
||||
_info.value = withContext(Dispatchers.IO) {
|
||||
source.info(itemID).also { it.awaitAll() }
|
||||
}.also {
|
||||
_related.value = it.extra[ItemInfo.ExtraType.RELATED_ITEM]?.await()?.split(", ")?.map { related ->
|
||||
async(Dispatchers.IO) {
|
||||
source.info(related)
|
||||
}
|
||||
}?.awaitAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,7 +39,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp">
|
||||
|
||||
<com.github.piasy.biv.view.BigImageView
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@+id/cover"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -106,20 +106,11 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressbar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
|
||||
@@ -12,4 +12,6 @@
|
||||
<dimen name="thumb_height">72dp</dimen>
|
||||
|
||||
<dimen name="thumbnail_page_height">300dp</dimen>
|
||||
|
||||
<dimen name="gallery_dialog_preview_height">150dp</dimen>
|
||||
</resources>
|
||||
@@ -6,7 +6,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||
classpath 'com.android.tools.build:gradle:4.1.2'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
|
||||
Reference in New Issue
Block a user