diff --git a/Reply/app/build.gradle.kts b/Reply/app/build.gradle.kts index bc4b910a1..57836241f 100644 --- a/Reply/app/build.gradle.kts +++ b/Reply/app/build.gradle.kts @@ -87,12 +87,6 @@ android { kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() } - packaging.resources { - // Multiple dependency bring these files in. Exclude them to enable - // our test APK to build (has no effect on our AARs) - excludes += "/META-INF/AL2.0" - excludes += "/META-INF/LGPL2.1" - } } dependencies { @@ -107,8 +101,7 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) debugImplementation(libs.androidx.compose.ui.tooling) - // custom declaration for latest versions of material 3 and adaptive accompanist - implementation("androidx.compose.material3:material3:1.0.0-rc01") + implementation(libs.androidx.compose.material3) implementation("com.google.accompanist:accompanist-adaptive:0.26.2-beta") implementation(libs.androidx.compose.materialWindow) diff --git a/Reply/app/src/main/java/com/example/reply/ui/ReplyListContent.kt b/Reply/app/src/main/java/com/example/reply/ui/ReplyListContent.kt index d93dcb151..df26c4ed8 100644 --- a/Reply/app/src/main/java/com/example/reply/ui/ReplyListContent.kt +++ b/Reply/app/src/main/java/com/example/reply/ui/ReplyListContent.kt @@ -43,9 +43,9 @@ import androidx.window.layout.DisplayFeature import com.example.reply.R import com.example.reply.data.Email import com.example.reply.ui.components.EmailDetailAppBar +import com.example.reply.ui.components.ReplyDockedSearchBar import com.example.reply.ui.components.ReplyEmailListItem import com.example.reply.ui.components.ReplyEmailThreadItem -import com.example.reply.ui.components.ReplySearchBar import com.example.reply.ui.utils.ReplyContentType import com.example.reply.ui.utils.ReplyNavigationType import com.google.accompanist.adaptive.HorizontalTwoPaneStrategy @@ -167,20 +167,34 @@ fun ReplyEmailList( modifier: Modifier = Modifier, navigateToDetail: (Long, ReplyContentType) -> Unit ) { - LazyColumn(modifier = modifier, state = emailLazyListState) { - item { - ReplySearchBar(modifier = Modifier.fillMaxWidth()) - } - items(items = emails, key = { it.id }) { email -> - ReplyEmailListItem( - email = email, - navigateToDetail = { emailId -> - navigateToDetail(emailId, ReplyContentType.SINGLE_PANE) - }, - toggleSelection = toggleEmailSelection, - isOpened = openedEmail?.id == email.id, - isSelected = selectedEmailIds.contains(email.id) - ) + Box(modifier = modifier) { + ReplyDockedSearchBar( + emails = emails, + onSearchItemSelected = { searchedEmail -> + navigateToDetail(searchedEmail.id, ReplyContentType.SINGLE_PANE) + }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + + LazyColumn( + modifier = modifier + .fillMaxWidth() + .padding(top = 80.dp), + state = emailLazyListState + ) { + items(items = emails, key = { it.id }) { email -> + ReplyEmailListItem( + email = email, + navigateToDetail = { emailId -> + navigateToDetail(emailId, ReplyContentType.SINGLE_PANE) + }, + toggleSelection = toggleEmailSelection, + isOpened = openedEmail?.id == email.id, + isSelected = selectedEmailIds.contains(email.id) + ) + } } } } diff --git a/Reply/app/src/main/java/com/example/reply/ui/components/ReplyAppBars.kt b/Reply/app/src/main/java/com/example/reply/ui/components/ReplyAppBars.kt index 81c2ec548..dc8e42628 100644 --- a/Reply/app/src/main/java/com/example/reply/ui/components/ReplyAppBars.kt +++ b/Reply/app/src/main/java/com/example/reply/ui/components/ReplyAppBars.kt @@ -16,27 +16,37 @@ package com.example.reply.ui.components -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.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.DockedSearchBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledIconButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -46,35 +56,111 @@ import com.example.reply.data.Email @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ReplySearchBar(modifier: Modifier = Modifier) { - Row( - modifier = modifier - .fillMaxWidth() - .padding(top = 24.dp, bottom = 16.dp, start = 16.dp, end = 16.dp) - .background(MaterialTheme.colorScheme.surface, CircleShape), - verticalAlignment = Alignment.CenterVertically +fun ReplyDockedSearchBar( + emails: List, + onSearchItemSelected: (Email) -> Unit, + modifier: Modifier = Modifier +) { + var query by remember { mutableStateOf("") } + var active by remember { mutableStateOf(false) } + val searchResults = remember { mutableStateListOf() } + + LaunchedEffect(query) { + searchResults.clear() + if (query.isNotEmpty()) { + searchResults.addAll( + emails.filter { + it.subject.startsWith( + prefix = query, + ignoreCase = true + ) || it.sender.fullName.startsWith( + prefix = + query, + ignoreCase = true + ) + } + ) + } + } + + DockedSearchBar( + modifier = modifier, + query = query, + onQueryChange = { + query = it + }, + onSearch = { active = false }, + active = active, + onActiveChange = { + active = it + }, + placeholder = { Text(text = stringResource(id = R.string.search_emails)) }, + leadingIcon = { + if (active) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = stringResource(id = R.string.back_button), + modifier = Modifier + .padding(start = 16.dp) + .clickable { + active = false + query = "" + }, + ) + } else { + Icon( + imageVector = Icons.Default.Search, + contentDescription = stringResource(id = R.string.search), + modifier = Modifier.padding(start = 16.dp), + ) + } + }, + trailingIcon = { + ReplyProfileImage( + drawableResource = R.drawable.avatar_6, + description = stringResource(id = R.string.profile), + modifier = Modifier + .padding(12.dp) + .size(32.dp) + ) + }, ) { - Icon( - imageVector = Icons.Default.Search, - contentDescription = stringResource(id = R.string.search), - modifier = Modifier.padding(start = 16.dp), - tint = MaterialTheme.colorScheme.outline - ) - Text( - text = stringResource(id = R.string.search_replies), - modifier = Modifier - .weight(1f) - .padding(16.dp), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.outline - ) - ReplyProfileImage( - drawableResource = R.drawable.avatar_6, - description = stringResource(id = R.string.profile), - modifier = Modifier - .padding(12.dp) - .size(32.dp) - ) + if (searchResults.isNotEmpty()) { + LazyColumn( + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + items(items = searchResults, key = { it.id }) { email -> + ListItem( + headlineContent = { Text(email.subject) }, + supportingContent = { Text(email.sender.fullName) }, + leadingContent = { + ReplyProfileImage( + drawableResource = email.sender.avatar, + description = stringResource(id = R.string.profile), + modifier = Modifier + .size(32.dp) + ) + }, + modifier = Modifier.clickable { + onSearchItemSelected.invoke(email) + query = "" + active = false + } + ) + } + } + } else if (query.isNotEmpty()) { + Text( + text = stringResource(id = R.string.no_item_found), + modifier = Modifier.padding(16.dp) + ) + } else + Text( + text = stringResource(id = R.string.no_search_history), + modifier = Modifier.padding(16.dp) + ) } } diff --git a/Reply/app/src/main/res/values/strings.xml b/Reply/app/src/main/res/values/strings.xml index a23156eab..e5227ea85 100644 --- a/Reply/app/src/main/res/values/strings.xml +++ b/Reply/app/src/main/res/values/strings.xml @@ -18,7 +18,6 @@ Profile Search - Search replies Reply Reply All @@ -32,4 +31,8 @@ More options Messages 4 hrs ago + + Search emails + No item found + No search history \ No newline at end of file