Grid mode option for sources list
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package org.koitharu.kotatsu.base.ui.util
|
||||
|
||||
import android.view.View
|
||||
import androidx.annotation.Px
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.parsers.util.toIntUp
|
||||
import kotlin.math.abs
|
||||
|
||||
class SpanSizeResolver(
|
||||
private val recyclerView: RecyclerView,
|
||||
@Px private val minItemWidth: Int,
|
||||
) : View.OnLayoutChangeListener {
|
||||
|
||||
fun attach() {
|
||||
recyclerView.addOnLayoutChangeListener(this)
|
||||
}
|
||||
|
||||
fun detach() {
|
||||
recyclerView.removeOnLayoutChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onLayoutChange(
|
||||
v: View?,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int,
|
||||
) {
|
||||
invalidateInternal(abs(right - left))
|
||||
}
|
||||
|
||||
fun invalidate() {
|
||||
invalidateInternal(recyclerView.width)
|
||||
}
|
||||
|
||||
private fun invalidateInternal(width: Int) {
|
||||
if (width <= 0) {
|
||||
return
|
||||
}
|
||||
val lm = recyclerView.layoutManager as? GridLayoutManager ?: return
|
||||
val estimatedCount = (width / minItemWidth.toFloat()).toIntUp()
|
||||
if (lm.spanCount != estimatedCount) {
|
||||
lm.spanCount = estimatedCount
|
||||
lm.spanSizeLookup?.run {
|
||||
invalidateSpanGroupIndexCache()
|
||||
invalidateSpanIndexCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,6 +205,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
sourcesOrder = (sourcesOrder + sources.map { it.name }).distinct()
|
||||
}
|
||||
|
||||
var isSourcesGridMode: Boolean
|
||||
get() = prefs.getBoolean(KEY_SOURCES_GRID, false)
|
||||
set(value) = prefs.edit { putBoolean(KEY_SOURCES_GRID, value) }
|
||||
|
||||
val isPagesNumbersEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_PAGES_NUMBERS, false)
|
||||
|
||||
@@ -376,6 +380,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_APP_LOCALE = "app_locale"
|
||||
const val KEY_LOGGING_ENABLED = "logging"
|
||||
const val KEY_LOGS_SHARE = "logs_share"
|
||||
const val KEY_SOURCES_GRID = "sources_grid"
|
||||
|
||||
// About
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package org.koitharu.kotatsu.core.prefs
|
||||
|
||||
import androidx.lifecycle.liveData
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun <T> AppSettings.observeAsFlow(key: String, valueProducer: AppSettings.() -> T) = flow {
|
||||
var lastValue: T = valueProducer()
|
||||
@@ -33,3 +38,13 @@ fun <T> AppSettings.observeAsLiveData(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> AppSettings.observeAsStateFlow(
|
||||
key: String,
|
||||
scope: CoroutineScope,
|
||||
valueProducer: AppSettings.() -> T,
|
||||
): StateFlow<T> = observe().transform {
|
||||
if (it == key) {
|
||||
emit(valueProducer())
|
||||
}
|
||||
}.stateIn(scope, SharingStarted.Eagerly, valueProducer())
|
||||
|
||||
@@ -9,6 +9,8 @@ import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -19,6 +21,7 @@ import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.base.ui.util.SpanSizeResolver
|
||||
import org.koitharu.kotatsu.bookmarks.ui.BookmarksActivity
|
||||
import org.koitharu.kotatsu.databinding.FragmentExploreBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
@@ -63,6 +66,7 @@ class ExploreFragment :
|
||||
with(binding.recyclerView) {
|
||||
adapter = exploreAdapter
|
||||
setHasFixedSize(true)
|
||||
SpanSizeResolver(this, resources.getDimensionPixelSize(R.dimen.explore_grid_width)).attach()
|
||||
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
|
||||
paddingHorizontal = spacing
|
||||
}
|
||||
@@ -72,6 +76,7 @@ class ExploreFragment :
|
||||
viewModel.onError.observe(viewLifecycleOwner, ::onError)
|
||||
viewModel.onOpenManga.observe(viewLifecycleOwner, ::onOpenManga)
|
||||
viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone)
|
||||
viewModel.isGrid.observe(viewLifecycleOwner, ::onGridModeChanged)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -149,6 +154,16 @@ class ExploreFragment :
|
||||
snackbar.show()
|
||||
}
|
||||
|
||||
private fun onGridModeChanged(isGrid: Boolean) {
|
||||
binding.recyclerView.layoutManager = if (isGrid) {
|
||||
GridLayoutManager(requireContext(), 4).also { lm ->
|
||||
lm.spanSizeLookup = ExploreGridSpanSizeLookup(checkNotNull(exploreAdapter), lm)
|
||||
}
|
||||
} else {
|
||||
LinearLayoutManager(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
private inner class SourceMenuListener(
|
||||
private val sourceItem: ExploreItem.Source,
|
||||
) : PopupMenu.OnMenuItemClickListener {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.koitharu.kotatsu.explore.ui
|
||||
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
|
||||
import org.koitharu.kotatsu.explore.ui.adapter.ExploreAdapter
|
||||
|
||||
class ExploreGridSpanSizeLookup(
|
||||
private val adapter: ExploreAdapter,
|
||||
private val layoutManager: GridLayoutManager,
|
||||
) : SpanSizeLookup() {
|
||||
|
||||
override fun getSpanSize(position: Int): Int {
|
||||
val itemType = adapter.getItemViewType(position)
|
||||
return if (itemType == ExploreAdapter.ITEM_TYPE_SOURCE_GRID) 1 else layoutManager.spanCount
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,26 @@ import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.ReversibleHandle
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.explore.domain.ExploreRepository
|
||||
import org.koitharu.kotatsu.explore.ui.model.ExploreItem
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -30,8 +34,15 @@ class ExploreViewModel @Inject constructor(
|
||||
private val exploreRepository: ExploreRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val gridMode = settings.observeAsStateFlow(
|
||||
key = AppSettings.KEY_SOURCES_GRID,
|
||||
scope = viewModelScope + Dispatchers.IO,
|
||||
valueProducer = { isSourcesGridMode },
|
||||
)
|
||||
|
||||
val onOpenManga = SingleLiveEvent<Manga>()
|
||||
val onActionDone = SingleLiveEvent<ReversibleAction>()
|
||||
val isGrid = gridMode.asFlowLiveData(viewModelScope.coroutineContext)
|
||||
|
||||
val content: LiveData<List<ExploreItem>> = isLoading.asFlow().flatMapLatest { loading ->
|
||||
if (loading) {
|
||||
@@ -67,16 +78,16 @@ class ExploreViewModel @Inject constructor(
|
||||
.onStart { emit("") }
|
||||
.map { settings.getMangaSources(includeHidden = false) }
|
||||
.distinctUntilChanged()
|
||||
.map { buildList(it) }
|
||||
.combine(gridMode) { content, grid -> buildList(content, grid) }
|
||||
|
||||
private fun buildList(sources: List<MangaSource>): List<ExploreItem> {
|
||||
private fun buildList(sources: List<MangaSource>, isGrid: Boolean): List<ExploreItem> {
|
||||
val result = ArrayList<ExploreItem>(sources.size + 3)
|
||||
result += ExploreItem.Buttons(
|
||||
isSuggestionsEnabled = settings.isSuggestionsEnabled,
|
||||
)
|
||||
result += ExploreItem.Header(R.string.remote_sources, sources.isNotEmpty())
|
||||
if (sources.isNotEmpty()) {
|
||||
sources.mapTo(result) { ExploreItem.Source(it) }
|
||||
sources.mapTo(result) { ExploreItem.Source(it, isGrid) }
|
||||
} else {
|
||||
result += ExploreItem.EmptyHint(
|
||||
icon = R.drawable.ic_empty_common,
|
||||
|
||||
@@ -11,11 +11,25 @@ class ExploreAdapter(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
listener: ExploreListEventListener,
|
||||
clickListener: OnListItemClickListener<ExploreItem.Source>,
|
||||
) : AsyncListDifferDelegationAdapter<ExploreItem>(
|
||||
ExploreDiffCallback(),
|
||||
exploreButtonsAD(listener),
|
||||
exploreSourcesHeaderAD(listener),
|
||||
exploreSourceItemAD(coil, clickListener, lifecycleOwner),
|
||||
exploreEmptyHintListAD(listener),
|
||||
exploreLoadingAD(),
|
||||
)
|
||||
) : AsyncListDifferDelegationAdapter<ExploreItem>(ExploreDiffCallback()) {
|
||||
|
||||
init {
|
||||
delegatesManager
|
||||
.addDelegate(ITEM_TYPE_BUTTONS, exploreButtonsAD(listener))
|
||||
.addDelegate(ITEM_TYPE_HEADER, exploreSourcesHeaderAD(listener))
|
||||
.addDelegate(ITEM_TYPE_SOURCE_LIST, exploreSourceListItemAD(coil, clickListener, lifecycleOwner))
|
||||
.addDelegate(ITEM_TYPE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner))
|
||||
.addDelegate(ITEM_TYPE_HINT, exploreEmptyHintListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_LOADING, exploreLoadingAD())
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val ITEM_TYPE_BUTTONS = 0
|
||||
const val ITEM_TYPE_HEADER = 1
|
||||
const val ITEM_TYPE_SOURCE_LIST = 2
|
||||
const val ITEM_TYPE_SOURCE_GRID = 3
|
||||
const val ITEM_TYPE_HINT = 4
|
||||
const val ITEM_TYPE_LOADING = 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
||||
import org.koitharu.kotatsu.databinding.ItemEmptyCardBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreSourceBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreSourceListBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding
|
||||
import org.koitharu.kotatsu.explore.ui.model.ExploreItem
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||
@@ -25,7 +26,7 @@ import org.koitharu.kotatsu.utils.image.FaviconFallbackDrawable
|
||||
fun exploreButtonsAD(
|
||||
clickListener: View.OnClickListener,
|
||||
) = adapterDelegateViewBinding<ExploreItem.Buttons, ExploreItem, ItemExploreButtonsBinding>(
|
||||
{ layoutInflater, parent -> ItemExploreButtonsBinding.inflate(layoutInflater, parent, false) }
|
||||
{ layoutInflater, parent -> ItemExploreButtonsBinding.inflate(layoutInflater, parent, false) },
|
||||
) {
|
||||
|
||||
binding.buttonBookmarks.setOnClickListener(clickListener)
|
||||
@@ -43,7 +44,7 @@ fun exploreButtonsAD(
|
||||
fun exploreSourcesHeaderAD(
|
||||
listener: ExploreListEventListener,
|
||||
) = adapterDelegateViewBinding<ExploreItem.Header, ExploreItem, ItemHeaderButtonBinding>(
|
||||
{ layoutInflater, parent -> ItemHeaderButtonBinding.inflate(layoutInflater, parent, false) }
|
||||
{ layoutInflater, parent -> ItemHeaderButtonBinding.inflate(layoutInflater, parent, false) },
|
||||
) {
|
||||
|
||||
val listenerAdapter = View.OnClickListener {
|
||||
@@ -58,13 +59,44 @@ fun exploreSourcesHeaderAD(
|
||||
}
|
||||
}
|
||||
|
||||
fun exploreSourceItemAD(
|
||||
fun exploreSourceListItemAD(
|
||||
coil: ImageLoader,
|
||||
listener: OnListItemClickListener<ExploreItem.Source>,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) = adapterDelegateViewBinding<ExploreItem.Source, ExploreItem, ItemExploreSourceBinding>(
|
||||
{ layoutInflater, parent -> ItemExploreSourceBinding.inflate(layoutInflater, parent, false) },
|
||||
on = { item, _, _ -> item is ExploreItem.Source }
|
||||
) = adapterDelegateViewBinding<ExploreItem.Source, ExploreItem, ItemExploreSourceListBinding>(
|
||||
{ layoutInflater, parent -> ItemExploreSourceListBinding.inflate(layoutInflater, parent, false) },
|
||||
on = { item, _, _ -> item is ExploreItem.Source && !item.isGrid },
|
||||
) {
|
||||
|
||||
val eventListener = AdapterDelegateClickListenerAdapter(this, listener)
|
||||
|
||||
binding.root.setOnClickListener(eventListener)
|
||||
binding.root.setOnLongClickListener(eventListener)
|
||||
|
||||
bind {
|
||||
binding.textViewTitle.text = item.source.title
|
||||
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
|
||||
binding.imageViewIcon.newImageRequest(item.source.faviconUri())?.run {
|
||||
fallback(fallbackIcon)
|
||||
placeholder(fallbackIcon)
|
||||
error(fallbackIcon)
|
||||
lifecycle(lifecycleOwner)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewIcon.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
fun exploreSourceGridItemAD(
|
||||
coil: ImageLoader,
|
||||
listener: OnListItemClickListener<ExploreItem.Source>,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) = adapterDelegateViewBinding<ExploreItem.Source, ExploreItem, ItemExploreSourceGridBinding>(
|
||||
{ layoutInflater, parent -> ItemExploreSourceGridBinding.inflate(layoutInflater, parent, false) },
|
||||
on = { item, _, _ -> item is ExploreItem.Source && item.isGrid },
|
||||
) {
|
||||
|
||||
val eventListener = AdapterDelegateClickListenerAdapter(this, listener)
|
||||
@@ -92,7 +124,7 @@ fun exploreSourceItemAD(
|
||||
fun exploreEmptyHintListAD(
|
||||
listener: ListStateHolderListener,
|
||||
) = adapterDelegateViewBinding<ExploreItem.EmptyHint, ExploreItem, ItemEmptyCardBinding>(
|
||||
{ inflater, parent -> ItemEmptyCardBinding.inflate(inflater, parent, false) }
|
||||
{ inflater, parent -> ItemEmptyCardBinding.inflate(inflater, parent, false) },
|
||||
) {
|
||||
|
||||
binding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() }
|
||||
|
||||
@@ -12,11 +12,13 @@ class ExploreDiffCallback : DiffUtil.ItemCallback<ExploreItem>() {
|
||||
oldItem is ExploreItem.Loading && newItem is ExploreItem.Loading -> true
|
||||
oldItem is ExploreItem.EmptyHint && newItem is ExploreItem.EmptyHint -> true
|
||||
oldItem is ExploreItem.Source && newItem is ExploreItem.Source -> {
|
||||
oldItem.source == newItem.source
|
||||
oldItem.source == newItem.source && oldItem.isGrid == newItem.isGrid
|
||||
}
|
||||
|
||||
oldItem is ExploreItem.Header && newItem is ExploreItem.Header -> {
|
||||
oldItem.titleResId == newItem.titleResId
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -24,4 +26,4 @@ class ExploreDiffCallback : DiffUtil.ItemCallback<ExploreItem>() {
|
||||
override fun areContentsTheSame(oldItem: ExploreItem, newItem: ExploreItem): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ sealed interface ExploreItem : ListModel {
|
||||
|
||||
class Source(
|
||||
val source: MangaSource,
|
||||
val isGrid: Boolean,
|
||||
) : ExploreItem {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -63,12 +64,15 @@ sealed interface ExploreItem : ListModel {
|
||||
other as Source
|
||||
|
||||
if (source != other.source) return false
|
||||
if (isGrid != other.isGrid) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return source.hashCode()
|
||||
var result = source.hashCode()
|
||||
result = 31 * result + isGrid.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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"
|
||||
@@ -9,9 +8,8 @@
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/list_spacing"
|
||||
android:paddingRight="@dimen/list_spacing"
|
||||
android:paddingTop="@dimen/grid_spacing_outer"
|
||||
android:paddingRight="@dimen/list_spacing"
|
||||
android:paddingBottom="@dimen/grid_spacing_outer"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_explore_source" />
|
||||
tools:listitem="@layout/item_explore_source_list" />
|
||||
|
||||
38
app/src/main/res/layout/item_explore_source_grid.xml
Normal file
38
app/src/main/res/layout/item_explore_source_grid.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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:background="@drawable/list_selector"
|
||||
android:clipChildren="false"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/list_spacing"
|
||||
tools:layout_width="120dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_icon"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:background="?colorControlHighlight"
|
||||
android:labelFor="@id/textView_title"
|
||||
android:scaleType="fitCenter"
|
||||
app:shapeAppearance="?shapeAppearanceCornerMedium"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/list_spacing"
|
||||
android:elegantTextHeight="false"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_horizontal"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
tools:text="@tools:sample/lorem[2]" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -31,6 +31,7 @@
|
||||
<dimen name="reading_progress_text_size">10dp</dimen>
|
||||
<dimen name="reader_bar_inset_fallback">8dp</dimen>
|
||||
<dimen name="scrobbling_list_spacing">12dp</dimen>
|
||||
<dimen name="explore_grid_width">120dp</dimen>
|
||||
|
||||
<dimen name="search_suggestions_manga_height">124dp</dimen>
|
||||
<dimen name="search_suggestions_manga_spacing">4dp</dimen>
|
||||
|
||||
@@ -411,4 +411,5 @@
|
||||
<string name="theme_name_dynamic">Dynamic</string>
|
||||
<string name="color_theme">Color scheme</string>
|
||||
<string name="theme_name_october">October</string>
|
||||
<string name="show_in_grid_view">Show in grid view</string>
|
||||
</resources>
|
||||
|
||||
@@ -9,10 +9,16 @@
|
||||
android:key="remote_sources"
|
||||
android:title="@string/remote_sources" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="sources_grid"
|
||||
android:title="@string/show_in_grid_view" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:fragment="org.koitharu.kotatsu.settings.SuggestionsSettingsFragment"
|
||||
android:key="suggestions"
|
||||
android:title="@string/suggestions" />
|
||||
android:title="@string/suggestions"
|
||||
app:allowDividerAbove="true" />
|
||||
|
||||
<ListPreference
|
||||
android:entries="@array/doh_providers"
|
||||
|
||||
Reference in New Issue
Block a user