Manga languages onboarding
parent
cd7d6d7674
commit
8c2bc078e5
@ -0,0 +1,86 @@
|
||||
package org.koitharu.kotatsu.settings.onboard
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.DialogOnboardBinding
|
||||
import org.koitharu.kotatsu.settings.onboard.adapter.SourceLocalesAdapter
|
||||
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
|
||||
import org.koitharu.kotatsu.utils.ext.observeNotNull
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class OnboardDialogFragment : AlertDialogFragment<DialogOnboardBinding>(),
|
||||
OnListItemClickListener<SourceLocale>, DialogInterface.OnClickListener {
|
||||
|
||||
private val viewModel by viewModel<OnboardViewModel>(mode = LazyThreadSafetyMode.NONE)
|
||||
private var isWelcome: Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.run {
|
||||
isWelcome = getBoolean(ARG_WELCOME, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
) = DialogOnboardBinding.inflate(inflater, container, false)
|
||||
|
||||
override fun onBuildDialog(builder: AlertDialog.Builder) {
|
||||
builder
|
||||
.setPositiveButton(R.string.done, this)
|
||||
.setCancelable(true)
|
||||
if (isWelcome) {
|
||||
builder.setTitle(R.string.welcome)
|
||||
} else {
|
||||
builder
|
||||
.setTitle(R.string.remote_sources)
|
||||
.setNegativeButton(android.R.string.cancel, this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val adapter = SourceLocalesAdapter(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
viewModel.list.observeNotNull(viewLifecycleOwner) {
|
||||
adapter.items = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(item: SourceLocale, view: View) {
|
||||
viewModel.setItemChecked(item.key, !item.isChecked)
|
||||
}
|
||||
|
||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> viewModel.apply()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "OnboardDialog"
|
||||
private const val ARG_WELCOME = "welcome"
|
||||
|
||||
fun show(fm: FragmentManager) = OnboardDialogFragment().show(fm, TAG)
|
||||
|
||||
fun showWelcome(settings: AppSettings, fm: FragmentManager) {
|
||||
if (!settings.isSourcesSelected) {
|
||||
OnboardDialogFragment().withArgs(1) {
|
||||
putBoolean(ARG_WELCOME, true)
|
||||
}.show(fm, TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
package org.koitharu.kotatsu.settings.onboard
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
|
||||
import org.koitharu.kotatsu.utils.ext.map
|
||||
import org.koitharu.kotatsu.utils.ext.mapTo
|
||||
import org.koitharu.kotatsu.utils.ext.mapToSet
|
||||
import java.util.*
|
||||
|
||||
class OnboardViewModel(
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val allSources = MangaSource.values().filterNot { x -> x == MangaSource.LOCAL }
|
||||
|
||||
private val locales = allSources.mapTo(ArraySet()) {
|
||||
it.locale
|
||||
}
|
||||
|
||||
private val selectedLocales = locales.toMutableSet()
|
||||
|
||||
val list = MutableLiveData<List<SourceLocale>?>()
|
||||
|
||||
init {
|
||||
if (settings.isSourcesSelected) {
|
||||
selectedLocales.removeAll(settings.hiddenSources.map { x -> MangaSource.valueOf(x).locale })
|
||||
} else {
|
||||
LocaleListCompat.getDefault().mapTo(selectedLocales) { x ->
|
||||
x.language
|
||||
}
|
||||
selectedLocales.retainAll(allSources.map { x -> x.locale })
|
||||
if (selectedLocales.isEmpty()) {
|
||||
selectedLocales += "en"
|
||||
}
|
||||
}
|
||||
rebuildList()
|
||||
}
|
||||
|
||||
fun setItemChecked(key: String?, isChecked: Boolean) {
|
||||
val isModified = if (isChecked) {
|
||||
selectedLocales.add(key)
|
||||
} else {
|
||||
selectedLocales.remove(key)
|
||||
}
|
||||
if (isModified) {
|
||||
rebuildList()
|
||||
}
|
||||
}
|
||||
|
||||
fun apply() {
|
||||
settings.hiddenSources = allSources.filterNot { x ->
|
||||
x.locale in selectedLocales
|
||||
}.mapToSet { x -> x.name }
|
||||
}
|
||||
|
||||
private fun rebuildList() {
|
||||
list.value = locales.map { key ->
|
||||
val locale = if (key != null) {
|
||||
Locale(key)
|
||||
} else null
|
||||
SourceLocale(
|
||||
key = key,
|
||||
title = locale?.getDisplayLanguage(locale)?.capitalize(locale),
|
||||
isChecked = key in selectedLocales
|
||||
)
|
||||
}.sortedWith(SourceLocaleComparator())
|
||||
}
|
||||
|
||||
private class SourceLocaleComparator : Comparator<SourceLocale> {
|
||||
|
||||
private val deviceLocales = LocaleListCompat.getAdjustedDefault()
|
||||
.map { it.language }
|
||||
|
||||
override fun compare(a: SourceLocale?, b: SourceLocale?): Int {
|
||||
return when {
|
||||
a === b -> 0
|
||||
a?.key == null -> 1
|
||||
b?.key == null -> -1
|
||||
else -> {
|
||||
val index = deviceLocales.indexOf(a.key)
|
||||
if (index == -1) {
|
||||
compareValues(a.title, b.title)
|
||||
} else {
|
||||
-2 - index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.koitharu.kotatsu.settings.onboard.adapter
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.ItemSourceLocaleBinding
|
||||
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
|
||||
|
||||
fun sourceLocaleAD(
|
||||
clickListener: OnListItemClickListener<SourceLocale>
|
||||
) = adapterDelegateViewBinding<SourceLocale, SourceLocale, ItemSourceLocaleBinding>(
|
||||
{ inflater, parent -> ItemSourceLocaleBinding.inflate(inflater, parent, false) }
|
||||
) {
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
clickListener.onItemClick(item, it)
|
||||
}
|
||||
|
||||
bind {
|
||||
binding.root.text = item.title ?: getString(R.string.other)
|
||||
binding.root.isChecked = item.isChecked
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package org.koitharu.kotatsu.settings.onboard.adapter
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
|
||||
|
||||
class SourceLocalesAdapter(
|
||||
clickListener: OnListItemClickListener<SourceLocale>,
|
||||
) : AsyncListDifferDelegationAdapter<SourceLocale>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager.addDelegate(sourceLocaleAD(clickListener))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<SourceLocale>() {
|
||||
|
||||
override fun areItemsTheSame(
|
||||
oldItem: SourceLocale,
|
||||
newItem: SourceLocale,
|
||||
): Boolean = oldItem.key == newItem.key
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: SourceLocale,
|
||||
newItem: SourceLocale,
|
||||
): Boolean = oldItem == newItem
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
package org.koitharu.kotatsu.settings.onboard.model
|
||||
|
||||
import java.util.*
|
||||
|
||||
data class SourceLocale(
|
||||
val key: String?,
|
||||
val title: String?,
|
||||
val isChecked: Boolean,
|
||||
) : Comparable<SourceLocale> {
|
||||
|
||||
override fun compareTo(other: SourceLocale): Int {
|
||||
return when {
|
||||
this === other -> 0
|
||||
key == Locale.getDefault().language -> -2
|
||||
key == null -> 1
|
||||
other.key == null -> -1
|
||||
else -> compareValues(title, other.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
fun LocaleListCompat.toList(): List<Locale> {
|
||||
val list = ArrayList<Locale>(size())
|
||||
for (i in 0 until size()) {
|
||||
list += get(i)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
inline fun <R, C : MutableCollection<in R>> LocaleListCompat.mapTo(
|
||||
destination: C,
|
||||
block: (Locale) -> R,
|
||||
): C {
|
||||
val len = size()
|
||||
for (i in 0 until len) {
|
||||
val item = get(i)
|
||||
destination.add(block(item))
|
||||
}
|
||||
return destination
|
||||
}
|
||||
|
||||
inline fun <T> LocaleListCompat.map(block: (Locale) -> T): List<T> {
|
||||
return mapTo(ArrayList(size()), block)
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z" />
|
||||
</vector>
|
||||
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||
android:paddingBottom="6dp"
|
||||
android:text="Select languages which you want to read manga. You can change it later in settings."
|
||||
android:textAppearance="?android:textAppearanceSmall" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_source_locale" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||
android:background="?selectableItemBackground"
|
||||
android:drawableStart="?android:listChoiceIndicatorMultiple"
|
||||
android:drawablePadding="10dp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="@tools:sample/lorem[2]" />
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_languages"
|
||||
android:icon="@drawable/ic_locale"
|
||||
android:title="@string/languages"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
</menu>
|
||||
Loading…
Reference in New Issue