diff --git a/app/src/main/kotlin/com/wire/android/ui/common/AttachmentButton.kt b/app/src/main/kotlin/com/wire/android/ui/common/AttachmentButton.kt index b265dee74fe..54d08f993c7 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/AttachmentButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/AttachmentButton.kt @@ -26,6 +26,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape @@ -39,10 +41,12 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import com.wire.android.R +import com.wire.android.ui.common.spacers.VerticalSpace import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography @@ -51,11 +55,13 @@ import com.wire.android.ui.theme.wireTypography fun AttachmentButton( text: String = "", @DrawableRes icon: Int, + labelStyle: TextStyle, modifier: Modifier = Modifier, onClick: () -> Unit ) { Column( modifier = modifier + .height(dimensions().spacing100x) .padding(dimensions().spacing4x) .clip(RoundedCornerShape(size = MaterialTheme.wireDimensions.buttonSmallCornerSize)) .clickable { onClick() } @@ -78,19 +84,22 @@ fun AttachmentButton( colorFilter = ColorFilter.tint(MaterialTheme.wireColorScheme.onPrimaryButtonEnabled) ) } + VerticalSpace.x4() + Spacer(modifier = Modifier.weight(1F)) Text( text = text, maxLines = 2, textAlign = TextAlign.Center, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.wireTypography.button03, + style = labelStyle, color = MaterialTheme.wireColorScheme.onBackground, ) + Spacer(modifier = Modifier.weight(1F)) } } @Preview(showBackground = true) @Composable fun PreviewAttachmentButton() { - AttachmentButton("Share Location", R.drawable.ic_location) { } + AttachmentButton("Share Location", R.drawable.ic_location, MaterialTheme.wireTypography.button03) { } } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt index e2d3fdff931..72fe53f7c48 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt @@ -92,7 +92,7 @@ internal fun ConversationMainSheetContent( customVerticalPadding = dimensions().spacing8x ), menuItems = buildList<@Composable () -> Unit> { - if (conversationSheetContent.canEditNotifications()) { + if (conversationSheetContent.canEditNotifications() && !conversationSheetContent.isArchived) { add { MenuBottomSheetItem( title = stringResource(R.string.label_notifications), diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt index 49fa82ab500..3129a138d9e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt @@ -59,6 +59,7 @@ import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.ui.home.conversationslist.model.ConversationsSource import com.wire.android.ui.home.conversationslist.model.DialogState import com.wire.android.ui.home.conversationslist.model.GroupDialogState +import com.wire.android.ui.home.conversationslist.model.isArchive import com.wire.android.ui.home.conversationslist.search.SearchConversationScreen import com.wire.android.util.extension.openAppInfoScreen import com.wire.kalium.logic.data.id.ConversationId @@ -68,6 +69,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow // Since the HomeScreen is responsible for displaying the bottom sheet content, // we create a bridge that passes the content of the BottomSheet // also we expose the lambda which expands the BottomSheet from the HomeScreen +@Suppress("ComplexMethod") @Composable fun ConversationRouterHomeBridge( navigator: Navigator, @@ -119,6 +121,16 @@ fun ConversationRouterHomeBridge( } } + fun showConfirmationDialogOrUnarchive(): (DialogState) -> Unit { + return { dialogState -> + if (dialogState.isArchived) { + viewModel.moveConversationToArchive(dialogState) + } else { + conversationRouterHomeState.archiveConversationDialogState.show(dialogState) + } + } + } + with(conversationRouterHomeState) { fun openConversationBottomSheet( conversationItem: ConversationItem, @@ -156,7 +168,7 @@ fun ConversationRouterHomeBridge( }, addConversationToFavourites = viewModel::addConversationToFavourites, moveConversationToFolder = viewModel::moveConversationToFolder, - updateConversationArchiveStatus = archiveConversationDialogState::show, + updateConversationArchiveStatus = showConfirmationDialogOrUnarchive(), clearConversationContent = clearContentDialogState::show, blockUser = blockUserDialogState::show, unblockUser = unblockUserDialogState::show, @@ -201,6 +213,7 @@ fun ConversationRouterHomeBridge( ConversationItemType.ALL_CONVERSATIONS -> AllConversationScreenContent( conversations = foldersWithConversations, + isFromArchive = conversationsSource.isArchive(), hasNoConversations = hasNoConversations, onEditConversation = onEditConversationItem, onOpenConversationNotificationsSettings = onEditNotifications, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/model/SearchQuery.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/model/SearchQuery.kt index 549df639d93..6e0535eac9f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/model/SearchQuery.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/model/SearchQuery.kt @@ -29,3 +29,5 @@ enum class ConversationsSource { MAIN, ARCHIVE } + +fun ConversationsSource.isArchive(): Boolean = this == ConversationsSource.ARCHIVE diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt index 08f12273ed5..a0cc294d185 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt @@ -22,24 +22,31 @@ import android.net.Uri import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material.Surface +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import com.wire.android.R import com.wire.android.ui.common.AttachmentButton import com.wire.android.ui.common.dimensions import com.wire.android.ui.home.conversations.model.UriAsset +import com.wire.android.ui.theme.wireTypography import com.wire.android.util.debug.LocalFeatureVisibilityFlags import com.wire.android.util.permission.UseCameraAndWriteStorageRequestFlow import com.wire.android.util.permission.UseCameraRequestFlow @@ -49,6 +56,7 @@ import com.wire.android.util.permission.rememberCurrentLocationFlow import com.wire.android.util.permission.rememberOpenFileBrowserFlow import com.wire.android.util.permission.rememberOpenGalleryFlow import com.wire.android.util.permission.rememberTakePictureFlow +import com.wire.android.util.ui.KeyboardHeight @Composable fun AttachmentOptionsComponent( @@ -58,6 +66,9 @@ fun AttachmentOptionsComponent( tempWritableVideoUri: Uri?, isFileSharingEnabled: Boolean ) { + val density = LocalDensity.current + val textMeasurer = rememberTextMeasurer() + val attachmentOptions = buildAttachmentOptionItems( isFileSharingEnabled, tempWritableImageUri, @@ -66,10 +77,31 @@ fun AttachmentOptionsComponent( onRecordAudioMessageClicked ) + val labelStyle = MaterialTheme.wireTypography.button03 + + /** + * Calculate the maximum text width among a list of attachment options. + */ + val maxTextWidth: Int = attachmentOptions + .map { optionItem -> + val label = stringResource(optionItem.text) + val longestLabel = if (label.contains(" ")) { + label.split(" ").maxBy { it.length } + } else { + label + } + // Measure the width of the longest label using the specified typography style + textMeasurer.measure( + longestLabel, + labelStyle + ).size.width + } + .maxBy { it } + BoxWithConstraints(Modifier.fillMaxSize()) { - val fullWidth: Dp = with(LocalDensity.current) { constraints.maxWidth.toDp() } - val minColumnWidth: Dp = dimensions().spacing80x - val minPadding: Dp = dimensions().spacing8x + val fullWidth: Dp = with(density) { constraints.maxWidth.toDp() } + val minPadding: Dp = dimensions().spacing2x + val minColumnWidth: Dp = with(density) { maxTextWidth.toDp() + dimensions().spacing24x } val visibleAttachmentOptions = attachmentOptions.filter { it.shouldShow } val params by remember(fullWidth, visibleAttachmentOptions.size) { derivedStateOf { @@ -87,20 +119,31 @@ fun AttachmentOptionsComponent( ) { visibleAttachmentOptions.forEach { option -> if (option.shouldShow) { - item { AttachmentButton(stringResource(option.text), option.icon) { option.onClick() } } + item { AttachmentButton(stringResource(option.text), option.icon, labelStyle) { option.onClick() } } } } } } } -private fun calculateGridParams(minPadding: Dp, minColumnWidth: Dp, fullWidth: Dp, itemsCount: Int): Pair { +private fun calculateGridParams( + minPadding: Dp, + minColumnWidth: Dp, + fullWidth: Dp, + itemsCount: Int +): Pair { + // Calculate the width available for columns by subtracting the minimum padding from both sides val availableWidth = fullWidth - (minPadding * 2) + // Determine the maximum number of columns that can fit in the available width val currentMaxColumns = availableWidth / minColumnWidth + // Check if the maximum number of columns is less than or equal to the number of items return if (currentMaxColumns <= itemsCount) { + // If so, use adaptive grid cells with the minimum column width and minimum padding GridCells.Adaptive(minColumnWidth) to PaddingValues(minPadding) } else { + // Otherwise, calculate the padding needed to center the columns val currentPadding = (availableWidth - (minColumnWidth * itemsCount)) / 2 + // Use fixed grid cells with the exact number of items and calculated padding GridCells.Fixed(itemsCount) to PaddingValues(vertical = minPadding, horizontal = currentPadding) } } @@ -241,7 +284,7 @@ private data class AttachmentOptionItem( val onClick: () -> Unit ) -@Preview(showBackground = true) +@Preview(showBackground = true, locale = "de") @Composable fun PreviewAttachmentComponents() { AttachmentOptionsComponent( @@ -252,3 +295,60 @@ fun PreviewAttachmentComponents() { onRecordAudioMessageClicked = {}, ) } + +@Preview(name = "Small Screen", widthDp = 320, heightDp = 480, showBackground = true) +@Composable +fun PreviewAttachmentOptionsComponentSmallScreen() { + Surface { + Box( + modifier = Modifier.height(KeyboardHeight.default), + contentAlignment = Alignment.BottomCenter + ) { + AttachmentOptionsComponent( + {}, + isFileSharingEnabled = true, + tempWritableImageUri = null, + tempWritableVideoUri = null, + onRecordAudioMessageClicked = {}, + ) + } + } +} + +@Preview(name = "Normal Screen", widthDp = 360, heightDp = 640) +@Composable +fun PreviewAttachmentOptionsComponentNormalScreen() { + Surface { + Box( + modifier = Modifier.height(KeyboardHeight.default), + contentAlignment = Alignment.BottomCenter + ) { + AttachmentOptionsComponent( + {}, + isFileSharingEnabled = true, + tempWritableImageUri = null, + tempWritableVideoUri = null, + onRecordAudioMessageClicked = {}, + ) + } + } +} + +@Preview(name = "Tablet Screen", widthDp = 600, heightDp = 960) +@Composable +fun PreviewAttachmentOptionsComponentTabledScreen() { + Surface { + Box( + modifier = Modifier.height(KeyboardHeight.default), + contentAlignment = Alignment.BottomCenter + ) { + AttachmentOptionsComponent( + {}, + isFileSharingEnabled = true, + tempWritableImageUri = null, + tempWritableVideoUri = null, + onRecordAudioMessageClicked = {}, + ) + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 53ececb3d29..d8e4a4dd475 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,7 @@ compose-material = "1.6.0-alpha05" compose-activity = "1.7.2" compose-compiler = "1.5.2" compose-constraint = "1.0.1" -compose-material3 = "1.1.1" +compose-material3 = "1.2.0-alpha09" compose-navigation = "2.6.0" compose-destinations = "1.9.40-beta"