Gallery Dialog
This commit is contained in:
@@ -92,19 +92,19 @@ dependencies {
|
|||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||||
implementation "androidx.activity:activity-ktx:1.2.0-rc01"
|
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.preference:preference-ktx:1.1.1"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
implementation "androidx.recyclerview:recyclerview:1.1.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||||
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
implementation "androidx.gridlayout:gridlayout:1.0.0"
|
||||||
implementation "androidx.biometric:biometric:1.0.1"
|
implementation "androidx.biometric:biometric:1.1.0"
|
||||||
implementation "androidx.work:work-runtime-ktx:2.4.0"
|
implementation "androidx.work:work-runtime-ktx:2.5.0"
|
||||||
|
|
||||||
implementation 'org.kodein.di:kodein-di-framework-android-x:7.1.0'
|
implementation 'org.kodein.di:kodein-di-framework-android-x:7.1.0'
|
||||||
|
|
||||||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
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 platform("com.google.firebase:firebase-bom:26.1.0")
|
||||||
implementation "com.google.firebase:firebase-analytics-ktx"
|
implementation "com.google.firebase:firebase-analytics-ktx"
|
||||||
@@ -138,7 +138,9 @@ dependencies {
|
|||||||
implementation "xyz.quaver:documentfilex:0.4-alpha02"
|
implementation "xyz.quaver:documentfilex:0.4-alpha02"
|
||||||
implementation "xyz.quaver:floatingsearchview:1.1.1"
|
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"
|
testImplementation "junit:junit:4.13.1"
|
||||||
androidTestImplementation "androidx.test.ext:junit:1.1.2"
|
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.analytics.ktx.analytics
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import com.google.firebase.ktx.Firebase
|
import com.google.firebase.ktx.Firebase
|
||||||
|
import com.orhanobut.logger.AndroidLogAdapter
|
||||||
|
import com.orhanobut.logger.Logger
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import org.kodein.di.*
|
import org.kodein.di.*
|
||||||
import org.kodein.di.android.x.androidXModule
|
import org.kodein.di.android.x.androidXModule
|
||||||
@@ -96,6 +98,8 @@ class Pupil : Application(), DIAware {
|
|||||||
firebaseAnalytics = Firebase.analytics
|
firebaseAnalytics = Firebase.analytics
|
||||||
FirebaseCrashlytics.getInstance().setUserId(userID)
|
FirebaseCrashlytics.getInstance().setUserId(userID)
|
||||||
|
|
||||||
|
Logger.addLogAdapter(AndroidLogAdapter())
|
||||||
|
|
||||||
val proxyInfo = getProxyInfo()
|
val proxyInfo = getProxyInfo()
|
||||||
|
|
||||||
clientBuilder = OkHttpClient.Builder()
|
clientBuilder = OkHttpClient.Builder()
|
||||||
|
|||||||
@@ -18,29 +18,40 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
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
|
import xyz.quaver.pupil.R
|
||||||
|
|
||||||
class ThumbnailAdapter(var thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
|
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() {
|
fun clear() {
|
||||||
view.ssiv?.recycle()
|
view.controller = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(BigImageView(parent.context).apply {
|
return ViewHolder(SimpleDraweeView(parent.context).apply {
|
||||||
setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant))
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
resources.getDimensionPixelSize(R.dimen.gallery_dialog_preview_height)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
holder.view.showImage(Uri.parse(thumbnails[position]))
|
holder.bind(thumbnails[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = thumbnails.size
|
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 {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
return ViewHolder(RecyclerView(parent.context).apply {
|
return ViewHolder(RecyclerView(parent.context).apply {
|
||||||
val layoutManager = GridLayoutManager(parent.context, 3)
|
this.layoutManager = GridLayoutManager(parent.context, 3)
|
||||||
val adapter = ThumbnailAdapter(listOf())
|
this.adapter = ThumbnailAdapter(listOf())
|
||||||
|
|
||||||
this.layoutManager = layoutManager
|
|
||||||
this.adapter = adapter
|
|
||||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
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 {
|
(holder.view.adapter as ThumbnailAdapter).apply {
|
||||||
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
|
thumbnails = this@ThumbnailPageAdapter.thumbnails.slice(9*position until min(9*position+9, this@ThumbnailPageAdapter.thumbnails.size))
|
||||||
notifyDataSetChanged()
|
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 {
|
companion object {
|
||||||
val extraTypeMap = mapOf(
|
val extraTypeMap = mapOf(
|
||||||
ExtraType.SERIES to R.string.galleryblock_series,
|
ExtraType.SERIES to R.string.galleryblock_series,
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class Hitomi : Source<Hitomi.SortMode, Hitomi.TagSuggestion>() {
|
|||||||
mapOf(
|
mapOf(
|
||||||
ExtraType.TYPE to async { it.type.wordCapitalize() },
|
ExtraType.TYPE to async { it.type.wordCapitalize() },
|
||||||
ExtraType.GROUP to async { it.groups.joinToString { it.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.SERIES to async { it.series.joinToString { it.wordCapitalize() } },
|
||||||
ExtraType.CHARACTER to async { it.characters.joinToString { it.wordCapitalize() } },
|
ExtraType.CHARACTER to async { it.characters.joinToString { it.wordCapitalize() } },
|
||||||
ExtraType.TAGS to async { it.tags.joinToString() },
|
ExtraType.TAGS to async { it.tags.joinToString() },
|
||||||
@@ -227,7 +227,7 @@ class Hitomi : Source<Hitomi.SortMode, Hitomi.TagSuggestion>() {
|
|||||||
}.getOrDefault("") },
|
}.getOrDefault("") },
|
||||||
ExtraType.SERIES to CoroutineScope(Dispatchers.Unconfined).async { galleryBlock.series.joinToString { it.wordCapitalize() } },
|
ExtraType.SERIES to CoroutineScope(Dispatchers.Unconfined).async { galleryBlock.series.joinToString { it.wordCapitalize() } },
|
||||||
ExtraType.TYPE to CoroutineScope(Dispatchers.Unconfined).async { galleryBlock.type.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 {
|
ExtraType.PAGECOUNT to CoroutineScope(Dispatchers.IO).async { kotlin.runCatching {
|
||||||
getGalleryInfo(galleryBlock.id).files.size.toString()
|
getGalleryInfo(galleryBlock.id).files.size.toString()
|
||||||
}.getOrNull() },
|
}.getOrNull() },
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import xyz.quaver.pupil.sources.sourceIcons
|
|||||||
import xyz.quaver.pupil.sources.sources
|
import xyz.quaver.pupil.sources.sources
|
||||||
import xyz.quaver.pupil.types.*
|
import xyz.quaver.pupil.types.*
|
||||||
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
|
||||||
import xyz.quaver.pupil.ui.dialog.GalleryDialog
|
import xyz.quaver.pupil.ui.dialog.GalleryDialogFragment
|
||||||
import xyz.quaver.pupil.ui.dialog.SourceSelectDialog
|
import xyz.quaver.pupil.ui.dialog.SourceSelectDialog
|
||||||
import xyz.quaver.pupil.ui.view.ProgressCardView
|
import xyz.quaver.pupil.ui.view.ProgressCardView
|
||||||
import xyz.quaver.pupil.ui.view.SwipePageTurnView
|
import xyz.quaver.pupil.ui.view.SwipePageTurnView
|
||||||
@@ -264,7 +264,7 @@ class MainActivity :
|
|||||||
|
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
setImageResource(R.drawable.shuffle_variant)
|
setImageResource(R.drawable.shuffle_variant)
|
||||||
GalleryDialog(this@MainActivity, randomResult.id).apply {
|
GalleryDialogFragment(source.name, randomResult.id).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
currentPage = 1
|
currentPage = 1
|
||||||
@@ -272,7 +272,7 @@ class MainActivity :
|
|||||||
query()
|
query()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show(supportFragmentManager, "GalleryDialogFragment")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +292,7 @@ class MainActivity :
|
|||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
val galleryID = editText.text.toString()
|
val galleryID = editText.text.toString()
|
||||||
|
|
||||||
GalleryDialog(this@MainActivity, galleryID).apply {
|
GalleryDialogFragment(source.name, galleryID).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
currentPage = 1
|
currentPage = 1
|
||||||
@@ -300,7 +300,7 @@ class MainActivity :
|
|||||||
query()
|
query()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show(supportFragmentManager, "GalleryDialogFragment")
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
@@ -380,7 +380,7 @@ class MainActivity :
|
|||||||
|
|
||||||
val result = searchResults.getOrNull(position) ?: return@listener true
|
val result = searchResults.getOrNull(position) ?: return@listener true
|
||||||
|
|
||||||
GalleryDialog(this@MainActivity, result.id).apply {
|
GalleryDialogFragment(source.name, result.id).apply {
|
||||||
onChipClickedHandler.add {
|
onChipClickedHandler.add {
|
||||||
query = it.toQuery()
|
query = it.toQuery()
|
||||||
currentPage = 1
|
currentPage = 1
|
||||||
@@ -388,7 +388,7 @@ class MainActivity :
|
|||||||
query()
|
query()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}.show()
|
}.show(supportFragmentManager, "GalleryDialogFragment")
|
||||||
|
|
||||||
true
|
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:layout_height="wrap_content"
|
||||||
android:padding="8dp">
|
android:padding="8dp">
|
||||||
|
|
||||||
<com.github.piasy.biv.view.BigImageView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/cover"
|
android:id="@+id/cover"
|
||||||
android:layout_width="150dp"
|
android:layout_width="150dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -106,20 +106,11 @@
|
|||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<ProgressBar
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/progressbar"
|
||||||
android:layout_height="match_parent">
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
<ProgressBar
|
android:layout_gravity="center"/>
|
||||||
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>
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab"
|
||||||
|
|||||||
@@ -12,4 +12,6 @@
|
|||||||
<dimen name="thumb_height">72dp</dimen>
|
<dimen name="thumb_height">72dp</dimen>
|
||||||
|
|
||||||
<dimen name="thumbnail_page_height">300dp</dimen>
|
<dimen name="thumbnail_page_height">300dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="gallery_dialog_preview_height">150dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
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-gradle-plugin:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
|
|||||||
Reference in New Issue
Block a user