Support ascending/descending sort orders

This commit is contained in:
Koitharu
2024-08-29 07:40:53 +03:00
parent 05b5953f35
commit c70e3547d1
14 changed files with 264 additions and 62 deletions

View File

@@ -0,0 +1,31 @@
package org.koitharu.kotatsu.core.model
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.model.SortOrder
enum class GenericSortOrder(
@StringRes val titleResId: Int,
val ascending: SortOrder,
val descending: SortOrder,
) {
UPDATED(R.string.updated, SortOrder.UPDATED_ASC, SortOrder.UPDATED),
RATING(R.string.by_rating, SortOrder.RATING_ASC, SortOrder.RATING),
POPULARITY(R.string.popularity, SortOrder.POPULARITY_ASC, SortOrder.POPULARITY),
DATE(R.string.by_date, SortOrder.NEWEST_ASC, SortOrder.NEWEST),
NAME(R.string.by_name, SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC),
;
operator fun get(direction: SortDirection): SortOrder = when (direction) {
SortDirection.ASC -> ascending
SortDirection.DESC -> descending
}
companion object {
fun of(order: SortOrder): GenericSortOrder = entries.first { e ->
e.ascending == order || e.descending == order
}
}
}

View File

@@ -0,0 +1,6 @@
package org.koitharu.kotatsu.core.model
enum class SortDirection {
ASC, DESC;
}

View File

@@ -31,7 +31,7 @@ class ExternalPluginContentSource(
@Blocking
@WorkerThread
fun getList(offset: Int, filter: MangaListFilter?): List<Manga> = runCatchingCompatibility {
fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
val uri = "content://${source.authority}/manga".toUri().buildUpon()
uri.appendQueryParameter("offset", offset.toString())
when (filter) {
@@ -49,7 +49,7 @@ class ExternalPluginContentSource(
null -> Unit
}
contentResolver.query(uri.build(), null, null, null, filter?.sortOrder?.name)
return contentResolver.query(uri.build(), null, null, null, filter?.sortOrder?.name)
.safe()
.use { cursor ->
val result = ArrayList<Manga>(cursor.count)
@@ -64,10 +64,10 @@ class ExternalPluginContentSource(
@Blocking
@WorkerThread
fun getDetails(manga: Manga) = runCatchingCompatibility {
fun getDetails(manga: Manga): Manga {
val chapters = queryChapters(manga.url)
val details = queryDetails(manga.url)
Manga(
return Manga(
id = manga.id,
title = details.title.ifBlank { manga.title },
altTitle = details.altTitle.ifNullOrEmpty { manga.altTitle },
@@ -88,12 +88,12 @@ class ExternalPluginContentSource(
@Blocking
@WorkerThread
fun getPages(chapter: MangaChapter): List<MangaPage> = runCatchingCompatibility {
fun getPages(chapter: MangaChapter): List<MangaPage> {
val uri = "content://${source.authority}/chapters".toUri()
.buildUpon()
.appendPath(chapter.url)
.build()
contentResolver.query(uri, null, null, null, null)
return contentResolver.query(uri, null, null, null, null)
.safe()
.use { cursor ->
val result = ArrayList<MangaPage>(cursor.count)
@@ -113,9 +113,9 @@ class ExternalPluginContentSource(
@Blocking
@WorkerThread
fun getTags(): Set<MangaTag> = runCatchingCompatibility {
fun getTags(): Set<MangaTag> {
val uri = "content://${source.authority}/tags".toUri()
contentResolver.query(uri, null, null, null, null)
return contentResolver.query(uri, null, null, null, null)
.safe()
.use { cursor ->
val result = ArraySet<MangaTag>(cursor.count)
@@ -212,7 +212,7 @@ class ExternalPluginContentSource(
}
}
private fun SafeCursor.getManga() = Manga(
private fun ExternalPluginCursor.getManga() = Manga(
id = getLong(COLUMN_ID),
title = getString(COLUMN_TITLE),
altTitle = getStringOrNull(COLUMN_ALT_TITLE),
@@ -233,15 +233,10 @@ class ExternalPluginContentSource(
source = source,
)
private inline fun <R> runCatchingCompatibility(block: () -> R): R = try {
block()
} catch (e: NoSuchElementException) { // unknown column name
throw IncompatiblePluginException(source.name, e)
} catch (e: IllegalArgumentException) {
throw IncompatiblePluginException(source.name, e)
}
private fun Cursor?.safe() = SafeCursor(this ?: throw IncompatiblePluginException(source.name, null))
private fun Cursor?.safe() = ExternalPluginCursor(
source = source,
cursor = this ?: throw IncompatiblePluginException(source.name, null),
)
class MangaSourceCapabilities(
val availableSortOrders: Set<SortOrder>,

View File

@@ -2,14 +2,19 @@ package org.koitharu.kotatsu.core.parser.external
import android.database.Cursor
import android.database.CursorWrapper
import org.koitharu.kotatsu.core.exceptions.IncompatiblePluginException
import org.koitharu.kotatsu.core.util.ext.getBoolean
class SafeCursor(cursor: Cursor) : CursorWrapper(cursor) {
class ExternalPluginCursor(private val source: ExternalMangaSource, cursor: Cursor) : CursorWrapper(cursor) {
fun getString(columnName: String): String {
return getString(getColumnIndexOrThrow(columnName))
override fun getColumnIndexOrThrow(columnName: String?): Int = try {
super.getColumnIndexOrThrow(columnName)
} catch (e: Exception) {
throw IncompatiblePluginException(source.name, e)
}
fun getString(columnName: String): String = getString(getColumnIndexOrThrow(columnName))
fun getStringOrNull(columnName: String): String? {
val columnIndex = getColumnIndex(columnName)
return when {
@@ -19,9 +24,7 @@ class SafeCursor(cursor: Cursor) : CursorWrapper(cursor) {
}
}
fun getBoolean(columnName: String): Boolean {
return getBoolean(getColumnIndexOrThrow(columnName))
}
fun getBoolean(columnName: String): Boolean = getBoolean(getColumnIndexOrThrow(columnName))
fun getBooleanOrDefault(columnName: String, defaultValue: Boolean): Boolean {
val columnIndex = getColumnIndex(columnName)
@@ -32,9 +35,7 @@ class SafeCursor(cursor: Cursor) : CursorWrapper(cursor) {
}
}
fun getInt(columnName: String): Int {
return getInt(getColumnIndexOrThrow(columnName))
}
fun getInt(columnName: String): Int = getInt(getColumnIndexOrThrow(columnName))
fun getIntOrDefault(columnName: String, defaultValue: Int): Int {
val columnIndex = getColumnIndex(columnName)
@@ -45,9 +46,7 @@ class SafeCursor(cursor: Cursor) : CursorWrapper(cursor) {
}
}
fun getLong(columnName: String): Long {
return getLong(getColumnIndexOrThrow(columnName))
}
fun getLong(columnName: String): Long = getLong(getColumnIndexOrThrow(columnName))
fun getLongOrDefault(columnName: String, defaultValue: Long): Long {
val columnIndex = getColumnIndex(columnName)
@@ -58,9 +57,7 @@ class SafeCursor(cursor: Cursor) : CursorWrapper(cursor) {
}
}
fun getFloat(columnName: String): Float {
return getFloat(getColumnIndexOrThrow(columnName))
}
fun getFloat(columnName: String): Float = getFloat(getColumnIndexOrThrow(columnName))
fun getFloatOrDefault(columnName: String, defaultValue: Float): Float {
val columnIndex = getColumnIndex(columnName)

View File

@@ -2,15 +2,45 @@ package org.koitharu.kotatsu.core.ui.model
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.SortDirection
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.model.SortOrder.ALPHABETICAL
import org.koitharu.kotatsu.parsers.model.SortOrder.ALPHABETICAL_DESC
import org.koitharu.kotatsu.parsers.model.SortOrder.NEWEST
import org.koitharu.kotatsu.parsers.model.SortOrder.NEWEST_ASC
import org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY
import org.koitharu.kotatsu.parsers.model.SortOrder.POPULARITY_ASC
import org.koitharu.kotatsu.parsers.model.SortOrder.RATING
import org.koitharu.kotatsu.parsers.model.SortOrder.RATING_ASC
import org.koitharu.kotatsu.parsers.model.SortOrder.UPDATED
import org.koitharu.kotatsu.parsers.model.SortOrder.UPDATED_ASC
@get:StringRes
val SortOrder.titleRes: Int
get() = when (this) {
SortOrder.UPDATED -> R.string.updated
SortOrder.POPULARITY -> R.string.popular
SortOrder.RATING -> R.string.by_rating
SortOrder.NEWEST -> R.string.newest
SortOrder.ALPHABETICAL -> R.string.by_name
SortOrder.ALPHABETICAL_DESC -> R.string.by_name_reverse
UPDATED -> R.string.updated
POPULARITY -> R.string.popular
RATING -> R.string.by_rating
NEWEST -> R.string.newest
ALPHABETICAL -> R.string.by_name
ALPHABETICAL_DESC -> R.string.by_name_reverse
UPDATED_ASC -> R.string.updated_long_ago
POPULARITY_ASC -> R.string.unpopular
RATING_ASC -> R.string.low_rating
NEWEST_ASC -> R.string.order_oldest
}
val SortOrder.direction: SortDirection
get() = when (this) {
UPDATED_ASC,
POPULARITY_ASC,
RATING_ASC,
NEWEST_ASC,
ALPHABETICAL -> SortDirection.ASC
UPDATED,
POPULARITY,
RATING,
NEWEST,
ALPHABETICAL_DESC -> SortDirection.DESC
}

View File

@@ -24,14 +24,18 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.GenericSortOrder
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.SortDirection
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.ui.model.direction
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.LocaleComparator
import org.koitharu.kotatsu.core.util.ext.asArrayList
import org.koitharu.kotatsu.core.util.ext.lifecycleScope
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.filter.ui.model.TagCatalogItem
@@ -52,6 +56,7 @@ import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import java.text.Collator
import java.util.EnumSet
import java.util.LinkedList
import java.util.Locale
import java.util.TreeSet
@@ -131,24 +136,42 @@ class FilterCoordinator @Inject constructor(
MutableStateFlow(emptyProperty())
}
override val filterSortOrder: StateFlow<FilterProperty<SortOrder>> = combine(
currentState.distinctUntilChangedBy { it.sortOrder },
flowOf(repository.sortOrders),
) { state, orders ->
FilterProperty(
availableItems = orders.sortedBy { it.ordinal },
selectedItems = setOf(state.sortOrder),
isLoading = false,
error = null,
)
}.stateIn(coroutineScope + Dispatchers.Default, SharingStarted.Lazily, loadingProperty())
override val filterSortOrder: StateFlow<FilterProperty<GenericSortOrder>> =
currentState.distinctUntilChangedBy { it.sortOrder }.map { state ->
val orders = repository.sortOrders
FilterProperty(
availableItems = orders.mapTo(EnumSet.noneOf(GenericSortOrder::class.java)) {
GenericSortOrder.of(it)
}.sortedByOrdinal(),
selectedItems = setOf(GenericSortOrder.of(state.sortOrder)),
isLoading = false,
error = null,
)
}.stateIn(coroutineScope + Dispatchers.Default, SharingStarted.Lazily, loadingProperty())
override val filterSortDirection: StateFlow<FilterProperty<SortDirection>> =
currentState.distinctUntilChangedBy { it.sortOrder }.map { state ->
val orders = repository.sortOrders
FilterProperty(
availableItems = state.sortOrder.let {
val genericOrder = GenericSortOrder.of(it)
val result = EnumSet.noneOf(SortDirection::class.java)
if (genericOrder.ascending in orders) result.add(SortDirection.ASC)
if (genericOrder.descending in orders) result.add(SortDirection.DESC)
result
}?.sortedByOrdinal().orEmpty(),
selectedItems = setOf(state.sortOrder.direction),
isLoading = false,
error = null,
)
}.stateIn(coroutineScope + Dispatchers.Default, SharingStarted.Lazily, loadingProperty())
override val filterState: StateFlow<FilterProperty<MangaState>> = combine(
currentState.distinctUntilChangedBy { it.states },
flowOf(repository.states),
) { state, states ->
FilterProperty(
availableItems = states.sortedBy { it.ordinal },
availableItems = states.sortedByOrdinal(),
selectedItems = state.states,
isLoading = false,
error = null,
@@ -160,7 +183,7 @@ class FilterCoordinator @Inject constructor(
flowOf(repository.contentRatings),
) { rating, ratings ->
FilterProperty(
availableItems = ratings.sortedBy { it.ordinal },
availableItems = ratings.sortedByOrdinal(),
selectedItems = rating.contentRating,
isLoading = false,
error = null,

View File

@@ -1,13 +1,14 @@
package org.koitharu.kotatsu.filter.ui
import kotlinx.coroutines.flow.StateFlow
import org.koitharu.kotatsu.core.model.GenericSortOrder
import org.koitharu.kotatsu.core.model.SortDirection
import org.koitharu.kotatsu.filter.ui.model.FilterHeaderModel
import org.koitharu.kotatsu.filter.ui.model.FilterProperty
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import java.util.Locale
interface MangaFilter : OnFilterChangedListener {
@@ -18,7 +19,9 @@ interface MangaFilter : OnFilterChangedListener {
val filterTagsExcluded: StateFlow<FilterProperty<MangaTag>>
val filterSortOrder: StateFlow<FilterProperty<SortOrder>>
val filterSortOrder: StateFlow<FilterProperty<GenericSortOrder>>
val filterSortDirection: StateFlow<FilterProperty<SortDirection>>
val filterState: StateFlow<FilterProperty<MangaState>>

View File

@@ -7,13 +7,17 @@ import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.annotation.IdRes
import androidx.core.view.isGone
import androidx.core.view.updatePadding
import androidx.fragment.app.FragmentManager
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.chip.Chip
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.GenericSortOrder
import org.koitharu.kotatsu.core.model.SortDirection
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.ui.model.titleRes
import org.koitharu.kotatsu.core.ui.model.direction
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
@@ -21,6 +25,7 @@ import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.parentView
import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.SheetFilterBinding
import org.koitharu.kotatsu.filter.ui.FilterOwner
@@ -30,12 +35,14 @@ import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.mapToSet
import java.util.EnumSet
import java.util.Locale
import com.google.android.material.R as materialR
class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
AdapterView.OnItemSelectedListener,
ChipsView.OnChipClickListener {
ChipsView.OnChipClickListener, MaterialButtonToggleGroup.OnButtonCheckedListener {
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {
return SheetFilterBinding.inflate(inflater, container, false)
@@ -51,6 +58,7 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
}
val filter = requireFilter()
filter.filterSortOrder.observe(viewLifecycleOwner, this::onSortOrderChanged)
filter.filterSortDirection.observe(viewLifecycleOwner, this::onSortDirectionChanged)
filter.filterLocale.observe(viewLifecycleOwner, this::onLocaleChanged)
filter.filterTags.observe(viewLifecycleOwner, this::onTagsChanged)
filter.filterTagsExcluded.observe(viewLifecycleOwner, this::onTagsExcludedChanged)
@@ -63,12 +71,24 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
binding.chipsContentRating.onChipClickListener = this
binding.chipsGenres.onChipClickListener = this
binding.chipsGenresExclude.onChipClickListener = this
binding.layoutSortDirection.addOnButtonCheckedListener(this)
}
override fun onButtonChecked(group: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean) {
if (isChecked) {
setSortDirection(getSortDirection(checkedId))
}
}
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
val filter = requireFilter()
when (parent.id) {
R.id.spinner_order -> filter.setSortOrder(filter.filterSortOrder.value.availableItems[position])
R.id.spinner_order -> {
val genericOrder = filter.filterSortOrder.value.availableItems[position]
val direction = getSortDirection(requireViewBinding().layoutSortDirection.checkedButtonId)
filter.setSortOrder(genericOrder[direction])
}
R.id.spinner_locale -> filter.setLanguage(filter.filterLocale.value.availableItems[position])
}
}
@@ -90,7 +110,7 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
}
}
private fun onSortOrderChanged(value: FilterProperty<SortOrder>) {
private fun onSortOrderChanged(value: FilterProperty<GenericSortOrder>) {
val b = viewBinding ?: return
b.textViewOrderTitle.isGone = value.isEmpty()
b.cardOrder.isGone = value.isEmpty()
@@ -102,7 +122,7 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
b.spinnerOrder.context,
android.R.layout.simple_spinner_dropdown_item,
android.R.id.text1,
value.availableItems.map { b.spinnerOrder.context.getString(it.titleRes) },
value.availableItems.map { b.spinnerOrder.context.getString(it.titleResId) },
)
val selectedIndex = value.availableItems.indexOf(selected)
if (selectedIndex >= 0) {
@@ -110,6 +130,25 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
}
}
private fun onSortDirectionChanged(value: FilterProperty<SortDirection>) {
val b = viewBinding ?: return
b.layoutSortDirection.isGone = value.isEmpty()
if (value.isEmpty()) {
return
}
val selected = value.selectedItems.single()
b.buttonOrderAsc.isEnabled = SortDirection.ASC in value.availableItems
b.buttonOrderDesc.isEnabled = SortDirection.DESC in value.availableItems
b.layoutSortDirection.removeOnButtonCheckedListener(this)
b.layoutSortDirection.check(
when (selected) {
SortDirection.ASC -> R.id.button_order_asc
SortDirection.DESC -> R.id.button_order_desc
},
)
b.layoutSortDirection.addOnButtonCheckedListener(this)
}
private fun onLocaleChanged(value: FilterProperty<Locale?>) {
val b = viewBinding ?: return
b.textViewLocaleTitle.isGone = value.isEmpty()
@@ -239,6 +278,19 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
private fun requireFilter() = (requireActivity() as FilterOwner).filter
private fun setSortDirection(direction: SortDirection) {
val filter = requireFilter()
val currentOrder = filter.filterSortOrder.value.selectedItems.single()
val newOrder = currentOrder[direction]
filter.setSortOrder(newOrder)
}
private fun getSortDirection(@IdRes buttonId: Int): SortDirection = when (buttonId) {
R.id.button_order_asc -> SortDirection.ASC
R.id.button_order_desc -> SortDirection.DESC
else -> throw IllegalArgumentException("Wrong button id $buttonId")
}
companion object {
private const val TAG = "FilterSheet"

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="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M19 17H22L18 21L14 17H17V3H19M2 17H12V19H2M6 5V7H2V5M2 11H9V13H2V11Z" />
</vector>

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="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M19 7H22L18 3L14 7H17V21H19M2 17H12V19H2M6 5V7H2V5M2 11H9V13H2V11Z" />
</vector>

View File

@@ -39,6 +39,7 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_order"
style="?materialCardViewOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_normal"
@@ -54,6 +55,37 @@
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/layout_sort_direction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:baselineAligned="false"
android:orientation="horizontal"
android:weightSum="2"
app:selectionRequired="true"
app:singleSelection="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_order_asc"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sort_order_asc"
app:icon="@drawable/ic_sort_asc" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_order_desc"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/sort_order_desc"
app:icon="@drawable/ic_sort_desc" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<TextView
android:id="@+id/textView_locale_title"
android:layout_width="match_parent"
@@ -67,6 +99,7 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_locale"
style="?materialCardViewOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_normal"

View File

@@ -113,6 +113,7 @@
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_order"
style="?materialCardViewOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
@@ -124,8 +125,8 @@
android:id="@+id/spinner_order"
android:layout_width="match_parent"
android:layout_height="56dp"
android:paddingHorizontal="8dp"
android:minHeight="?listPreferredItemHeightSmall" />
android:minHeight="?listPreferredItemHeightSmall"
android:paddingHorizontal="8dp" />
</com.google.android.material.card.MaterialCardView>

View File

@@ -686,4 +686,11 @@
<string name="skip_all">Skip all</string>
<string name="stuck">Stuck</string>
<string name="not_in_favorites">Not in favoites</string>
<string name="updated_long_ago">Updated long ago</string>
<string name="unpopular">Unpopular</string>
<string name="low_rating">Low rating</string>
<string name="sort_order_asc">Ascending</string>
<string name="sort_order_desc">Descending</string>
<string name="by_date">Date</string>
<string name="popularity">Popularity</string>
</resources>