feat: Toggle display of search operators with toolbar action (#836)

Default to hiding the search operators, and provide a new toolbar icon
(always visible) to show them.

The toolbar icon is displayed with a badge if any operators are present.

Adjust the operator display to three horizontal scrolling rows, to
further limit the maximum amount of vertical space the operators use.
This commit is contained in:
Nik Clayton 2024-07-24 18:51:00 +02:00 committed by GitHub
parent 5d574d4d76
commit 01831474dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 336 additions and 135 deletions

View File

@ -105,7 +105,7 @@
line="388"
column="28"/>
<location
file="${:core:activity*buildDir}/generated/res/resValues/orangeFdroid/debug/values/gradleResValues.xml"
file="${:core:activity*buildDir}/generated/res/resValues/blueFdroid/debug/values/gradleResValues.xml"
line="7"
column="5"
message="This definition does not require arguments"/>
@ -767,7 +767,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="282"
line="283"
column="5"/>
</issue>
@ -778,7 +778,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="333"
line="334"
column="5"/>
</issue>
@ -789,7 +789,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="452"
line="453"
column="5"/>
</issue>
@ -800,7 +800,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="628"
line="629"
column="5"/>
</issue>
@ -1196,7 +1196,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/donottranslate.xml"
line="273"
line="275"
column="19"/>
</issue>
@ -1207,7 +1207,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/donottranslate.xml"
line="278"
line="280"
column="19"/>
</issue>
@ -1372,7 +1372,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="198"
line="199"
column="13"/>
</issue>
@ -1383,7 +1383,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="219"
line="220"
column="13"/>
</issue>
@ -1394,7 +1394,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="220"
line="221"
column="13"/>
</issue>
@ -1405,7 +1405,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="243"
line="244"
column="13"/>
</issue>
@ -1416,7 +1416,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="272"
line="273"
column="13"/>
</issue>
@ -1427,7 +1427,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="343"
line="344"
column="13"/>
</issue>
@ -1438,7 +1438,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="383"
line="384"
column="13"/>
</issue>
@ -1449,7 +1449,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="419"
line="420"
column="13"/>
</issue>
@ -1460,7 +1460,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="422"
line="423"
column="13"/>
</issue>
@ -1471,7 +1471,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="423"
line="424"
column="13"/>
</issue>
@ -1482,7 +1482,7 @@
errorLine2=" ~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="424"
line="425"
column="13"/>
</issue>
@ -1493,7 +1493,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="425"
line="426"
column="13"/>
</issue>
@ -1504,7 +1504,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="426"
line="427"
column="13"/>
</issue>
@ -1515,7 +1515,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="438"
line="439"
column="13"/>
</issue>
@ -1526,7 +1526,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="439"
line="440"
column="13"/>
</issue>
@ -1537,7 +1537,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="490"
line="491"
column="13"/>
</issue>
@ -1548,7 +1548,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="491"
line="492"
column="13"/>
</issue>
@ -1559,7 +1559,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="502"
line="503"
column="13"/>
</issue>
@ -1570,7 +1570,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="503"
line="504"
column="13"/>
</issue>
@ -1581,7 +1581,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="504"
line="505"
column="13"/>
</issue>
@ -1592,7 +1592,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="507"
line="508"
column="13"/>
</issue>
@ -1603,7 +1603,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="545"
line="546"
column="13"/>
</issue>
@ -1614,7 +1614,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="586"
line="587"
column="13"/>
</issue>
@ -1625,7 +1625,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="592"
line="593"
column="13"/>
</issue>
@ -1636,7 +1636,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="620"
line="621"
column="13"/>
</issue>
@ -1647,7 +1647,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/strings.xml"
line="646"
line="647"
column="13"/>
</issue>

View File

@ -25,6 +25,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
@ -70,6 +71,7 @@ import app.pachli.components.search.adapter.SearchPagerAdapter
import app.pachli.core.activity.BottomSheetActivity
import app.pachli.core.common.extensions.hide
import app.pachli.core.common.extensions.show
import app.pachli.core.common.extensions.toggleVisibility
import app.pachli.core.common.extensions.viewBinding
import app.pachli.core.common.extensions.visible
import app.pachli.core.network.Server
@ -98,7 +100,11 @@ import app.pachli.databinding.SearchOperatorDateDialogBinding
import app.pachli.databinding.SearchOperatorFromDialogBinding
import app.pachli.databinding.SearchOperatorWhereLocationDialogBinding
import com.github.michaelbull.result.get
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import com.google.android.material.badge.ExperimentalBadgeUtils
import com.google.android.material.chip.Chip
import com.google.android.material.color.MaterialColors
import com.google.android.material.datepicker.CalendarConstraints
import com.google.android.material.datepicker.DateValidatorPointBackward
import com.google.android.material.datepicker.MaterialDatePicker
@ -125,6 +131,9 @@ class SearchActivity :
private lateinit var searchView: SearchView
val showFilterIcon: Boolean
get() = viewModel.availableOperators.value.isNotEmpty()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
@ -156,6 +165,7 @@ class SearchActivity :
* Binds the initial search operator chips UI and updates as the search
* operators change.
*/
@OptIn(ExperimentalBadgeUtils::class)
private fun bindOperators() {
val viewDataToChip: Map<Class<out SearchOperatorViewData<SearchOperator>>, Chip> = mapOf(
DateOperatorViewData::class.java to binding.chipDate,
@ -170,6 +180,18 @@ class SearchActivity :
WhereOperatorViewData::class.java to binding.chipWhere,
)
// Chips are initially hidden, toggled by the "filter" button
binding.chipsFilter.hide()
binding.chipsFilter2.hide()
binding.chipsFilter3.hide()
// Badge to draw on the filter button if any filters are active.
val filterBadgeDrawable = BadgeDrawable.create(this).apply {
text = "!"
backgroundColor = MaterialColors.getColor(binding.toolbar, com.google.android.material.R.attr.colorPrimary)
}
BadgeUtils.attachBadgeDrawable(filterBadgeDrawable, binding.toolbar, R.id.action_filter_search)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
launch {
@ -189,17 +211,28 @@ class SearchActivity :
}
}
launch {
viewModel.availableOperators.collectLatest {
invalidateOptionsMenu()
setSearchViewWidth(showFilterIcon)
}
}
launch {
viewModel.operatorViewData.collectLatest { operators ->
var showFilterBadgeDrawable = false
operators.forEach { viewData ->
viewDataToChip[viewData::class.java]?.let { chip ->
showFilterBadgeDrawable = showFilterBadgeDrawable or (viewData.operator.choice != null)
chip.isChecked = viewData.operator.choice != null
chip.setCloseIconVisible(viewData.operator.choice != null)
chip.text = viewData.chipLabel(this@SearchActivity)
}
viewModel.search()
}
filterBadgeDrawable.setVisible(showFilterBadgeDrawable)
viewModel.search()
}
}
}
@ -933,15 +966,35 @@ class SearchActivity :
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
super.onCreateMenu(menu, menuInflater)
menuInflater.inflate(R.menu.search_toolbar, menu)
menu.findItem(R.id.action_filter_search)?.apply {
icon = makeIcon(this@SearchActivity, GoogleMaterial.Icon.gmd_tune, IconicsSize.dp(20))
}
val searchViewMenuItem = menu.findItem(R.id.action_search)
searchViewMenuItem.expandActionView()
searchView = searchViewMenuItem.actionView as SearchView
bindSearchView()
}
override fun onPrepareMenu(menu: Menu) {
menu.findItem(R.id.action_filter_search)?.apply {
isVisible = showFilterIcon
}
return super<BottomSheetActivity>.onPrepareMenu(menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
super.onMenuItemSelected(menuItem)
return false
return when (menuItem.itemId) {
R.id.action_filter_search -> {
binding.chipsFilter.toggleVisibility()
binding.chipsFilter2.toggleVisibility()
binding.chipsFilter3.toggleVisibility()
true
}
else -> super.onMenuItemSelected(menuItem)
}
}
private fun getPageTitle(position: Int): CharSequence {
@ -965,6 +1018,27 @@ class SearchActivity :
searchView.setIconifiedByDefault(false)
searchView.setSearchableInfo((getSystemService(Context.SEARCH_SERVICE) as? SearchManager)?.getSearchableInfo(componentName))
setSearchViewWidth(showFilterIcon)
// Keep text that was entered also when switching to a different tab (before the search is executed)
searchView.setOnQueryTextListener(this)
searchView.setQuery(viewModel.currentSearchFieldContent ?: "", false)
// Only focus if the query is empty. This ensures that if the user is returning
// to the search results after visiting a result the full list is available,
// instead of being obscured by the keyboard.
if (viewModel.currentQuery.isBlank()) searchView.requestFocus()
}
/**
* Compute and set the width of [searchView].
*
* @param showingFilterIcon True if the filter icon is showing and the width should
* be adjusted to account for this.
*/
private fun setSearchViewWidth(showingFilterIcon: Boolean) {
if (!this::searchView.isInitialized) return
// SearchView has a bug. If it's displayed 'app:showAsAction="always"' it's too wide,
// pushing other icons (including the options menu '...' icon) off the edge of the
// screen.
@ -986,20 +1060,11 @@ class SearchActivity :
// It appears to be impossible to override this behaviour on API level < 33.
//
// SearchView does allow you to specify the maximum width. So take the screen width,
// subtract 48dp * 2 (for the menu icon and back icon on either side), convert to pixels,
// and use that.
// subtract 48dp * iconCount (for the menu, filter, and back icons), convert to pixels, and use that.
val iconCount = if (showingFilterIcon) 3 else 2
val pxScreenWidth = resources.displayMetrics.widthPixels
val pxBuffer = ((48 * 2) * resources.displayMetrics.density).toInt()
val pxBuffer = ((48 * iconCount) * resources.displayMetrics.density).toInt()
searchView.maxWidth = pxScreenWidth - pxBuffer
// Keep text that was entered also when switching to a different tab (before the search is executed)
searchView.setOnQueryTextListener(this)
searchView.setQuery(viewModel.currentSearchFieldContent ?: "", false)
// Only focus if the query is empty. This ensures that if the user is returning
// to the search results after visiting a result the full list is available,
// instead of being obscured by the keyboard.
if (viewModel.currentQuery.isBlank()) searchView.requestFocus()
}
override fun onQueryTextSubmit(query: String?): Boolean {

View File

@ -36,6 +36,20 @@ import app.pachli.components.search.adapter.SearchPagingSourceFactory
import app.pachli.core.accounts.AccountManager
import app.pachli.core.data.repository.ServerRepository
import app.pachli.core.database.model.AccountEntity
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_FROM
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE
import app.pachli.core.network.ServerOperation.ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE
import app.pachli.core.network.model.DeletedStatus
import app.pachli.core.network.model.Poll
import app.pachli.core.network.model.Status
@ -47,7 +61,9 @@ import app.pachli.viewdata.StatusViewData
import at.connyduck.calladapter.networkresult.NetworkResult
import at.connyduck.calladapter.networkresult.fold
import at.connyduck.calladapter.networkresult.onFailure
import com.github.michaelbull.result.mapBoth
import dagger.hilt.android.lifecycle.HiltViewModel
import io.github.z4kn4fein.semver.constraints.toConstraint
import javax.inject.Inject
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
@ -118,6 +134,68 @@ class SearchViewModel @Inject constructor(
null,
)
/**
* Set of operators the server supports.
*
* Empty set if the server does not support any operators.
*/
val availableOperators = serverRepository.flow.map { result ->
result.mapBoth(
{ server ->
buildSet {
val constraint100 = ">=1.0.0".toConstraint()
val canHasMedia = server.can(ORG_JOINMASTODON_SEARCH_QUERY_HAS_MEDIA, constraint100)
val canHasImage = server.can(ORG_JOINMASTODON_SEARCH_QUERY_HAS_IMAGE, constraint100)
val canHasVideo = server.can(ORG_JOINMASTODON_SEARCH_QUERY_HAS_VIDEO, constraint100)
val canHasAudio = server.can(ORG_JOINMASTODON_SEARCH_QUERY_HAS_AUDIO, constraint100)
if (canHasMedia || canHasImage || canHasVideo || canHasAudio) {
add(HasMediaOperator())
}
if (server.can(ORG_JOINMASTODON_SEARCH_QUERY_BY_DATE, constraint100)) {
add(DateOperator())
}
if (server.can(ORG_JOINMASTODON_SEARCH_QUERY_FROM, constraint100)) {
add(FromOperator())
}
if (server.can(ORG_JOINMASTODON_SEARCH_QUERY_LANGUAGE, constraint100)) {
add(LanguageOperator())
}
if (server.can(ORG_JOINMASTODON_SEARCH_QUERY_HAS_LINK, constraint100)) {
add(HasLinkOperator())
}
if (server.can(ORG_JOINMASTODON_SEARCH_QUERY_HAS_EMBED, constraint100)) {
add(HasEmbedOperator())
}
if (server.can(ORG_JOINMASTODON_SEARCH_QUERY_HAS_POLL, constraint100)) {
add(HasPollOperator())
}
if (server.can(ORG_JOINMASTODON_SEARCH_QUERY_IS_REPLY, constraint100)) {
add(IsReplyOperator())
}
if (server.can(ORG_JOINMASTODON_SEARCH_QUERY_IS_SENSITIVE, constraint100)) {
add(IsSensitiveOperator())
}
val canInLibrary = server.can(ORG_JOINMASTODON_SEARCH_QUERY_IN_LIBRARY, constraint100)
val canInPublic = server.can(ORG_JOINMASTODON_SEARCH_QUERY_IN_PUBLIC, constraint100)
if (canInLibrary || canInPublic) add(WhereOperator())
}
},
{
emptySet()
},
)
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
emptySet(),
)
private val loadedStatuses: MutableList<StatusViewData> = mutableListOf()
private val statusesPagingSourceFactory = SearchPagingSourceFactory(mastodonApi, SearchType.Status, loadedStatuses) {
@ -162,7 +240,7 @@ class SearchViewModel @Inject constructor(
* with [viewData].
*/
fun <T : SearchOperator> replaceOperator(viewData: SearchOperatorViewData<T>) = _operatorViewData.update { operators ->
operators.find { it.javaClass == viewData.javaClass }?.let { operators - it + viewData } ?: (operators + viewData)
operators.find { it.javaClass == viewData.javaClass }?.let { operators - it + viewData } ?: operators
}
fun search() {

View File

@ -21,100 +21,141 @@
app:layout_scrollFlags="scroll|snap|enterAlways"
app:navigationIcon="?attr/homeAsUpIndicator" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipsFilter"
android:layout_width="match_parent"
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
app:layout_scrollFlags="scroll|snap|enterAlways"
android:animateLayoutChanges="true">
android:scrollbars="none">
<com.google.android.material.chip.Chip
android:id="@+id/chipFrom"
style="@style/Widget.Material3.Chip.Suggestion"
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipsFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_from_all" />
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
app:singleLine="true"
android:animateLayoutChanges="true">
<com.google.android.material.chip.Chip
android:id="@+id/chipDate"
style="@style/Widget.Material3.Chip.Suggestion"
<com.google.android.material.chip.Chip
android:id="@+id/chipFrom"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_from_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chipDate"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_date_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chipLanguage"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_language_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chipWhere"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_where_all" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|enterAlways"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipsFilter2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_date_all" />
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
app:singleLine="true"
android:animateLayoutChanges="true">
<com.google.android.material.chip.Chip
android:id="@+id/chipLanguage"
style="@style/Widget.Material3.Chip.Suggestion"
<com.google.android.material.chip.Chip
android:id="@+id/chip_has_media"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_attach_file_24dp"
app:chipIconEnabled="true"
android:text="@string/search_operator_attachment_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_has_poll"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_attach_file_24dp"
app:chipIconEnabled="true"
android:text="@string/search_operator_poll_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_has_link"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_link_24"
app:chipIconEnabled="true"
android:text="@string/search_operator_link_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_has_embed"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_embed_all" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap|enterAlways"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipsFilter3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_language_all" />
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
app:singleLine="true"
android:animateLayoutChanges="true">
<com.google.android.material.chip.Chip
android:id="@+id/chip_has_media"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_attach_file_24dp"
app:chipIconEnabled="true"
android:text="@string/search_operator_attachment_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_is_reply"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_reply_all_24dp"
app:chipIconEnabled="true"
android:text="@string/search_operator_replies_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_is_reply"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_reply_all_24dp"
app:chipIconEnabled="true"
android:text="@string/search_operator_replies_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_is_sensitive"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_eye_24dp"
app:chipIconEnabled="true"
android:text="@string/search_operator_sensitive_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_has_poll"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_attach_file_24dp"
app:chipIconEnabled="true"
android:text="@string/search_operator_poll_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_has_embed"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_embed_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chip_has_link"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_link_24"
app:chipIconEnabled="true"
android:text="@string/search_operator_link_all" />
<com.google.android.material.chip.Chip
android:id="@+id/chipWhere"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIconEnabled="true"
android:text="@string/search_operator_where_all" />
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.chip.Chip
android:id="@+id/chip_is_sensitive"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipIcon="@drawable/ic_eye_24dp"
app:chipIconEnabled="true"
android:text="@string/search_operator_sensitive_all" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
@ -8,5 +9,12 @@
android:icon="@android:drawable/ic_menu_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:actionLayout="@layout/search_view"
app:showAsAction="always" />
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/action_filter_search"
android:title="@string/action_filter_search"
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
</menu>

View File

@ -165,6 +165,7 @@
<string name="action_accept">Accept</string>
<string name="action_reject">Reject</string>
<string name="action_search">Search</string>
<string name="action_filter_search">Filter search</string>
<string name="action_access_drafts">Drafts</string>
<string name="action_access_scheduled_posts">Scheduled posts</string>
<string name="action_toggle_visibility">Post visibility</string>

View File

@ -30,3 +30,11 @@ fun View.hide() {
fun View.visible(visible: Boolean, or: Int = View.GONE) {
this.visibility = if (visible) View.VISIBLE else or
}
fun View.toggleVisibility() {
when (this.visibility) {
View.GONE -> this.show()
View.INVISIBLE -> this.show()
View.VISIBLE -> this.hide()
}
}

View File

@ -8,7 +8,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/styles.xml"
line="134"
line="137"
column="42"/>
</issue>
@ -19,7 +19,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/res/values/styles.xml"
line="135"
line="138"
column="43"/>
</issue>