Sources settings

This commit is contained in:
Koitharu
2020-02-28 21:47:08 +02:00
parent bb1ddc31df
commit 9373bae091
18 changed files with 321 additions and 46 deletions

View File

@@ -7,15 +7,35 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.delegates.prefs.EnumPreferenceDelegate
import org.koitharu.kotatsu.utils.delegates.prefs.NullableStringPreferenceDelegate
import org.koitharu.kotatsu.utils.delegates.prefs.StringIntPreferenceDelegate
class AppSettings private constructor(resources: Resources, private val prefs: SharedPreferences) : SharedPreferences by prefs {
class AppSettings private constructor(resources: Resources, private val prefs: SharedPreferences) :
SharedPreferences by prefs {
constructor(context: Context) : this(context.resources, PreferenceManager.getDefaultSharedPreferences(context))
constructor(context: Context) : this(
context.resources,
PreferenceManager.getDefaultSharedPreferences(context)
)
var listMode by EnumPreferenceDelegate(ListMode::class.java, resources.getString(R.string.key_list_mode), ListMode.DETAILED_LIST)
var listMode by EnumPreferenceDelegate(
ListMode::class.java,
resources.getString(R.string.key_list_mode),
ListMode.DETAILED_LIST
)
val theme by StringIntPreferenceDelegate(resources.getString(R.string.key_theme), AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
val theme by StringIntPreferenceDelegate(
resources.getString(R.string.key_theme),
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)
private var sourcesOrderStr by NullableStringPreferenceDelegate(resources.getString(R.string.key_sources_order))
var sourcesOrder: List<Int>
get() = sourcesOrderStr?.split('|')?.mapNotNull(String::toIntOrNull).orEmpty()
set(value) {
sourcesOrderStr = value.joinToString("|")
}
fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
prefs.registerOnSharedPreferenceChangeListener(listener)

View File

@@ -1,14 +1,26 @@
package org.koitharu.kotatsu.domain
import org.koin.core.KoinComponent
import org.koin.core.get
import org.koin.core.inject
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
object MangaProviderFactory : KoinComponent {
private val loaderContext by inject<MangaLoaderContext>()
val sources: List<MangaSource>
get() {
val list = MangaSource.values().toList() - MangaSource.LOCAL
val order = get<AppSettings>().sourcesOrder
return list.sortedBy { x ->
val e = order.indexOf(x.ordinal)
if (e == -1) order.size + x.ordinal else e
}
}
fun create(source: MangaSource): MangaRepository {
val constructor = source.cls.getConstructor(MangaLoaderContext::class.java)
return constructor.newInstance(loaderContext)

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.ui.main
import android.content.SharedPreferences
import android.content.res.Configuration
import android.os.Bundle
import android.view.Menu
@@ -8,8 +9,11 @@ import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.fragment.app.Fragment
import com.google.android.material.navigation.NavigationView
import kotlinx.android.synthetic.main.activity_main.*
import org.koin.core.inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.common.BaseActivity
import org.koitharu.kotatsu.ui.main.list.favourites.FavouritesListFragment
import org.koitharu.kotatsu.ui.main.list.history.HistoryListFragment
@@ -18,8 +22,10 @@ import org.koitharu.kotatsu.ui.main.list.remote.RemoteListFragment
import org.koitharu.kotatsu.ui.settings.SettingsActivity
import org.koitharu.kotatsu.utils.SearchHelper
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private val settings by inject<AppSettings>()
private lateinit var drawerToggle: ActionBarDrawerToggle
override fun onCreate(savedInstanceState: Bundle?) {
@@ -33,6 +39,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
supportActionBar?.setHomeButtonEnabled(true)
navigationView.setNavigationItemSelectedListener(this)
settings.subscribe(this)
if (supportFragmentManager.findFragmentById(R.id.container) == null) {
navigationView.setCheckedItem(R.id.nav_history)
@@ -40,10 +47,15 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
}
override fun onDestroy() {
settings.unsubscribe(this)
super.onDestroy()
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
drawerToggle.syncState()
initSideMenu(MangaSource.values().asList() - MangaSource.LOCAL)
initSideMenu(MangaProviderFactory.sources)
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -90,6 +102,12 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
submenu.setGroupCheckable(R.id.group_remote_sources, true, true)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
getString(R.string.key_sources_order) -> initSideMenu(MangaProviderFactory.sources)
}
}
private fun setPrimaryFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment)

View File

@@ -11,7 +11,6 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.ui.common.BasePreferenceFragment
import org.koitharu.kotatsu.ui.main.list.ListModeSelectDialog
import org.koitharu.kotatsu.utils.ext.bindSummary
class AppearanceSettingsFragment : BasePreferenceFragment(R.string.appearance),
SharedPreferences.OnSharedPreferenceChangeListener {
@@ -21,7 +20,7 @@ class AppearanceSettingsFragment : BasePreferenceFragment(R.string.appearance),
findPreference<Preference>(R.string.key_list_mode)?.summary =
listModes[settings.listMode]?.let(::getString)
findPreference<ListPreference>(R.string.key_theme)?.bindSummary()
findPreference<ListPreference>(R.string.key_theme)?.summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -3,7 +3,9 @@ package org.koitharu.kotatsu.ui.settings
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.commit
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.common.BaseActivity
class SettingsActivity : BaseActivity() {
@@ -20,6 +22,13 @@ class SettingsActivity : BaseActivity() {
}
}
fun openMangaSourceSettings(mangaSource: MangaSource) {
supportFragmentManager.commit {
replace(R.id.container, SourceSettingsFragment.newInstance(mangaSource))
addToBackStack(null)
}
}
companion object {
fun newIntent(context: Context) = Intent(context, SettingsActivity::class.java)

View File

@@ -0,0 +1,35 @@
package org.koitharu.kotatsu.ui.settings
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import org.koin.core.KoinComponent
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.utils.ext.withArgs
class SourceSettingsFragment : PreferenceFragmentCompat(), KoinComponent {
private lateinit var source: MangaSource
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
source = requireArguments().getParcelable(EXTRA_SOURCE)!!
}
override fun onResume() {
super.onResume()
activity?.title = source.title
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
}
companion object {
private const val EXTRA_SOURCE = "source"
fun newInstance(source: MangaSource) = SourceSettingsFragment().withArgs(1) {
putParcelable(EXTRA_SOURCE, source)
}
}
}

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.ui.settings.sources
import android.view.ViewGroup
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
class SourceDividerHolder(parent: ViewGroup) :
BaseViewHolder<Unit, Unit>(parent, R.layout.item_sources_pref_divider) {
override fun onBind(data: Unit, extra: Unit) = Unit
}

View File

@@ -0,0 +1,15 @@
package org.koitharu.kotatsu.ui.settings.sources
import android.view.ViewGroup
import kotlinx.android.synthetic.main.item_source_config.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
class SourceViewHolder(parent: ViewGroup) :
BaseViewHolder<MangaSource, Unit>(parent, R.layout.item_source_config) {
override fun onBind(data: MangaSource, extra: Unit) {
textView_title.text = data.title
}
}

View File

@@ -0,0 +1,59 @@
package org.koitharu.kotatsu.ui.settings.sources
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_source_config.*
import org.koin.core.KoinComponent
import org.koin.core.get
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.domain.MangaProviderFactory
import org.koitharu.kotatsu.ui.common.list.BaseViewHolder
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
class SourcesAdapter(private val onItemClickListener: OnRecyclerItemClickListener<MangaSource>) :
RecyclerView.Adapter<BaseViewHolder<*, Unit>>(), KoinComponent {
private val dataSet = MangaProviderFactory.sources.toMutableList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
ITEM_SOURCE -> SourceViewHolder(parent).also(::onViewHolderCreated)
ITEM_DIVIDER -> SourceDividerHolder(parent)
else -> throw IllegalArgumentException("Unsupported viewType $viewType")
}
override fun getItemCount() = dataSet.size
override fun onBindViewHolder(holder: BaseViewHolder<*, Unit>, position: Int) {
(holder as? SourceViewHolder)?.bind(dataSet[position], Unit)
}
@SuppressLint("ClickableViewAccessibility")
private fun onViewHolderCreated(holder: SourceViewHolder) {
holder.imageView_config.setOnClickListener { v ->
onItemClickListener.onItemClick(holder.requireData(), holder.adapterPosition, v)
}
holder.imageView_handle.setOnTouchListener { v, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
onItemClickListener.onItemLongClick(holder.requireData(), holder.adapterPosition, v)
} else {
false
}
}
}
fun moveItem(oldPos: Int, newPos: Int) {
val item = dataSet.removeAt(oldPos)
dataSet.add(newPos, item)
notifyItemMoved(oldPos, newPos)
get<AppSettings>().sourcesOrder = dataSet.map { it.ordinal }
}
companion object {
const val ITEM_SOURCE = 0
const val ITEM_DIVIDER = 1
}
}

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.ui.settings.sources
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
class SourcesReorderCallback : ItemTouchHelper.SimpleCallback(ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val adapter = recyclerView.adapter as? SourcesAdapter ?: return false
val oldPos = viewHolder.adapterPosition
val newPos = target.adapterPosition
adapter.moveItem(oldPos, newPos)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
override fun isLongPressDragEnabled() = false
}

View File

@@ -1,12 +1,47 @@
package org.koitharu.kotatsu.ui.settings.sources
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.ItemTouchHelper
import kotlinx.android.synthetic.main.fragment_settings_sources.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.ui.common.BaseFragment
import org.koitharu.kotatsu.ui.common.list.OnRecyclerItemClickListener
import org.koitharu.kotatsu.ui.settings.SettingsActivity
class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources) {
class SourcesSettingsFragment : BaseFragment(R.layout.fragment_settings_sources),
OnRecyclerItemClickListener<MangaSource> {
private lateinit var reorderHelper: ItemTouchHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
reorderHelper = ItemTouchHelper(SourcesReorderCallback())
}
override fun onResume() {
super.onResume()
activity?.setTitle(R.string.remote_sources)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.adapter = SourcesAdapter(this)
reorderHelper.attachToRecyclerView(recyclerView)
}
override fun onDestroyView() {
reorderHelper.attachToRecyclerView(null)
super.onDestroyView()
}
override fun onItemClick(item: MangaSource, position: Int, view: View) {
(activity as? SettingsActivity)?.openMangaSourceSettings(item)
}
override fun onItemLongClick(item: MangaSource, position: Int, view: View): Boolean {
reorderHelper.startDrag(recyclerView.findViewHolderForAdapterPosition(position) ?: return false)
return super.onItemLongClick(item, position, view)
}
}

View File

@@ -1,33 +0,0 @@
package org.koitharu.kotatsu.utils.ext
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
fun ListPreference.bindSummary(listener: (String) -> Boolean = { true }) {
summary = entries.getOrNull(findIndexOfValue(value))
this.setOnPreferenceChangeListener { preference, newValue ->
newValue as String
preference as ListPreference
val res = listener(newValue)
if (res) {
preference.summary = preference.entries.getOrNull(preference.findIndexOfValue(newValue))
}
res
}
}
fun EditTextPreference.bindSummary(
formatter: (String) -> String = { it },
listener: (String) -> Boolean = { true }
) {
summary = formatter(text)
this.setOnPreferenceChangeListener { preference, newValue ->
newValue as String
preference as EditTextPreference
val res = listener(newValue)
if (res) {
preference.summary = formatter(newValue)
}
res
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M7,19V17H9V19H7M11,19V17H13V19H11M15,19V17H17V19H15M7,15V13H9V15H7M11,15V13H13V15H11M15,15V13H17V15H15M7,11V9H9V11H7M11,11V9H13V11H11M15,11V9H17V11H15M7,7V5H9V7H7M11,7V5H13V7H11M15,7V5H17V7H15Z" />
</vector>

View File

@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
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:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_source_config" />

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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="?android:windowBackground"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="?listPreferredItemPaddingStart"
android:scaleType="center"
android:src="@drawable/ic_reorder_handle" />
<TextView
android:id="@+id/textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
tools:text="@tools:sample/lorem[1]" />
<ImageView
android:id="@+id/imageView_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:padding="?listPreferredItemPaddingEnd"
android:scaleType="center"
android:src="@drawable/ic_settings" />
</LinearLayout>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/header_height"
android:background="?attr/colorButtonNormal"
android:gravity="start|center_vertical"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
android:text="@string/put_items_below_to_disable_it"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="?android:textColorPrimary" />

View File

@@ -2,6 +2,7 @@
<resources>
<string name="key_list_mode">list_mode</string>
<string name="key_theme">theme</string>
<string name="key_sources_order">sources_order</string>
<string name="key_pages_cache_clear">pages_cache_clear</string>
<string-array name="values_theme">
<item>-1</item>

View File

@@ -83,4 +83,5 @@
<string name="standard">Standard</string>
<string name="webtoon">Webtoon</string>
<string name="read_mode">Read mode</string>
<string name="put_items_below_to_disable_it">Put items below to disable it</string>
</resources>