diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index da0f68e6be..ecfb9c30a3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -347,7 +347,9 @@ fun ChatView( setGroupMembers(chatRh, groupInfo, chatModel) if (!isActive) return@launch - ModalManager.end.closeModals() + if (!groupReports.value.reportsView) { + ModalManager.end.closeModals() + } ModalManager.end.showModalCloseable(true) { close -> remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close) @@ -744,7 +746,7 @@ fun ChatLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { val composeViewHeight = remember { mutableStateOf(0.dp) } - Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer)) { + Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, !groupReports.value.reportsView)) { val remoteHostId = remember { remoteHostId }.value val chatInfo = remember { chatInfo }.value val oneHandUI = remember { appPrefs.oneHandUI.state } @@ -766,7 +768,42 @@ fun ChatLayout( } } } - if (!groupReports.value.reportsView) { + if (groupReports.value.reportsView) { + Column( + Modifier + .layoutId(CHAT_COMPOSE_LAYOUT_ID) + .align(Alignment.BottomCenter) + .navigationBarsPadding() + .imePadding() + ) { + AnimatedVisibility(selectedChatItems.value != null) { + if (chatInfo != null) { + SelectedItemsBottomToolbar( + reversedChatItems = reversedChatItems, + selectedChatItems = selectedChatItems, + chatInfo = chatInfo, + deleteItems = { _ -> + val itemIds = selectedChatItems.value + val questionText = generalGetString(MR.strings.delete_messages_cannot_be_undone_warning) + if (itemIds != null) { + deleteMessagesAlertDialog(itemIds.sorted(), questionText = questionText, forAll = false, deleteMessages = { ids, _ -> + deleteMessages(remoteHostId, chatInfo, ids, false, moderate = false) { + selectedChatItems.value = null + } + }) + } + }, + moderateItems = {}, + forwardItems = {} + ) + } + } + if (oneHandUI.value) { + // That's placeholder to take some space for bottom app bar in oneHandUI + Box(Modifier.height(AppBarHeight * fontSizeSqrtMultiplier)) + } + } + } else { Box( Modifier .layoutId(CHAT_COMPOSE_LAYOUT_ID) @@ -777,49 +814,46 @@ fun ChatLayout( ) { composeView() } - } else { - // That's placeholder to take some space for bottom app bar in oneHandUI - Box( - Modifier - .layoutId(CHAT_COMPOSE_LAYOUT_ID) - .align(Alignment.BottomCenter) - .height(if (oneHandUI.value) AppBarHeight * fontSizeSqrtMultiplier else 0.dp) - ) } } if (oneHandUI.value && chatBottomBar.value) { - if (groupReports.value.showBar) { - GroupReportsToolbar(groupReports, withStatusBar = true, showGroupReports) - } else { - StatusBarBackground() - } + if (groupReports.value.showBar) { + ReportedCountToolbar(groupReports, withStatusBar = true, showGroupReports) + } else { + StatusBarBackground() + } } else { NavigationBarBackground(true, oneHandUI.value, noAlpha = true) } - Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { - if (!groupReports.value.reportsView) { + if (groupReports.value.reportsView) { + if (oneHandUI.value) { + StatusBarBackground() + } + Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { + Box { + if (selectedChatItems.value == null) { + GroupReportsAppBar(groupReports, { ModalManager.end.closeModal() }, showArchived = { showHide -> + showArchivedReports.value = showHide + }, onSearchValueChanged) + } else { + SelectedItemsTopToolbar(selectedChatItems, !oneHandUI.value) + } + } + } + } else { + Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) { Box { if (selectedChatItems.value == null) { if (chatInfo != null) { ChatInfoToolbar(chatInfo, groupReports, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) } } else { - SelectedItemsTopToolbar(selectedChatItems) + SelectedItemsTopToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value) } } - } - if (groupReports.value.showBar && (!oneHandUI.value || !chatBottomBar.value)) { - GroupReportsToolbar(groupReports, withStatusBar = false, showGroupReports) - } - } - if (groupReports.value.reportsView) { - if (oneHandUI.value) { - StatusBarBackground() - } - Box(Modifier.align(if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart)) { - GroupReportsAppBar(groupReports, { ModalManager.end.closeModal() }, showArchived = { showHide -> - showArchivedReports.value = showHide - }, onSearchValueChanged) + if (groupReports.value.showBar && (!oneHandUI.value || !chatBottomBar.value)) { + ReportedCountToolbar(groupReports, withStatusBar = false, showGroupReports) + } } } } @@ -1047,7 +1081,7 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo } @Composable -private fun GroupReportsToolbar( +private fun ReportedCountToolbar( groupReports: State, withStatusBar: Boolean, showGroupReports: () -> Unit @@ -1139,7 +1173,7 @@ fun BoxScope.ChatItemsList( val maxHeightForList = rememberUpdatedState( with(LocalDensity.current) { LocalWindowHeight().roundToPx() - topPaddingToContentPx.value - (AppBarHeight * fontSizeSqrtMultiplier * 2).roundToPx() } ) - val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, saver = LazyListState.Saver) { + val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, groupReports.value.showArchived, saver = LazyListState.Saver) { val index = mergedItems.value.items.indexOfLast { it.hasUnread() } val reportsState = reportsListState if (reportsState != null) { @@ -1187,7 +1221,13 @@ fun BoxScope.ChatItemsList( } val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem, groupReports.value.contentTag) } if (!groupReports.value.reportsView) { - LaunchedEffect(Unit) { snapshotFlow { scrollToItemId.value }.filterNotNull().collect { scrollToItem(it); scrollToItemId.value = null } } + LaunchedEffect(Unit) { snapshotFlow { scrollToItemId.value }.filterNotNull().collect { + if (appPlatform.isAndroid) { + ModalManager.end.closeModals() + } + scrollToItem(it) + scrollToItemId.value = null } + } } LoadLastItems(loadingMoreItems, remoteHostId, chatInfo, groupReports) SmallScrollOnNewMessage(listState, reversedChatItems) @@ -2347,7 +2387,8 @@ fun Modifier.chatViewBackgroundModifier( colors: Colors, wallpaper: AppWallpaper, backgroundGraphicsLayerSize: MutableState?, - backgroundGraphicsLayer: GraphicsLayer? + backgroundGraphicsLayer: GraphicsLayer?, + drawWallpaper: Boolean ): Modifier { val wallpaperImage = wallpaper.type.image val wallpaperType = wallpaper.type @@ -2355,7 +2396,7 @@ fun Modifier.chatViewBackgroundModifier( val tintColor = wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base) return this - .then(if (wallpaperImage != null) + .then(if (wallpaperImage != null && drawWallpaper) Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor, backgroundGraphicsLayerSize, backgroundGraphicsLayer) } else Modifier.drawWithCache { onDrawBehind { copyBackgroundToAppBar(backgroundGraphicsLayerSize, backgroundGraphicsLayer) { drawRect(backgroundColor) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt index d45365abd6..e449831ee0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt @@ -21,11 +21,10 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource @Composable -fun BoxScope.SelectedItemsTopToolbar(selectedChatItems: MutableState?>) { +fun BoxScope.SelectedItemsTopToolbar(selectedChatItems: MutableState?>, onTop: Boolean) { val onBackClicked = { selectedChatItems.value = null } BackHandler(onBack = onBackClicked) val count = selectedChatItems.value?.size ?: 0 - val oneHandUI = remember { appPrefs.oneHandUI.state } DefaultAppBar( navigationButton = { NavigationButtonClose(onButtonClicked = onBackClicked) }, title = { @@ -41,7 +40,7 @@ fun BoxScope.SelectedItemsTopToolbar(selectedChatItems: MutableState?> ) }, onTitleClick = null, - onTop = !oneHandUI.value, + onTop = onTop, onSearchValueChanged = {}, ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt index 28b0e7c979..666388c419 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt @@ -1,13 +1,18 @@ package chat.simplex.common.views.chat.group +import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* -import chat.simplex.common.views.chat.ChatView -import chat.simplex.common.views.chat.apiLoadMessages +import chat.simplex.common.views.chat.* import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.helpers.* @@ -15,7 +20,6 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch val LocalContentTag: ProvidableCompositionLocal = staticCompositionLocalOf { null } @@ -69,29 +73,40 @@ fun GroupReportsAppBar( IconButton({ showMenu.value = true }) { Icon(MoreVertFilled, stringResource(MR.strings.icon_descr_more_button), tint = MaterialTheme.colors.primary) } - val onClosedAction = remember { mutableStateOf({}) } - DefaultDropdownMenu( - showMenu, - onClosed = onClosedAction - ) { - ItemAction(stringResource(MR.strings.search_verb), painterResource(MR.images.ic_search), onClick = { - showMenu.value = false - showSearch.value = true - }) - ItemAction( - if (groupReports.value.showArchived) stringResource(MR.strings.group_reports_show_active) else stringResource(MR.strings.group_reports_show_archived), - painterResource(if (groupReports.value.showArchived) MR.images.ic_flag else MR.images.ic_inventory_2), - onClick = { - onClosedAction.value = { - showArchived(!groupReports.value.showArchived) - onClosedAction.value = {} - } - showMenu.value = false - } - ) - } } ) + Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd)) { + val density = LocalDensity.current + val width = remember { mutableStateOf(250.dp) } + val height = remember { mutableStateOf(0.dp) } + + val onClosedAction = remember { mutableStateOf({}) } + DefaultDropdownMenu( + showMenu, + modifier = Modifier.onSizeChanged { with(density) { + width.value = it.width.toDp().coerceAtLeast(250.dp) + if (oneHandUI.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp() + } }, + offset = DpOffset(-width.value, if (oneHandUI.value) -height.value else AppBarHeight), + onClosed = onClosedAction + ) { + ItemAction(stringResource(MR.strings.search_verb), painterResource(MR.images.ic_search), onClick = { + showMenu.value = false + showSearch.value = true + }) + ItemAction( + if (groupReports.value.showArchived) stringResource(MR.strings.group_reports_show_active) else stringResource(MR.strings.group_reports_show_archived), + painterResource(if (groupReports.value.showArchived) MR.images.ic_flag else MR.images.ic_inventory_2), + onClick = { + onClosedAction.value = { + showArchived(!groupReports.value.showArchived) + onClosedAction.value = {} + } + showMenu.value = false + } + ) + } + } ItemsReload(groupReports) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index 2827a649b5..d491a1d189 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -128,17 +128,6 @@ fun FramedItemView( Modifier .background(if (sent) sentColor else receivedColor) .fillMaxWidth() - .combinedClickable( - onLongClick = { showMenu.value = true }, - onClick = { - if (qi.itemId != null) { - scrollToItem(qi.itemId) - } else { - scrollToQuotedItemFromItem(ci.id) - } - } - ) - .onRightClick { showMenu.value = true } ) { when (qi.content) { is MsgContent.MCImage -> { @@ -216,39 +205,61 @@ fun FramedItemView( .padding(start = if (tailRendered) msgTailWidthDp else 0.dp, end = if (sent && tailRendered) msgTailWidthDp else 0.dp) ) { PriorityLayout(Modifier, CHAT_IMAGE_LAYOUT_ID) { - if (ci.isReport) { - if (ci.meta.itemDeleted == null) { - FramedItemHeader( - stringResource(if (ci.chatDir.sent) MR.strings.report_item_visibility_submitter else MR.strings.report_item_visibility_moderators), - true, - painterResource(MR.images.ic_flag), - iconColor = Color.Red - ) - } else { - FramedItemHeader(stringResource(MR.strings.report_item_archived), true, painterResource(MR.images.ic_flag)) + @Composable + fun Header() { + if (ci.isReport) { + if (ci.meta.itemDeleted == null) { + FramedItemHeader( + stringResource(if (ci.chatDir.sent) MR.strings.report_item_visibility_submitter else MR.strings.report_item_visibility_moderators), + true, + painterResource(MR.images.ic_flag), + iconColor = Color.Red + ) + } else { + FramedItemHeader(stringResource(MR.strings.report_item_archived), true, painterResource(MR.images.ic_flag)) + } + } else if (ci.meta.itemDeleted != null) { + when (ci.meta.itemDeleted) { + is CIDeleted.Moderated -> { + FramedItemHeader(String.format(stringResource(MR.strings.moderated_item_description), ci.meta.itemDeleted.byGroupMember.chatViewName), true, painterResource(MR.images.ic_flag)) + } + is CIDeleted.Blocked -> { + FramedItemHeader(stringResource(MR.strings.blocked_item_description), true, painterResource(MR.images.ic_back_hand)) + } + is CIDeleted.BlockedByAdmin -> { + FramedItemHeader(stringResource(MR.strings.blocked_by_admin_item_description), true, painterResource(MR.images.ic_back_hand)) + } + is CIDeleted.Deleted -> { + FramedItemHeader(stringResource(MR.strings.marked_deleted_description), true, painterResource(MR.images.ic_delete)) + } + } + } else if (ci.meta.isLive) { + FramedItemHeader(stringResource(MR.strings.live), false) } - } else if (ci.meta.itemDeleted != null) { - when (ci.meta.itemDeleted) { - is CIDeleted.Moderated -> { - FramedItemHeader(String.format(stringResource(MR.strings.moderated_item_description), ci.meta.itemDeleted.byGroupMember.chatViewName), true, painterResource(MR.images.ic_flag)) - } - is CIDeleted.Blocked -> { - FramedItemHeader(stringResource(MR.strings.blocked_item_description), true, painterResource(MR.images.ic_back_hand)) - } - is CIDeleted.BlockedByAdmin -> { - FramedItemHeader(stringResource(MR.strings.blocked_by_admin_item_description), true, painterResource(MR.images.ic_back_hand)) - } - is CIDeleted.Deleted -> { - FramedItemHeader(stringResource(MR.strings.marked_deleted_description), true, painterResource(MR.images.ic_delete)) - } - } - } else if (ci.meta.isLive) { - FramedItemHeader(stringResource(MR.strings.live), false) } if (ci.quotedItem != null) { - ciQuoteView(ci.quotedItem) - } else if (ci.meta.itemForwarded != null) { - FramedItemHeader(ci.meta.itemForwarded.text(chatInfo.chatType), true, painterResource(MR.images.ic_forward), pad = true) + Column( + Modifier + .combinedClickable( + onLongClick = { showMenu.value = true }, + onClick = { + if (ci.quotedItem.itemId != null) { + scrollToItem(ci.quotedItem.itemId) + } else { + scrollToQuotedItemFromItem(ci.id) + } + } + ) + .onRightClick { showMenu.value = true } + ) { + Header() + ciQuoteView(ci.quotedItem) + } + } else { + Header() + if (ci.meta.itemForwarded != null) { + FramedItemHeader(ci.meta.itemForwarded.text(chatInfo.chatType), true, painterResource(MR.images.ic_forward), pad = true) + } } if (ci.file == null && ci.formattedText == null && !ci.meta.isLive && isShortEmoji(ci.content.text)) { Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt index 4bf20d2128..1c5f86c8b5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt @@ -35,7 +35,8 @@ fun DefaultAppBar( // If I just disable clickable modifier when don't need it, it will stop passing clicks to search. Replacing the whole modifier val modifier = if (!showSearch) { Modifier.clickable(enabled = onTitleClick != null, onClick = onTitleClick ?: { }) - } else Modifier.imePadding() + } else if (!onTop) Modifier.imePadding() + else Modifier val themeBackgroundMix = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f) val prefAlpha = remember { appPrefs.inAppBarsAlpha.state }