mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 21:05:28 +00:00
Add remove item and clear history buttons to search history (#7816)
* Add remove and clear history buttons to search * Rename `SearchHistoryResultItem` to `SearchHistoryListItem` and `MessagesSearchResultItem` to `MessageSearchListItem` Also, add some docs. * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
d589ff37dc
commit
6cde0706be
+7
-1
@@ -29,5 +29,11 @@ sealed interface GlobalSearchEvent {
|
||||
data class SaveRoomToHistory(val roomId: RoomId) : GlobalSearchEvent
|
||||
|
||||
/** The user selected a search result from the search history. */
|
||||
data class SearchHistoryResultSelected(val resultItem: SearchHistoryResultItem) : GlobalSearchEvent
|
||||
data class SearchHistoryResultSelected(val resultItem: SearchHistoryListItem) : GlobalSearchEvent
|
||||
|
||||
/** The user removed a search history entry. */
|
||||
data class RemoveSearchHistoryResult(val result: SearchHistoryListItem) : GlobalSearchEvent
|
||||
|
||||
/** The user cleared the search history. */
|
||||
data object ClearSearchHistory : GlobalSearchEvent
|
||||
}
|
||||
|
||||
+22
-9
@@ -57,6 +57,7 @@ import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -87,14 +88,14 @@ class GlobalSearchPresenter(
|
||||
var currentTarget: GlobalSearchTarget by remember { mutableStateOf(GlobalSearchTarget.ROOMS) }
|
||||
val currentMessageSearch: MessageSearch = remember { messageSearchService.createMessageSearch(scope = coroutineScope) }
|
||||
|
||||
val searchHistory by produceState<AsyncData<ImmutableList<SearchHistoryResultItem>>>(initialValue = AsyncData.Uninitialized) {
|
||||
val searchHistory by produceState<AsyncData<ImmutableList<SearchHistoryListItem>>>(initialValue = AsyncData.Uninitialized) {
|
||||
searchHistoryStore.history.collectLatest { history ->
|
||||
val mappedHistoryItems = history.mapNotNull { result ->
|
||||
when (result) {
|
||||
is SearchHistoryResult.Query -> SearchHistoryResultItem.Query(term = result.term)
|
||||
is SearchHistoryResult.Query -> SearchHistoryListItem.Query(term = result.term)
|
||||
is SearchHistoryResult.Room -> {
|
||||
val roomInfo = matrixClient.getRoomInfo(result.roomId).getOrNull() ?: return@mapNotNull null
|
||||
SearchHistoryResultItem.Room(roomId = result.roomId, roomInfo = roomInfo)
|
||||
SearchHistoryListItem.Room(roomId = result.roomId, roomInfo = roomInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,7 +237,7 @@ class GlobalSearchPresenter(
|
||||
}
|
||||
is GlobalSearchEvent.SearchHistoryResultSelected -> {
|
||||
when (event.resultItem) {
|
||||
is SearchHistoryResultItem.Query -> coroutineScope.launch {
|
||||
is SearchHistoryListItem.Query -> coroutineScope.launch {
|
||||
delay(300.milliseconds)
|
||||
queryState.edit {
|
||||
replace(0, length, event.resultItem.term)
|
||||
@@ -244,9 +245,21 @@ class GlobalSearchPresenter(
|
||||
searchResults = AsyncData.Loading()
|
||||
currentTarget = GlobalSearchTarget.ROOMS
|
||||
}
|
||||
is SearchHistoryResultItem.Room -> Unit
|
||||
is SearchHistoryListItem.Room -> Unit
|
||||
}
|
||||
}
|
||||
is GlobalSearchEvent.RemoveSearchHistoryResult -> coroutineScope.launch {
|
||||
val currentHistory = searchHistoryStore.history.first()
|
||||
val id = when (event.result) {
|
||||
is SearchHistoryListItem.Query -> event.result.term
|
||||
is SearchHistoryListItem.Room -> event.result.roomId.value
|
||||
}
|
||||
val result = currentHistory.find { it.id == id } ?: return@launch
|
||||
searchHistoryStore.remove(result)
|
||||
}
|
||||
GlobalSearchEvent.ClearSearchHistory -> coroutineScope.launch {
|
||||
searchHistoryStore.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +278,7 @@ class GlobalSearchPresenter(
|
||||
result: MessageSearchResult,
|
||||
roomInfo: RoomInfo,
|
||||
formattedTimestamp: String,
|
||||
): MessageSearchResultItem.Message? {
|
||||
): MessageSearchListItem.Message? {
|
||||
val body = latestEventFormatter.format(
|
||||
latestEvent = LatestEventValue.Remote(
|
||||
timestamp = result.timestamp,
|
||||
@@ -276,7 +289,7 @@ class GlobalSearchPresenter(
|
||||
),
|
||||
isDmRoom = false,
|
||||
)
|
||||
return MessageSearchResultItem.Message(
|
||||
return MessageSearchListItem.Message(
|
||||
messageSearchResult = result,
|
||||
body = body?.toString() ?: "",
|
||||
formattedTimestamp = formattedTimestamp,
|
||||
@@ -288,7 +301,7 @@ class GlobalSearchPresenter(
|
||||
result: MessageSearchResult,
|
||||
roomInfo: RoomInfo,
|
||||
formattedTimestamp: String,
|
||||
): MessageSearchResultItem.Media? {
|
||||
): MessageSearchListItem.Media? {
|
||||
val messageType = when (val content = result.content) {
|
||||
is MessageContent if content.type is MessageTypeWithAttachment -> content.type as MessageTypeWithAttachment
|
||||
else -> return null
|
||||
@@ -342,7 +355,7 @@ class GlobalSearchPresenter(
|
||||
blurhash = blurhash
|
||||
)
|
||||
|
||||
return MessageSearchResultItem.Media(
|
||||
return MessageSearchListItem.Media(
|
||||
messageSearchResult = result,
|
||||
mediaContent = mediaContent,
|
||||
formattedTimestamp = formattedTimestamp,
|
||||
|
||||
+31
-8
@@ -28,20 +28,26 @@ data class GlobalSearchState(
|
||||
val queryState: TextFieldState,
|
||||
val currentTarget: GlobalSearchTarget,
|
||||
val results: AsyncData<GlobalSearchResults>,
|
||||
val history: AsyncData<ImmutableList<SearchHistoryResultItem>>,
|
||||
val history: AsyncData<ImmutableList<SearchHistoryListItem>>,
|
||||
val eventSink: (GlobalSearchEvent) -> Unit
|
||||
)
|
||||
|
||||
/**
|
||||
* The target of the global search: either rooms or messages.
|
||||
*/
|
||||
@Immutable
|
||||
enum class GlobalSearchTarget {
|
||||
ROOMS,
|
||||
MESSAGES,
|
||||
}
|
||||
|
||||
/**
|
||||
* The results of a global search, either a list of rooms or a list of messages.
|
||||
*/
|
||||
@Immutable
|
||||
sealed interface GlobalSearchResults {
|
||||
data class RoomListResults(val results: ImmutableList<RoomListRoomSummary>) : GlobalSearchResults
|
||||
data class MessageSearchResults(val results: ImmutableList<MessageSearchResultItem>) : GlobalSearchResults
|
||||
data class MessageSearchResults(val results: ImmutableList<MessageSearchListItem>) : GlobalSearchResults
|
||||
|
||||
fun isEmpty(): Boolean = when (this) {
|
||||
is RoomListResults -> results.isEmpty()
|
||||
@@ -49,7 +55,10 @@ sealed interface GlobalSearchResults {
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface MessageSearchResultItem {
|
||||
/**
|
||||
* A single item in the message search results. It's the UI representation of [MessageSearchResult], which is the mapped model returned by the Matrix SDK.
|
||||
*/
|
||||
sealed interface MessageSearchListItem {
|
||||
val roomId: RoomId
|
||||
val eventId: EventId
|
||||
val senderId: UserId
|
||||
@@ -57,24 +66,30 @@ sealed interface MessageSearchResultItem {
|
||||
val roomInfo: RoomInfo
|
||||
val formattedTimestamp: String
|
||||
|
||||
/**
|
||||
* A message item in the message search results.
|
||||
*/
|
||||
data class Message(
|
||||
val messageSearchResult: MessageSearchResult,
|
||||
val body: String,
|
||||
override val roomInfo: RoomInfo,
|
||||
override val formattedTimestamp: String,
|
||||
) : MessageSearchResultItem {
|
||||
) : MessageSearchListItem {
|
||||
override val roomId: RoomId = messageSearchResult.roomId
|
||||
override val eventId: EventId = messageSearchResult.eventId
|
||||
override val senderId: UserId = messageSearchResult.senderId
|
||||
override val senderName: String = messageSearchResult.senderProfile.getDisambiguatedDisplayName(senderId)
|
||||
}
|
||||
|
||||
/**
|
||||
* A message with media item in the message search results.
|
||||
*/
|
||||
data class Media(
|
||||
val messageSearchResult: MessageSearchResult,
|
||||
val mediaContent: MediaSearchResultContent,
|
||||
override val roomInfo: RoomInfo,
|
||||
override val formattedTimestamp: String,
|
||||
) : MessageSearchResultItem {
|
||||
) : MessageSearchListItem {
|
||||
override val roomId: RoomId = messageSearchResult.roomId
|
||||
override val eventId: EventId = messageSearchResult.eventId
|
||||
override val senderId: UserId = messageSearchResult.senderId
|
||||
@@ -82,20 +97,28 @@ sealed interface MessageSearchResultItem {
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface SearchHistoryResultItem {
|
||||
/**
|
||||
* A single entry in the global search history, displayed in the list of recent searches.
|
||||
*
|
||||
* It's the UI representation of [io.element.android.features.home.impl.search.history.SearchHistoryResult], which is the persisted model.
|
||||
*/
|
||||
sealed interface SearchHistoryListItem {
|
||||
val id: String
|
||||
|
||||
data class Query(val term: String) : SearchHistoryResultItem {
|
||||
data class Query(val term: String) : SearchHistoryListItem {
|
||||
override val id: String = "query:$term"
|
||||
}
|
||||
data class Room(
|
||||
val roomId: RoomId,
|
||||
val roomInfo: RoomInfo,
|
||||
) : SearchHistoryResultItem {
|
||||
) : SearchHistoryListItem {
|
||||
override val id: String = "room:${roomId.value}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the media content of a message search result.
|
||||
*/
|
||||
data class MediaSearchResultContent(
|
||||
val filename: String,
|
||||
val extension: String?,
|
||||
|
||||
+9
-9
@@ -79,13 +79,13 @@ class GlobalSearchStatePreviewParam : PreviewParameterProvider<GlobalSearchState
|
||||
currentTarget = GlobalSearchTarget.MESSAGES,
|
||||
queryState = TextFieldState("Query"),
|
||||
results = AsyncData.Success(GlobalSearchResults.MessageSearchResults(persistentListOf(
|
||||
MessageSearchResultItem.Message(
|
||||
MessageSearchListItem.Message(
|
||||
messageSearchResult = aMessageSearchResult(eventId = EventId("\$eventId1:server.org")),
|
||||
body = "A message with Query",
|
||||
roomInfo = aRoomInfo(),
|
||||
formattedTimestamp = "12:00",
|
||||
),
|
||||
MessageSearchResultItem.Media(
|
||||
MessageSearchListItem.Media(
|
||||
messageSearchResult = aMessageSearchResult(eventId = EventId("\$eventId2:server.org")),
|
||||
mediaContent = MediaSearchResultContent(
|
||||
filename = "file.png",
|
||||
@@ -114,9 +114,9 @@ class GlobalSearchStatePreviewParam : PreviewParameterProvider<GlobalSearchState
|
||||
results = AsyncData.Uninitialized,
|
||||
history = AsyncData.Success(
|
||||
persistentListOf(
|
||||
SearchHistoryResultItem.Query("Query 1"),
|
||||
SearchHistoryResultItem.Query("Query 2"),
|
||||
SearchHistoryResultItem.Room(
|
||||
SearchHistoryListItem.Query("Query 1"),
|
||||
SearchHistoryListItem.Query("Query 2"),
|
||||
SearchHistoryListItem.Room(
|
||||
roomId = roomId1,
|
||||
roomInfo = aRoomInfo(
|
||||
id = roomId1,
|
||||
@@ -125,7 +125,7 @@ class GlobalSearchStatePreviewParam : PreviewParameterProvider<GlobalSearchState
|
||||
canonicalAlias = aCanonicalAlias,
|
||||
)
|
||||
),
|
||||
SearchHistoryResultItem.Room(
|
||||
SearchHistoryListItem.Room(
|
||||
roomId = roomId2,
|
||||
roomInfo = aRoomInfo(
|
||||
id = roomId2,
|
||||
@@ -133,7 +133,7 @@ class GlobalSearchStatePreviewParam : PreviewParameterProvider<GlobalSearchState
|
||||
avatarUrl = AN_AVATAR_URL,
|
||||
)
|
||||
),
|
||||
SearchHistoryResultItem.Room(
|
||||
SearchHistoryListItem.Room(
|
||||
roomId = dmRoomId,
|
||||
roomInfo = aRoomInfo(
|
||||
id = dmRoomId,
|
||||
@@ -144,7 +144,7 @@ class GlobalSearchStatePreviewParam : PreviewParameterProvider<GlobalSearchState
|
||||
canonicalAlias = aCanonicalAlias,
|
||||
)
|
||||
),
|
||||
SearchHistoryResultItem.Room(
|
||||
SearchHistoryListItem.Room(
|
||||
roomId = tombstonedRoomId,
|
||||
roomInfo = aRoomInfo(
|
||||
id = tombstonedRoomId,
|
||||
@@ -300,7 +300,7 @@ internal fun aGlobalSearchState(
|
||||
queryState: TextFieldState = TextFieldState(),
|
||||
currentTarget: GlobalSearchTarget = GlobalSearchTarget.ROOMS,
|
||||
results: AsyncData<GlobalSearchResults> = AsyncData.Uninitialized,
|
||||
history: AsyncData<ImmutableList<SearchHistoryResultItem>> = AsyncData.Uninitialized,
|
||||
history: AsyncData<ImmutableList<SearchHistoryListItem>> = AsyncData.Uninitialized,
|
||||
eventSink: (GlobalSearchEvent) -> Unit = {},
|
||||
) = GlobalSearchState(
|
||||
isEnabled = isEnabled,
|
||||
|
||||
+63
-20
@@ -12,12 +12,14 @@ import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
@@ -31,9 +33,11 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.KeyboardActionHandler
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -67,6 +71,8 @@ import io.element.android.libraries.designsystem.modifiers.niceClickable
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.roundToPx
|
||||
import io.element.android.libraries.designsystem.text.toDp
|
||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||
import io.element.android.libraries.designsystem.theme.components.FilledTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
@@ -75,6 +81,7 @@ import io.element.android.libraries.designsystem.theme.components.LinearProgress
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.SegmentedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.OnVisibleRangeChangeEffect
|
||||
import io.element.android.libraries.designsystem.utils.lazyColumnContentPadding
|
||||
@@ -155,10 +162,14 @@ private fun GlobalSearchContent(
|
||||
val historyResults = state.history.dataOrNull()?.toImmutableList() ?: persistentListOf()
|
||||
when {
|
||||
state.results.isUninitialized() -> if (state.queryState.text.isBlank() && historyResults.isNotEmpty()) {
|
||||
searchHistory(historyResults) { result ->
|
||||
searchHistory(
|
||||
historyResults = historyResults,
|
||||
onRemoveResult = { item -> state.eventSink(GlobalSearchEvent.RemoveSearchHistoryResult(item)) },
|
||||
onClearAllResults = { state.eventSink(GlobalSearchEvent.ClearSearchHistory) },
|
||||
) { result ->
|
||||
when (result) {
|
||||
is SearchHistoryResultItem.Query -> Unit
|
||||
is SearchHistoryResultItem.Room -> onSelectSearchResult(result.roomInfo.id, null)
|
||||
is SearchHistoryListItem.Query -> Unit
|
||||
is SearchHistoryListItem.Room -> onSelectSearchResult(result.roomInfo.id, null)
|
||||
}
|
||||
|
||||
state.eventSink(GlobalSearchEvent.SearchHistoryResultSelected(result))
|
||||
@@ -214,7 +225,7 @@ private fun SearchTopAppBarContent(state: GlobalSearchState) {
|
||||
modifier = Modifier.clip(CircleShape).size(20.dp),
|
||||
onClick = { state.eventSink(GlobalSearchEvent.ClearQuery) },
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
containerColor = ElementTheme.colors.borderInteractivePrimary,
|
||||
containerColor = ElementTheme.colors.iconSecondary,
|
||||
contentColor = ElementTheme.colors.iconOnSolidPrimary
|
||||
)
|
||||
) {
|
||||
@@ -279,31 +290,52 @@ private fun LazyListScope.startSearching() {
|
||||
}
|
||||
|
||||
private fun LazyListScope.searchHistory(
|
||||
historyResults: ImmutableList<SearchHistoryResultItem>,
|
||||
onSelectSearchHistoryResult: (SearchHistoryResultItem) -> Unit,
|
||||
historyResults: ImmutableList<SearchHistoryListItem>,
|
||||
onRemoveResult: (SearchHistoryListItem) -> Unit,
|
||||
onClearAllResults: () -> Unit,
|
||||
onSelectSearchHistoryResult: (SearchHistoryListItem) -> Unit,
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp),
|
||||
text = stringResource(R.string.screen_search_recent_searches_section_title),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp).weight(1f),
|
||||
text = stringResource(R.string.screen_search_recent_searches_section_title),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_clear_all),
|
||||
size = ButtonSize.Small,
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = ElementTheme.colors.textActionAccent),
|
||||
onClick = onClearAllResults,
|
||||
)
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = historyResults,
|
||||
key = { it.id },
|
||||
) { result ->
|
||||
val twoLineHeight =
|
||||
// Vertical padding
|
||||
12.dp * 2 +
|
||||
// Text line heights
|
||||
ElementTheme.typography.fontBodyMdRegular.lineHeight.toDp() +
|
||||
ElementTheme.typography.fontBodyLgRegular.lineHeight.toDp()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.niceClickable(onClick = { onSelectSearchHistoryResult(result) })
|
||||
.clickable(onClick = { onSelectSearchHistoryResult(result) })
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
.padding(start = 16.dp, end = 2.dp)
|
||||
.height(twoLineHeight),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
when (result) {
|
||||
is SearchHistoryResultItem.Query -> {
|
||||
is SearchHistoryListItem.Query -> {
|
||||
Icon(
|
||||
modifier = Modifier.size(32.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
@@ -315,13 +347,14 @@ private fun LazyListScope.searchHistory(
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.weight(1f, fill = true),
|
||||
text = result.term,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
)
|
||||
}
|
||||
is SearchHistoryResultItem.Room -> {
|
||||
is SearchHistoryListItem.Room -> {
|
||||
val heroAvatars = remember(result.roomInfo.heroes) {
|
||||
result.roomInfo.heroes.map { it.getAvatarData(AvatarSize.SearchRoomListItem) }.toImmutableList()
|
||||
}
|
||||
@@ -333,7 +366,7 @@ private fun LazyListScope.searchHistory(
|
||||
),
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.weight(1f, fill = true)) {
|
||||
Text(
|
||||
text = result.roomInfo.name.orEmpty(),
|
||||
maxLines = 1,
|
||||
@@ -365,6 +398,16 @@ private fun LazyListScope.searchHistory(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Icon(
|
||||
modifier = Modifier.minimumInteractiveComponentSize()
|
||||
.niceClickable {
|
||||
onRemoveResult(result)
|
||||
},
|
||||
imageVector = CompoundIcons.Close(),
|
||||
contentDescription = stringResource(CommonStrings.action_remove),
|
||||
tint = ElementTheme.colors.iconSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,7 +494,7 @@ private fun LazyListScope.roomListResults(
|
||||
}
|
||||
|
||||
private fun LazyListScope.messageSearchResults(
|
||||
results: ImmutableList<MessageSearchResultItem>,
|
||||
results: ImmutableList<MessageSearchListItem>,
|
||||
onSearchResultSelected: (RoomId, EventId?) -> Unit,
|
||||
) {
|
||||
if (results.isNotEmpty()) {
|
||||
@@ -471,7 +514,7 @@ private fun LazyListScope.messageSearchResults(
|
||||
) { index, result ->
|
||||
val clickableModifier = Modifier.niceClickable(onClick = { onSearchResultSelected(result.roomId, result.eventId) })
|
||||
when (result) {
|
||||
is MessageSearchResultItem.Message -> TextMessageSearchResultItemView(
|
||||
is MessageSearchListItem.Message -> TextMessageSearchResultItemView(
|
||||
modifier = clickableModifier.addSeparatorLine(color = ElementTheme.colors.separatorSecondary, isLastItem = index == results.lastIndex),
|
||||
avatarData = result.roomInfo.getAvatarData(AvatarSize.RoomListItem),
|
||||
avatarType = AvatarType.Room(
|
||||
@@ -482,7 +525,7 @@ private fun LazyListScope.messageSearchResults(
|
||||
body = result.body,
|
||||
formattedTimestamp = result.formattedTimestamp,
|
||||
)
|
||||
is MessageSearchResultItem.Media -> {
|
||||
is MessageSearchListItem.Media -> {
|
||||
MediaMessageSearchResultItemView(
|
||||
modifier = clickableModifier.addSeparatorLine(color = ElementTheme.colors.separatorSecondary, isLastItem = index == results.lastIndex),
|
||||
avatarData = result.roomInfo.getAvatarData(AvatarSize.RoomListItem),
|
||||
|
||||
+11
@@ -54,6 +54,17 @@ class DefaultSearchHistoryStore(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(result: SearchHistoryResult) {
|
||||
withContext(dispatchers.io) {
|
||||
mutex.withLock {
|
||||
ensureLoadedLocked()
|
||||
val updated = cache.value.filterNot { it == result }
|
||||
cache.value = updated
|
||||
persist(updated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear() = withContext<Unit>(dispatchers.io) {
|
||||
mutex.withLock {
|
||||
isLoaded = true
|
||||
|
||||
+9
-3
@@ -18,9 +18,15 @@ import kotlinx.serialization.Serializable
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface SearchHistoryResult {
|
||||
@Serializable
|
||||
data class Query(val term: String) : SearchHistoryResult
|
||||
val id: String
|
||||
|
||||
@Serializable
|
||||
data class Room(val roomId: RoomId) : SearchHistoryResult
|
||||
data class Query(val term: String) : SearchHistoryResult {
|
||||
override val id: String = term
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Room(val roomId: RoomId) : SearchHistoryResult {
|
||||
override val id: String = roomId.value
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -30,6 +30,11 @@ interface SearchHistoryStore {
|
||||
*/
|
||||
suspend fun add(result: SearchHistoryResult)
|
||||
|
||||
/**
|
||||
* Remove a [result] from the history.
|
||||
*/
|
||||
suspend fun remove(result: SearchHistoryResult)
|
||||
|
||||
/**
|
||||
* Remove every entry from the history.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user