Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Search inside conversation (WPB-4915) #2358

Merged
merged 39 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cae835a
feat: add isFromSearch param in order to hide reactions and message s…
alexandreferris Oct 16, 2023
37ee4f4
feat: add GetSearchMessagesForConversationUseCase
alexandreferris Oct 16, 2023
59487af
feat: add search conversation+topbar state and screen nav args
alexandreferris Oct 16, 2023
802db95
feat: add get conversation messages usecase to message module
alexandreferris Oct 16, 2023
bc83581
feat: add modifier param to SearchTopBar
alexandreferris Oct 16, 2023
52eaa93
feat: add search message inside a conversation strings
alexandreferris Oct 16, 2023
04826cc
feat: add search inside conversation screens
alexandreferris Oct 16, 2023
3adb9bb
feat: add search inside conversation messages view model
alexandreferris Oct 16, 2023
bfeebf3
chore: enable conversation search icon and add navigation
alexandreferris Oct 16, 2023
534e719
Merge branch 'develop' into feat/search_inside_conversation
alexandreferris Oct 16, 2023
aa85e21
feat: add strings
alexandreferris Oct 18, 2023
23df066
feat: create search conversation messages button to be reused
alexandreferris Oct 18, 2023
bed168a
feat: create search conversation messages button to be reused
alexandreferris Oct 18, 2023
cf91cf2
feat: add navigate back when clicking on the left arrow on search input
alexandreferris Oct 18, 2023
f81cdea
feat: add number of participants string
alexandreferris Oct 18, 2023
c44b351
feat: move group conversation details top bar collapsing into its own…
alexandreferris Oct 19, 2023
8d9687f
feat: add search entry point and display group information on screen …
alexandreferris Oct 19, 2023
47394d1
chore: remove search icon from top bar
alexandreferris Oct 19, 2023
d35aba1
chore: add missing named parameter
alexandreferris Oct 19, 2023
7591e2f
feat: add search button entry point to other user profile
alexandreferris Oct 19, 2023
4566423
test: add tests for GetSearchMessagesForConversationUseCase
alexandreferris Oct 20, 2023
54e4869
chore: rename usecase
alexandreferris Oct 20, 2023
de7136c
test: add tests for Search Conversation Messages view model
alexandreferris Oct 20, 2023
8875b6f
feat: add first focus to search input
alexandreferris Oct 20, 2023
8fd54b0
Merge branch 'develop' into feat/search_inside_conversation
alexandreferris Oct 20, 2023
1e6b6e3
chore: adjust detekt and add missing experimental annotation
alexandreferris Oct 20, 2023
21931bd
chore: fix detekt issues
alexandreferris Oct 20, 2023
7a1f536
chore: adjust unused variable
alexandreferris Oct 20, 2023
97f1e72
chore: update kalium reference
alexandreferris Oct 20, 2023
ae5246e
Merge branch 'develop' into feat/search_inside_conversation
alexandreferris Oct 20, 2023
c540d5b
chore: remove unused code
alexandreferris Oct 23, 2023
159aa5d
chore: rename noneSearchSucceed to isEmptyResult
alexandreferris Oct 23, 2023
a852fd3
chore: add @Preview for SearchConversationMessagesNoResultsScreen
alexandreferris Oct 23, 2023
b9605f5
chore: add @Preview for SearchConversationMessagesEmptyScreen
alexandreferris Oct 23, 2023
95b6c15
chore: add @Preview for SearchConversationMessagesResultsScreen
alexandreferris Oct 23, 2023
a8e204e
Merge branch 'develop' into feat/search_inside_conversation
alexandreferris Oct 23, 2023
363a051
fix: detekt
alexandreferris Oct 23, 2023
2cbb1d8
fix: add more self explanatory parameters to messageItem
alexandreferris Oct 23, 2023
57f7fdf
Merge branch 'develop' into feat/search_inside_conversation
alexandreferris Oct 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.wire.kalium.logic.feature.asset.GetMessageAssetUseCase
import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageUseCase
import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCase
import com.wire.kalium.logic.feature.message.DeleteMessageUseCase
import com.wire.kalium.logic.feature.message.GetConversationMessagesFromSearchQueryUseCase
import com.wire.kalium.logic.feature.message.GetMessageByIdUseCase
import com.wire.kalium.logic.feature.message.GetNotificationsUseCase
import com.wire.kalium.logic.feature.message.GetPaginatedFlowOfMessagesByConversationUseCase
Expand Down Expand Up @@ -147,4 +148,9 @@ class MessageModule {
@Provides
fun provideGetPaginatedMessagesUseCase(messageScope: MessageScope): GetPaginatedFlowOfMessagesByConversationUseCase =
messageScope.getPaginatedFlowOfMessagesByConversation

@ViewModelScoped
@Provides
fun provideGetConversationMessagesFromSearchQueryUseCase(messageScope: MessageScope): GetConversationMessagesFromSearchQueryUseCase =
messageScope.getConversationMessagesFromSearchQuery
}
25 changes: 20 additions & 5 deletions app/src/main/kotlin/com/wire/android/ui/common/SearchBar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
Expand Down Expand Up @@ -73,6 +75,7 @@ fun SearchBar(
)
}

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SearchBarInput(
placeholderText: String,
Expand All @@ -82,10 +85,15 @@ fun SearchBarInput(
placeholderTextStyle: TextStyle = LocalTextStyle.current,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
textStyle: TextStyle = LocalTextStyle.current,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
shouldRequestFocus: Boolean = false
) {
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current

WireTextField(
modifier = modifier,
modifier = modifier
.focusRequester(focusRequester),
value = text,
onValueChange = onTextTyped,
leadingIcon = {
Expand Down Expand Up @@ -116,6 +124,13 @@ fun SearchBarInput(
maxLines = 1,
singleLine = true,
)

if (shouldRequestFocus) {
LaunchedEffect(Unit) {
focusRequester.requestFocus()
keyboardController?.show()
}
}
}

@Preview(showBackground = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,18 @@ import com.wire.android.ui.theme.wireDimensions
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun SearchTopBar(
modifier: Modifier = Modifier,
isSearchActive: Boolean,
searchBarHint: String,
searchQuery: TextFieldValue = TextFieldValue(""),
onSearchQueryChanged: (TextFieldValue) -> Unit,
onInputClicked: () -> Unit,
onCloseSearchClicked: () -> Unit,
shouldRequestFocus: Boolean = false,
bottomContent: @Composable ColumnScope.() -> Unit = {}
) {
Column(
modifier = Modifier
modifier = modifier
.wrapContentHeight()
.fillMaxWidth()
.background(MaterialTheme.wireColorScheme.background)
Expand Down Expand Up @@ -117,7 +119,8 @@ fun SearchTopBar(
interactionSource = interactionSource,
modifier = Modifier
.padding(dimensions().spacing8x)
.focusRequester(focusRequester)
.focusRequester(focusRequester),
shouldRequestFocus = shouldRequestFocus
)
// We added an invisible clickable box only present when the search is not active.
// That way we can still make the whole top bar clickable and intercept and discard the long press gestures.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ fun MessageItem(
onSelfDeletingMessageRead: (UIMessage) -> Unit,
onFailedMessageRetryClicked: (String) -> Unit = {},
onFailedMessageCancelClicked: (String) -> Unit = {},
onLinkClick: (String) -> Unit = {}
onLinkClick: (String) -> Unit = {},
isFromSearch: Boolean = false
alexandreferris marked this conversation as resolved.
Show resolved Hide resolved
) {
with(message) {
val selfDeletionTimerState = rememberSelfDeletionTimer(header.messageStatus.expirationStatus)
Expand All @@ -132,6 +133,8 @@ fun MessageItem(
)

Modifier.background(color)
} else if (isFromSearch) {
Modifier.background(colorsScheme().backgroundVariant)
} else {
Modifier
}
Expand Down Expand Up @@ -236,7 +239,7 @@ fun MessageItem(
onLinkClick = onLinkClick
)
}
if (isMyMessage) {
if (isMyMessage && !isFromSearch) {
alexandreferris marked this conversation as resolved.
Show resolved Hide resolved
MessageStatusIndicator(
status = message.header.messageStatus.flowStatus,
isGroupConversation = conversationDetailsData is ConversationDetailsData.Group,
Expand All @@ -249,10 +252,12 @@ fun MessageItem(
HorizontalSpace.x24()
}
}
MessageFooter(
messageFooter,
onReactionClicked
)
if (!isFromSearch) {
alexandreferris marked this conversation as resolved.
Show resolved Hide resolved
MessageFooter(
messageFooter = messageFooter,
onReactionClicked = onReactionClicked
)
}
} else {
MessageDecryptionFailure(
messageHeader = header,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
package com.wire.android.ui.home.conversations.details

import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
Expand All @@ -32,6 +34,7 @@ import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
Expand Down Expand Up @@ -60,6 +63,7 @@ import com.wire.android.appLogger
import com.wire.android.navigation.NavigationCommand
import com.wire.android.navigation.Navigator
import com.wire.android.navigation.style.PopUpNavigationAnimation
import com.wire.android.ui.common.CollapsingTopBarScaffold
import com.wire.android.ui.common.MoreOptionIcon
import com.wire.android.ui.common.TabItem
import com.wire.android.ui.common.WireTabRow
Expand All @@ -69,7 +73,6 @@ import com.wire.android.ui.common.bottomsheet.conversation.rememberConversationS
import com.wire.android.ui.common.bottomsheet.rememberWireModalSheetState
import com.wire.android.ui.common.calculateCurrentTab
import com.wire.android.ui.common.dialogs.ArchiveConversationDialog
import com.wire.android.ui.common.scaffold.WireScaffold
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.common.topBarElevation
import com.wire.android.ui.common.topappbar.NavigationIconType
Expand All @@ -94,7 +97,9 @@ import com.wire.android.ui.home.conversations.details.participants.GroupConversa
import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant
import com.wire.android.ui.home.conversationslist.model.DialogState
import com.wire.android.ui.home.conversationslist.model.GroupDialogState
import com.wire.android.ui.destinations.SearchConversationMessagesScreenDestination
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.util.ui.UIText
import kotlinx.coroutines.launch
Expand All @@ -116,6 +121,16 @@ fun GroupConversationDetailsScreen(
val snackbarHostState = LocalSnackbarHostState.current
val showSnackbarMessage: (UIText) -> Unit = remember { { scope.launch { snackbarHostState.showSnackbar(it.asString(resources)) } } }

val onSearchConversationMessagesClick: () -> Unit = {
navigator.navigate(
NavigationCommand(
SearchConversationMessagesScreenDestination(
conversationId = viewModel.conversationId
)
)
)
}

GroupConversationDetailsContent(
conversationSheetContent = viewModel.conversationSheetContent,
bottomSheetEventsHandler = viewModel,
Expand Down Expand Up @@ -195,7 +210,8 @@ fun GroupConversationDetailsScreen(
onEditGroupName = {
navigator.navigate(NavigationCommand(EditConversationNameScreenDestination(viewModel.conversationId)))
},
isLoading = viewModel.requestInProgress
isLoading = viewModel.requestInProgress,
onSearchConversationMessagesClick = onSearchConversationMessagesClick
)

val tryAgainSnackBarMessage = stringResource(id = R.string.error_unknown_message)
Expand Down Expand Up @@ -235,7 +251,8 @@ private fun GroupConversationDetailsContent(
onLeaveGroup: (GroupDialogState) -> Unit,
onDeleteGroup: (GroupDialogState) -> Unit,
groupParticipantsState: GroupConversationParticipantsState,
isLoading: Boolean
isLoading: Boolean,
onSearchConversationMessagesClick: () -> Unit
) {
val scope = rememberCoroutineScope()
val resources = LocalContext.current.resources
Expand Down Expand Up @@ -281,64 +298,87 @@ private fun GroupConversationDetailsContent(
clearConversationDialogState.dismiss()
archiveConversationDialogState.dismiss()
}
WireScaffold(
topBar = {

CollapsingTopBarScaffold(
topBarHeader = {
WireCenterAlignedTopAppBar(
elevation = elevationState,
title = stringResource(R.string.conversation_details_title),
navigationIconType = NavigationIconType.Close,
onNavigationPressed = onBackPressed,
actions = { MoreOptionIcon(onButtonClicked = openBottomSheet) }
) {
WireTabRow(
tabs = GroupConversationDetailsTabItem.entries,
selectedTabIndex = currentTabState,
onTabChange = { scope.launch { pagerState.animateScrollToPage(it) } },
modifier = Modifier.padding(top = MaterialTheme.wireDimensions.spacing16x),
divider = {} // no divider
)
},
topBarCollapsing = {
conversationSheetState.conversationSheetContent?.let {
GroupConversationDetailsTopBarCollapsing(
title = it.title,
conversationId = it.conversationId,
totalParticipants = groupParticipantsState.data.allCount,
isLoading = isLoading,
onSearchConversationMessagesClick = onSearchConversationMessagesClick
)
}
},
modifier = Modifier.fillMaxHeight(),
) { internalPadding ->
var focusedTabIndex: Int by remember { mutableStateOf(initialPageIndex) }
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current

CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.padding(internalPadding)
) { pageIndex ->
when (GroupConversationDetailsTabItem.entries[pageIndex]) {
GroupConversationDetailsTabItem.OPTIONS -> GroupConversationOptions(
lazyListState = lazyListStates[pageIndex],
onEditGuestAccess = onEditGuestAccess,
onEditSelfDeletingMessages = onEditSelfDeletingMessages,
onEditGroupName = onEditGroupName
)

GroupConversationDetailsTabItem.PARTICIPANTS -> GroupConversationParticipants(
groupParticipantsState = groupParticipantsState,
openFullListPressed = openFullListPressed,
onAddParticipantsPressed = onAddParticipantsPressed,
onProfilePressed = onProfilePressed,
lazyListState = lazyListStates[pageIndex]
topBarFooter = {
AnimatedVisibility(
visible = conversationSheetState.conversationSheetContent != null,
enter = fadeIn(),
exit = fadeOut(),
) {
Surface(
shadowElevation = elevationState,
color = MaterialTheme.wireColorScheme.background
) {
WireTabRow(
tabs = GroupConversationDetailsTabItem.entries,
selectedTabIndex = currentTabState,
onTabChange = { scope.launch { pagerState.animateScrollToPage(it) } },
modifier = Modifier.padding(top = MaterialTheme.wireDimensions.spacing16x),
divider = {} // no divider
)
}
}
},
content = {
var focusedTabIndex: Int by remember { mutableStateOf(initialPageIndex) }
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current

CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
) { pageIndex ->
when (GroupConversationDetailsTabItem.entries[pageIndex]) {
GroupConversationDetailsTabItem.OPTIONS -> GroupConversationOptions(
lazyListState = lazyListStates[pageIndex],
onEditGuestAccess = onEditGuestAccess,
onEditSelfDeletingMessages = onEditSelfDeletingMessages,
onEditGroupName = onEditGroupName
)

LaunchedEffect(pagerState.isScrollInProgress, focusedTabIndex, pagerState.currentPage) {
if (!pagerState.isScrollInProgress && focusedTabIndex != pagerState.currentPage) {
keyboardController?.hide()
focusManager.clearFocus()
focusedTabIndex = pagerState.currentPage
GroupConversationDetailsTabItem.PARTICIPANTS -> GroupConversationParticipants(
groupParticipantsState = groupParticipantsState,
openFullListPressed = openFullListPressed,
onAddParticipantsPressed = onAddParticipantsPressed,
onProfilePressed = onProfilePressed,
lazyListState = lazyListStates[pageIndex]
)
}
}

LaunchedEffect(pagerState.isScrollInProgress, focusedTabIndex, pagerState.currentPage) {
if (!pagerState.isScrollInProgress && focusedTabIndex != pagerState.currentPage) {
keyboardController?.hide()
focusManager.clearFocus()
focusedTabIndex = pagerState.currentPage
}
}
}
}
}
)

WireModalSheetLayout(
sheetState = sheetState,
Expand Down Expand Up @@ -428,7 +468,8 @@ fun PreviewGroupConversationDetails() {
isLoading = false,
onEditGroupName = {},
onEditSelfDeletingMessages = {},
onEditGuestAccess = {}
onEditGuestAccess = {},
onSearchConversationMessagesClick = {}
)
}
}
Loading
Loading