Manga languages onboarding

This commit is contained in:
Koitharu
2021-06-17 19:45:36 +03:00
parent cd7d6d7674
commit 8c2bc078e5
19 changed files with 391 additions and 5 deletions

7
.idea/misc.xml generated
View File

@@ -14,6 +14,8 @@
<entry key="app/src/main/res/drawable/ic_clear_all.xml" value="0.275" />
<entry key="app/src/main/res/drawable/ic_complete.xml" value="0.275" />
<entry key="app/src/main/res/drawable/ic_history.xml" value="0.275" />
<entry key="app/src/main/res/drawable/ic_locale.xml" value="0.197" />
<entry key="app/src/main/res/drawable/ic_open_external.xml" value="0.197" />
<entry key="app/src/main/res/drawable/tab_indicator.xml" value="0.28512820512820514" />
<entry key="app/src/main/res/drawable/tabs_background.xml" value="0.28512820512820514" />
<entry key="app/src/main/res/layout-w600dp/activity_details.xml" value="0.18072916666666666" />
@@ -23,6 +25,7 @@
<entry key="app/src/main/res/layout/activity_setup_protect.xml" value="0.26927083333333335" />
<entry key="app/src/main/res/layout/dialog_favorite_categories.xml" value="0.2601851851851852" />
<entry key="app/src/main/res/layout/dialog_list_mode.xml" value="0.2601851851851852" />
<entry key="app/src/main/res/layout/dialog_onboard.xml" value="0.25885416666666666" />
<entry key="app/src/main/res/layout/fragment_chapters.xml" value="0.24739583333333334" />
<entry key="app/src/main/res/layout/fragment_details.xml" value="0.26145833333333335" />
<entry key="app/src/main/res/layout/fragment_favourites.xml" value="0.26296296296296295" />
@@ -31,6 +34,7 @@
<entry key="app/src/main/res/layout/fragment_search_suggestion.xml" value="0.25885416666666666" />
<entry key="app/src/main/res/layout/item_branch.xml" value="0.24739583333333334" />
<entry key="app/src/main/res/layout/item_branch_dropdown.xml" value="0.25743589743589745" />
<entry key="app/src/main/res/layout/item_category.xml" value="0.25885416666666666" />
<entry key="app/src/main/res/layout/item_category_checkable.xml" value="0.2601851851851852" />
<entry key="app/src/main/res/layout/item_manga_grid.xml" value="0.26042632066728455" />
<entry key="app/src/main/res/layout/item_manga_list_details.xml" value="0.2601851851851852" />
@@ -41,9 +45,12 @@
<entry key="app/src/main/res/layout/item_search_suggestion_manga.xml" value="0.24479166666666666" />
<entry key="app/src/main/res/layout/item_search_suggestion_query.xml" value="0.587248322147651" />
<entry key="app/src/main/res/layout/item_source_config.xml" value="0.25885416666666666" />
<entry key="app/src/main/res/layout/item_source_locale.xml" value="0.25885416666666666" />
<entry key="app/src/main/res/layout/item_tracklog.xml" value="0.24479166666666666" />
<entry key="app/src/main/res/layout/sheet_pages.xml" value="0.2601851851851852" />
<entry key="app/src/main/res/menu/opt_browser.xml" value="0.24479166666666666" />
<entry key="app/src/main/res/menu/opt_protect.xml" value="0.26927083333333335" />
<entry key="app/src/main/res/menu/opt_sources.xml" value="0.24479166666666666" />
<entry key="app/src/main/res/menu/popup_category.xml" value="0.2601851851851852" />
<entry key="app/src/main/res/xml/pref_main.xml" value="0.26927083333333335" />
</map>

View File

@@ -74,7 +74,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-process:2.3.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.work:work-runtime-ktx:2.5.0'

View File

@@ -37,7 +37,7 @@ enum class MangaSource(
NINEMANGA_RU("NineManga Русский", "ru", NineMangaRepository.Russian::class.java),
NINEMANGA_DE("NineManga Deutsch", "de", NineMangaRepository.Deutsch::class.java),
NINEMANGA_IT("NineManga Italiano", "it", NineMangaRepository.Italiano::class.java),
NINEMANGA_BR("NineManga Brasil", "br", NineMangaRepository.Brazil::class.java),
NINEMANGA_BR("NineManga Brasil", "pt", NineMangaRepository.Brazil::class.java),
NINEMANGA_FR("NineManga Français", "fr", NineMangaRepository.Francais::class.java),
;

View File

@@ -101,6 +101,9 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
var hiddenSources by StringSetPreferenceDelegate(KEY_SOURCES_HIDDEN)
val isSourcesSelected: Boolean
get() = KEY_SOURCES_HIDDEN in prefs
fun getStorageDir(context: Context): File? {
val value = prefs.getString(KEY_LOCAL_STORAGE, null)?.let {
File(it)

View File

@@ -20,6 +20,7 @@ import androidx.fragment.app.commit
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
@@ -41,6 +42,7 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
import org.koitharu.kotatsu.search.ui.suggestion.SearchUI
import org.koitharu.kotatsu.settings.AppUpdateChecker
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
import org.koitharu.kotatsu.tracker.ui.FeedFragment
import org.koitharu.kotatsu.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
@@ -86,6 +88,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
if (savedInstanceState == null) {
TrackWorker.setup(applicationContext)
AppUpdateChecker(this).launchIfNeeded()
OnboardDialogFragment.showWelcome(get(), supportFragmentManager)
}
viewModel.onOpenReader.observe(this, this::onOpenReader)

View File

@@ -9,6 +9,7 @@ import org.koitharu.kotatsu.core.backup.RestoreRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.settings.backup.BackupViewModel
import org.koitharu.kotatsu.settings.backup.RestoreViewModel
import org.koitharu.kotatsu.settings.onboard.OnboardViewModel
import org.koitharu.kotatsu.settings.protect.ProtectSetupViewModel
val settingsModule
@@ -21,4 +22,5 @@ val settingsModule
viewModel { BackupViewModel(get(), androidContext()) }
viewModel { (uri: Uri?) -> RestoreViewModel(uri, get(), androidContext()) }
viewModel { ProtectSetupViewModel(get()) }
viewModel { OnboardViewModel(get()) }
}

View File

@@ -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)
}
}
}
}

View File

@@ -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
}
}
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -1,9 +1,7 @@
package org.koitharu.kotatsu.settings.sources
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.*
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.DividerItemDecoration
@@ -16,6 +14,7 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
OnListItemClickListener<MangaSource> {
@@ -25,6 +24,7 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
reorderHelper = ItemTouchHelper(SourcesReorderCallback())
setHasOptionsMenu(true)
}
override fun onInflateView(
@@ -51,6 +51,22 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
super.onDestroyView()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
// TODO handle changes in dialog
// inflater.inflate(R.menu.opt_sources, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId) {
R.id.action_languages -> {
OnboardDialogFragment.show(parentFragmentManager)
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.recyclerView.updatePadding(
bottom = insets.bottom,

View File

@@ -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)
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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]" />

View File

@@ -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>

View File

@@ -207,4 +207,7 @@
<string name="password_length_hint">Пароль должен содержать не менее 4 символов</string>
<string name="hide_toolbar">Прятать заголовок при прокрутке</string>
<string name="search_only_on_s">Поиск только по %s</string>
<string name="other">Другие</string>
<string name="languages">Languages</string>
<string name="welcome">Welcome</string>
</resources>

View File

@@ -210,4 +210,7 @@
<string name="hide_toolbar">Hide toolbar when scrolling</string>
<string name="search_only_on_s">Search only on %s</string>
<string name="text_clear_search_history_prompt">Do you really want to remove all recent search queries? This action cannot be undone.</string>
<string name="other">Other</string>
<string name="languages">Languages</string>
<string name="welcome">Welcome</string>
</resources>