Update manga list header
This commit is contained in:
@@ -9,6 +9,7 @@ import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipDrawable
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.utils.ext.castOrNull
|
||||
|
||||
class ChipsView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
@@ -18,10 +19,10 @@ class ChipsView @JvmOverloads constructor(
|
||||
|
||||
private var isLayoutSuppressedCompat = false
|
||||
private var isLayoutCalledOnSuppressed = false
|
||||
private var chipOnClickListener = OnClickListener {
|
||||
private val chipOnClickListener = OnClickListener {
|
||||
onChipClickListener?.onChipClick(it as Chip, it.tag)
|
||||
}
|
||||
private var chipOnCloseListener = OnClickListener {
|
||||
private val chipOnCloseListener = OnClickListener {
|
||||
onChipCloseClickListener?.onChipCloseClick(it as Chip, it.tag)
|
||||
}
|
||||
var onChipClickListener: OnChipClickListener? = null
|
||||
@@ -60,15 +61,27 @@ class ChipsView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> getCheckedData(cls: Class<T>): Set<T> {
|
||||
val result = LinkedHashSet<T>(childCount)
|
||||
for (child in children) {
|
||||
if (child is Chip && child.isChecked) {
|
||||
result += cls.castOrNull(child.tag) ?: continue
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun bindChip(chip: Chip, model: ChipModel) {
|
||||
chip.text = model.title
|
||||
if (model.icon == 0) {
|
||||
chip.isChipIconVisible = false
|
||||
} else {
|
||||
chip.isCheckedIconVisible = true
|
||||
chip.isChipIconVisible = true
|
||||
chip.setChipIconResource(model.icon)
|
||||
}
|
||||
chip.isClickable = onChipClickListener != null
|
||||
chip.isClickable = onChipClickListener != null || model.isCheckable
|
||||
chip.isCheckable = model.isCheckable
|
||||
chip.isChecked = model.isChecked
|
||||
chip.tag = model.data
|
||||
}
|
||||
|
||||
@@ -76,11 +89,12 @@ class ChipsView @JvmOverloads constructor(
|
||||
val chip = Chip(context)
|
||||
val drawable = ChipDrawable.createFromAttributes(context, null, 0, R.style.Widget_Kotatsu_Chip)
|
||||
chip.setChipDrawable(drawable)
|
||||
chip.isCheckedIconVisible = true
|
||||
chip.setCheckedIconResource(R.drawable.ic_check)
|
||||
chip.isCloseIconVisible = onChipCloseClickListener != null
|
||||
chip.setOnCloseIconClickListener(chipOnCloseListener)
|
||||
chip.setEnsureMinTouchTargetSize(false)
|
||||
chip.setOnClickListener(chipOnClickListener)
|
||||
chip.isCheckable = false
|
||||
addView(chip)
|
||||
return chip
|
||||
}
|
||||
@@ -98,7 +112,9 @@ class ChipsView @JvmOverloads constructor(
|
||||
class ChipModel(
|
||||
@DrawableRes val icon: Int,
|
||||
val title: CharSequence,
|
||||
val data: Any? = null
|
||||
val isCheckable: Boolean,
|
||||
val isChecked: Boolean,
|
||||
val data: Any? = null,
|
||||
) {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -109,6 +125,8 @@ class ChipsView @JvmOverloads constructor(
|
||||
|
||||
if (icon != other.icon) return false
|
||||
if (title != other.title) return false
|
||||
if (isCheckable != other.isCheckable) return false
|
||||
if (isChecked != other.isChecked) return false
|
||||
if (data != other.data) return false
|
||||
|
||||
return true
|
||||
@@ -117,7 +135,9 @@ class ChipsView @JvmOverloads constructor(
|
||||
override fun hashCode(): Int {
|
||||
var result = icon
|
||||
result = 31 * result + title.hashCode()
|
||||
result = 31 * result + data.hashCode()
|
||||
result = 31 * result + isCheckable.hashCode()
|
||||
result = 31 * result + isChecked.hashCode()
|
||||
result = 31 * result + (data?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,6 +341,8 @@ class DetailsFragment :
|
||||
title = tag.title,
|
||||
icon = 0,
|
||||
data = tag,
|
||||
isCheckable = false,
|
||||
isChecked = false,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -193,8 +193,8 @@ abstract class MangaListFragment :
|
||||
resolveException(error)
|
||||
}
|
||||
|
||||
override fun onTagRemoveClick(tag: MangaTag) {
|
||||
viewModel.onRemoveFilterTag(tag)
|
||||
override fun onUpdateFilter(tags: Set<MangaTag>) {
|
||||
viewModel.onUpdateFilter(tags)
|
||||
}
|
||||
|
||||
private fun onGridScaleChanged(scale: Float) {
|
||||
|
||||
@@ -25,7 +25,7 @@ abstract class MangaListViewModel(
|
||||
valueProducer = { gridSize / 100f },
|
||||
)
|
||||
|
||||
open fun onRemoveFilterTag(tag: MangaTag) = Unit
|
||||
open fun onUpdateFilter(tags: Set<MangaTag>) = Unit
|
||||
|
||||
protected fun createListModeFlow() = settings.observeAsFlow(AppSettings.KEY_LIST_MODE) { listMode }
|
||||
.onEach {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.list.ui.model.CurrentFilterModel
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
|
||||
fun currentFilterAD(
|
||||
listener: MangaListListener,
|
||||
) = adapterDelegate<CurrentFilterModel, ListModel>(R.layout.item_current_filter) {
|
||||
|
||||
val chipGroup = itemView as ChipsView
|
||||
|
||||
chipGroup.onChipCloseClickListener = ChipsView.OnChipCloseClickListener { _, data ->
|
||||
listener.onTagRemoveClick(data as? MangaTag ?: return@OnChipCloseClickListener)
|
||||
}
|
||||
|
||||
bind {
|
||||
chipGroup.setChips(item.chips)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.titleRes
|
||||
import org.koitharu.kotatsu.databinding.ItemHeader2Binding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListHeader2
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
|
||||
|
||||
fun listHeader2AD(
|
||||
listener: MangaListListener,
|
||||
) = adapterDelegateViewBinding<ListHeader2, ListModel, ItemHeader2Binding>(
|
||||
{ layoutInflater, parent -> ItemHeader2Binding.inflate(layoutInflater, parent, false) }
|
||||
) {
|
||||
|
||||
var ignoreChecking = false
|
||||
binding.textViewFilter.setOnClickListener {
|
||||
listener.onFilterClick()
|
||||
}
|
||||
binding.chipsTags.setOnCheckedStateChangeListener { _, _ ->
|
||||
if (!ignoreChecking) {
|
||||
listener.onUpdateFilter(binding.chipsTags.getCheckedData(MangaTag::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
bind { payloads ->
|
||||
if (payloads.isNotEmpty()) {
|
||||
binding.scrollView.smoothScrollTo(0, 0)
|
||||
}
|
||||
ignoreChecking = true
|
||||
binding.chipsTags.setChips(item.chips)
|
||||
ignoreChecking = false
|
||||
binding.textViewFilter.setTextAndVisible(item.sortOrder?.titleRes ?: 0)
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class MangaListAdapter(
|
||||
.addDelegate(ITEM_TYPE_ERROR_FOOTER, errorFooterAD(listener))
|
||||
.addDelegate(ITEM_TYPE_EMPTY, emptyStateListAD(listener))
|
||||
.addDelegate(ITEM_TYPE_HEADER, listHeaderAD())
|
||||
.addDelegate(ITEM_TYPE_FILTER, currentFilterAD(listener))
|
||||
.addDelegate(ITEM_TYPE_HEADER_2, listHeader2AD(listener))
|
||||
.addDelegate(ITEM_TYPE_HEADER_FILTER, listHeaderWithFilterAD(listener))
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class MangaListAdapter(
|
||||
Unit
|
||||
}
|
||||
}
|
||||
is CurrentFilterModel -> Unit
|
||||
is ListHeader2 -> Unit
|
||||
else -> super.getChangePayload(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ class MangaListAdapter(
|
||||
const val ITEM_TYPE_ERROR_FOOTER = 7
|
||||
const val ITEM_TYPE_EMPTY = 8
|
||||
const val ITEM_TYPE_HEADER = 9
|
||||
const val ITEM_TYPE_FILTER = 10
|
||||
const val ITEM_TYPE_HEADER_2 = 10
|
||||
const val ITEM_TYPE_HEADER_FILTER = 11
|
||||
|
||||
val PAYLOAD_PROGRESS = Any()
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
|
||||
interface MangaListListener : OnListItemClickListener<Manga>, ListStateHolderListener {
|
||||
|
||||
fun onTagRemoveClick(tag: MangaTag)
|
||||
|
||||
fun onUpdateFilter(tags: Set<MangaTag>)
|
||||
|
||||
fun onFilterClick()
|
||||
}
|
||||
@@ -56,12 +56,6 @@ class FilterCoordinator(
|
||||
|
||||
fun observeState() = currentState.asStateFlow()
|
||||
|
||||
fun removeTag(tag: MangaTag) {
|
||||
currentState.update { oldValue ->
|
||||
FilterState(oldValue.sortOrder, oldValue.tags - tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTags(tags: Set<MangaTag>) {
|
||||
currentState.update { oldValue ->
|
||||
FilterState(oldValue.sortOrder, tags)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.koitharu.kotatsu.list.ui.model
|
||||
|
||||
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
|
||||
data class CurrentFilterModel(
|
||||
data class ListHeader2(
|
||||
val chips: Collection<ChipsView.ChipModel>,
|
||||
val sortOrder: SortOrder?,
|
||||
) : ListModel
|
||||
@@ -14,6 +14,7 @@ val remoteListModule
|
||||
repository = MangaRepository(params[0]) as RemoteMangaRepository,
|
||||
settings = get(),
|
||||
dataRepository = get(),
|
||||
searchRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,16 @@ import org.koitharu.kotatsu.list.ui.filter.OnFilterChangedListener
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import java.util.*
|
||||
|
||||
private const val FILTER_MIN_INTERVAL = 750L
|
||||
private const val FILTER_MIN_INTERVAL = 250L
|
||||
|
||||
class RemoteListViewModel(
|
||||
private val repository: RemoteMangaRepository,
|
||||
private val searchRepository: MangaSearchRepository,
|
||||
settings: AppSettings,
|
||||
dataRepository: MangaDataRepository,
|
||||
) : MangaListViewModel(settings), OnFilterChangedListener {
|
||||
@@ -46,9 +49,8 @@ class RemoteListViewModel(
|
||||
listError,
|
||||
hasNextPage,
|
||||
) { list, mode, filterState, error, hasNext ->
|
||||
buildList(list?.size?.plus(3) ?: 3) {
|
||||
add(ListHeader(repository.source.title, 0, filterState.sortOrder))
|
||||
createFilterModel(filterState)?.let { add(it) }
|
||||
buildList(list?.size?.plus(2) ?: 2) {
|
||||
add(ListHeader2(createChipsList(filterState), filterState.sortOrder))
|
||||
when {
|
||||
list.isNullOrEmpty() && error != null -> add(error.toErrorState(canRetry = true))
|
||||
list == null -> add(LoadingState)
|
||||
@@ -88,10 +90,6 @@ class RemoteListViewModel(
|
||||
loadList(filter.snapshot(), append = !mangaList.value.isNullOrEmpty())
|
||||
}
|
||||
|
||||
override fun onRemoveFilterTag(tag: MangaTag) {
|
||||
filter.removeTag(tag)
|
||||
}
|
||||
|
||||
override fun onSortItemClick(item: FilterItem.Sort) {
|
||||
filter.onSortItemClick(item)
|
||||
}
|
||||
@@ -110,6 +108,10 @@ class RemoteListViewModel(
|
||||
|
||||
fun resetFilter() = filter.reset()
|
||||
|
||||
override fun onUpdateFilter(tags: Set<MangaTag>) {
|
||||
applyFilter(tags)
|
||||
}
|
||||
|
||||
fun applyFilter(tags: Set<MangaTag>) {
|
||||
filter.setTags(tags)
|
||||
}
|
||||
@@ -142,18 +144,41 @@ class RemoteListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFilterModel(filterState: FilterState): CurrentFilterModel? {
|
||||
return if (filterState.tags.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
CurrentFilterModel(filterState.tags.map { ChipsView.ChipModel(0, it.title, it) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEmptyState(filterState: FilterState) = EmptyState(
|
||||
icon = R.drawable.ic_empty_search,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = 0,
|
||||
actionStringRes = if (filterState.tags.isEmpty()) 0 else R.string.reset_filter,
|
||||
)
|
||||
|
||||
private suspend fun createChipsList(filterState: FilterState): List<ChipsView.ChipModel> {
|
||||
val selectedTags = filterState.tags.toMutableSet()
|
||||
val tags = searchRepository.getTagsSuggestion("", 6, repository.source)
|
||||
val result = LinkedList<ChipsView.ChipModel>()
|
||||
for (tag in tags) {
|
||||
val model = ChipsView.ChipModel(
|
||||
icon = 0,
|
||||
title = tag.title,
|
||||
isCheckable = true,
|
||||
isChecked = selectedTags.remove(tag),
|
||||
data = tag,
|
||||
)
|
||||
if (model.isChecked) {
|
||||
result.addFirst(model)
|
||||
} else {
|
||||
result.addLast(model)
|
||||
}
|
||||
}
|
||||
for (tag in selectedTags) {
|
||||
val model = ChipsView.ChipModel(
|
||||
icon = 0,
|
||||
title = tag.title,
|
||||
isCheckable = true,
|
||||
isChecked = true,
|
||||
data = tag,
|
||||
)
|
||||
result.addFirst(model)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,7 @@ package org.koitharu.kotatsu.search.ui
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
@@ -28,13 +26,14 @@ class MangaListActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
setContentView(ActivityContainerBinding.inflate(layoutInflater))
|
||||
val tags = intent.getParcelableExtra<ParcelableMangaTags>(EXTRA_TAGS)?.tags
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
val source = intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: tags?.firstOrNull()?.source
|
||||
if (source == null) {
|
||||
finishAfterTransition()
|
||||
return
|
||||
}
|
||||
title = source.title
|
||||
val fm = supportFragmentManager
|
||||
if (fm.findFragmentById(R.id.container) == null) {
|
||||
val source = intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: tags?.firstOrNull()?.source
|
||||
if (source == null) {
|
||||
finishAfterTransition()
|
||||
return
|
||||
}
|
||||
fm.commit {
|
||||
val fragment = if (source == MangaSource.LOCAL) {
|
||||
LocalListFragment.newInstance()
|
||||
|
||||
@@ -103,7 +103,7 @@ class MultiSearchActivity : BaseActivity<ActivitySearchMultiBinding>(), MangaLis
|
||||
viewModel.doSearch(viewModel.query.value.orEmpty())
|
||||
}
|
||||
|
||||
override fun onTagRemoveClick(tag: MangaTag) = Unit
|
||||
override fun onUpdateFilter(tags: Set<MangaTag>) = Unit
|
||||
|
||||
override fun onFilterClick() = Unit
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class SearchSuggestionViewModel(
|
||||
setupSuggestion()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun onSourceToggle(source: MangaSource, isEnabled: Boolean) {
|
||||
settings.hiddenSources = if (isEnabled) {
|
||||
settings.hiddenSources - source.name
|
||||
@@ -113,6 +113,8 @@ class SearchSuggestionViewModel(
|
||||
icon = 0,
|
||||
title = tag.title,
|
||||
data = tag,
|
||||
isCheckable = false,
|
||||
isChecked = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,12 @@ import org.koitharu.kotatsu.databinding.FragmentFeedBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.main.ui.AppBarOwner
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.tracker.ui.adapter.FeedAdapter
|
||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||
import org.koitharu.kotatsu.utils.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.measureHeight
|
||||
|
||||
class FeedFragment :
|
||||
BaseFragment<FragmentFeedBinding>(),
|
||||
@@ -84,7 +82,7 @@ class FeedFragment :
|
||||
|
||||
override fun onRetryClick(error: Throwable) = Unit
|
||||
|
||||
override fun onTagRemoveClick(tag: MangaTag) = Unit
|
||||
override fun onUpdateFilter(tags: Set<MangaTag>) = Unit
|
||||
|
||||
override fun onFilterClick() = Unit
|
||||
|
||||
|
||||
11
app/src/main/java/org/koitharu/kotatsu/utils/ext/OtherExt.kt
Normal file
11
app/src/main/java/org/koitharu/kotatsu/utils/ext/OtherExt.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.icu.lang.UCharacter.GraphemeClusterBreak.T
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> Class<T>.castOrNull(obj: Any?): T? {
|
||||
if (obj == null || !isInstance(obj)) {
|
||||
return null
|
||||
}
|
||||
return obj as T
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/chips_tags"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:closeIconEnabled="true" />
|
||||
47
app/src/main/res/layout/item_header_2.xml
Normal file
47
app/src/main/res/layout/item_header_2.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
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">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:layout_toStartOf="@id/textView_filter"
|
||||
android:requiresFadingEdge="horizontal"
|
||||
android:scrollbars="none">
|
||||
|
||||
<org.koitharu.kotatsu.base.ui.widgets.ChipsView
|
||||
android:id="@+id/chips_tags"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="@dimen/margin_small"
|
||||
app:selectionRequired="false"
|
||||
app:singleLine="true"
|
||||
app:singleSelection="false" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_filter"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/list_selector"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="6dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
|
||||
app:drawableEndCompat="@drawable/ic_expand_more"
|
||||
app:drawableTint="?android:attr/textColorSecondary"
|
||||
tools:ignore="RtlSymmetry"
|
||||
tools:text="@string/popular" />
|
||||
|
||||
</RelativeLayout>
|
||||
Reference in New Issue
Block a user