diff --git a/.idea/misc.xml b/.idea/misc.xml
index 4fd703491..bb01f41af 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -14,6 +14,8 @@
+
+
@@ -23,6 +25,7 @@
+
@@ -31,6 +34,7 @@
+
@@ -41,9 +45,12 @@
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 9e18f5bc5..2bc93b43f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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'
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt
index 41476a63b..3f8f07973 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt
@@ -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),
;
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
index 7fc34cc4e..4456df455 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
@@ -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)
diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
index cd95e7e8a..bfb806231 100644
--- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
@@ -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(),
if (savedInstanceState == null) {
TrackWorker.setup(applicationContext)
AppUpdateChecker(this).launchIfNeeded()
+ OnboardDialogFragment.showWelcome(get(), supportFragmentManager)
}
viewModel.onOpenReader.observe(this, this::onOpenReader)
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt
index 222f20258..435847a8e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsModule.kt
@@ -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()) }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardDialogFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardDialogFragment.kt
new file mode 100644
index 000000000..b6945a7e4
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardDialogFragment.kt
@@ -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(),
+ OnListItemClickListener, DialogInterface.OnClickListener {
+
+ private val viewModel by viewModel(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)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt
new file mode 100644
index 000000000..9f8d9e65a
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt
@@ -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?>()
+
+ 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 {
+
+ 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
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/adapter/SourceLocaleAD.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/adapter/SourceLocaleAD.kt
new file mode 100644
index 000000000..d46bafd3d
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/adapter/SourceLocaleAD.kt
@@ -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
+) = adapterDelegateViewBinding(
+ { 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/adapter/SourceLocalesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/adapter/SourceLocalesAdapter.kt
new file mode 100644
index 000000000..0d4112f31
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/adapter/SourceLocalesAdapter.kt
@@ -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,
+) : AsyncListDifferDelegationAdapter(DiffCallback()) {
+
+ init {
+ delegatesManager.addDelegate(sourceLocaleAD(clickListener))
+ }
+
+ private class DiffCallback : DiffUtil.ItemCallback() {
+
+ override fun areItemsTheSame(
+ oldItem: SourceLocale,
+ newItem: SourceLocale,
+ ): Boolean = oldItem.key == newItem.key
+
+ override fun areContentsTheSame(
+ oldItem: SourceLocale,
+ newItem: SourceLocale,
+ ): Boolean = oldItem == newItem
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/model/SourceLocale.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/model/SourceLocale.kt
new file mode 100644
index 000000000..c2bce3718
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/model/SourceLocale.kt
@@ -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 {
+
+ 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)
+ }
+ }
+}
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt
index 9c30c32cb..ac2d96c79 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt
@@ -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(),
OnListItemClickListener {
@@ -25,6 +24,7 @@ class SourcesSettingsFragment : BaseFragment(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
reorderHelper = ItemTouchHelper(SourcesReorderCallback())
+ setHasOptionsMenu(true)
}
override fun onInflateView(
@@ -51,6 +51,22 @@ class SourcesSettingsFragment : BaseFragment(),
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,
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt
new file mode 100644
index 000000000..1ad4cc003
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LocaleExt.kt
@@ -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 {
+ val list = ArrayList(size())
+ for (i in 0 until size()) {
+ list += get(i)
+ }
+ return list
+}
+
+inline fun > 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 LocaleListCompat.map(block: (Locale) -> T): List {
+ return mapTo(ArrayList(size()), block)
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_locale.xml b/app/src/main/res/drawable/ic_locale.xml
new file mode 100644
index 000000000..1bc2d39de
--- /dev/null
+++ b/app/src/main/res/drawable/ic_locale.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/layout/dialog_onboard.xml b/app/src/main/res/layout/dialog_onboard.xml
new file mode 100644
index 000000000..2845055a6
--- /dev/null
+++ b/app/src/main/res/layout/dialog_onboard.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_source_locale.xml b/app/src/main/res/layout/item_source_locale.xml
new file mode 100644
index 000000000..1ccef475e
--- /dev/null
+++ b/app/src/main/res/layout/item_source_locale.xml
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/opt_sources.xml b/app/src/main/res/menu/opt_sources.xml
new file mode 100644
index 000000000..35c7034be
--- /dev/null
+++ b/app/src/main/res/menu/opt_sources.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index b20c30211..4638416bc 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -207,4 +207,7 @@
Пароль должен содержать не менее 4 символов
Прятать заголовок при прокрутке
Поиск только по %s
+ Другие
+ Languages
+ Welcome
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 64fb9d0bc..8112a0029 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -210,4 +210,7 @@
Hide toolbar when scrolling
Search only on %s
Do you really want to remove all recent search queries? This action cannot be undone.
+ Other
+ Languages
+ Welcome
\ No newline at end of file