mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-11 15:24:58 +00:00
android, desktop: reports dashboard
This commit is contained in:
+2
@@ -29,6 +29,7 @@ actual fun LazyColumnWithScrollBar(
|
||||
flingBehavior: FlingBehavior,
|
||||
userScrollEnabled: Boolean,
|
||||
additionalBarOffset: State<Dp>?,
|
||||
additionalTopBar: State<Boolean>,
|
||||
chatBottomBar: State<Boolean>,
|
||||
fillMaxSize: Boolean,
|
||||
content: LazyListScope.() -> Unit
|
||||
@@ -92,6 +93,7 @@ actual fun LazyColumnWithScrollBarNoAppBar(
|
||||
flingBehavior: FlingBehavior,
|
||||
userScrollEnabled: Boolean,
|
||||
additionalBarOffset: State<Dp>?,
|
||||
additionalTopBar: State<Boolean>,
|
||||
chatBottomBar: State<Boolean>,
|
||||
content: LazyListScope.() -> Unit
|
||||
) {
|
||||
|
||||
@@ -339,7 +339,7 @@ fun AndroidScreen(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||
.graphicsLayer { translationX = maxWidth.toPx() - minOf(offset.value.dp, maxWidth).toPx() }
|
||||
) Box2@{
|
||||
currentChatId.value?.let {
|
||||
ChatView(currentChatId, onComposed)
|
||||
ChatView(currentChatId, reportsView = false, onComposed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,7 +393,7 @@ fun CenterPartOfScreen() {
|
||||
ModalManager.center.showInView()
|
||||
}
|
||||
}
|
||||
else -> ChatView(currentChatId) {}
|
||||
else -> ChatView(currentChatId, reportsView = false) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -23,6 +23,7 @@ expect fun LazyColumnWithScrollBar(
|
||||
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
||||
userScrollEnabled: Boolean = true,
|
||||
additionalBarOffset: State<Dp>? = null,
|
||||
additionalTopBar: State<Boolean> = remember { mutableStateOf(false) },
|
||||
chatBottomBar: State<Boolean> = remember { mutableStateOf(true) },
|
||||
// by default, this function will include .fillMaxSize() without you doing anything. If you don't need it, pass `false` here
|
||||
// maxSize (at least maxHeight) is needed for blur on appBars to work correctly
|
||||
@@ -42,6 +43,7 @@ expect fun LazyColumnWithScrollBarNoAppBar(
|
||||
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
|
||||
userScrollEnabled: Boolean = true,
|
||||
additionalBarOffset: State<Dp>? = null,
|
||||
additionalTopBar: State<Boolean> = remember { mutableStateOf(false) },
|
||||
chatBottomBar: State<Boolean> = remember { mutableStateOf(true) },
|
||||
content: LazyListScope.() -> Unit
|
||||
)
|
||||
|
||||
+2
-2
@@ -154,12 +154,12 @@ fun TerminalLog(floating: Boolean, composeViewHeight: State<Dp>) {
|
||||
}
|
||||
}
|
||||
LazyColumnWithScrollBar (
|
||||
reverseLayout = true,
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(
|
||||
top = topPaddingToContent(false),
|
||||
bottom = composeViewHeight.value
|
||||
),
|
||||
state = listState,
|
||||
reverseLayout = true,
|
||||
additionalBarOffset = composeViewHeight
|
||||
) {
|
||||
items(reversedTerminalItems, key = { item -> item.id to item.createdAtNanos }) { item ->
|
||||
|
||||
+139
-40
@@ -57,7 +57,7 @@ data class ItemSeparation(val timestamp: Boolean, val largeGap: Boolean, val dat
|
||||
@Composable
|
||||
// staleChatId means the id that was before chatModel.chatId becomes null. It's needed for Android only to make transition from chat
|
||||
// to chat list smooth. Otherwise, chat view will become blank right before the transition starts
|
||||
fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -> Unit) {
|
||||
fun ChatView(staleChatId: State<String?>, reportsView: Boolean, onComposed: suspend (chatId: String) -> Unit) {
|
||||
val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } }
|
||||
val showSearch = rememberSaveable { mutableStateOf(false) }
|
||||
val activeChatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo } }
|
||||
@@ -69,6 +69,8 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
ModalManager.end.closeModals()
|
||||
}
|
||||
} else {
|
||||
val showArchivedReports = remember { mutableStateOf(false) }
|
||||
val groupReports = remember { derivedStateOf { GroupReports((activeChatInfo.value as? ChatInfo.Group)?.apiId?.toInt() ?: 0, reportsView, showArchivedReports.value) } }
|
||||
val searchText = rememberSaveable { mutableStateOf("") }
|
||||
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
|
||||
val composeState = rememberSaveable(saver = ComposeState.saver()) {
|
||||
@@ -119,6 +121,15 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null
|
||||
val fullDeleteAllowed = remember(chatInfo) { chatInfo.featureEnabled(ChatFeature.FullDelete) }
|
||||
SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) {
|
||||
val onSearchValueChanged: (String) -> Unit = onSearchValueChanged@{ value ->
|
||||
if (searchText.value == value) return@onSearchValueChanged
|
||||
val c = chatModel.getChat(chatInfo.id) ?: return@onSearchValueChanged
|
||||
if (chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged
|
||||
withBGApi {
|
||||
apiFindMessages(c, value)
|
||||
searchText.value = value
|
||||
}
|
||||
}
|
||||
ChatLayout(
|
||||
remoteHostId = remoteHostId,
|
||||
chatInfo = activeChatInfo,
|
||||
@@ -211,6 +222,7 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
)
|
||||
}
|
||||
},
|
||||
groupReports,
|
||||
attachmentOption,
|
||||
attachmentBottomSheetState,
|
||||
searchText,
|
||||
@@ -278,6 +290,34 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
}
|
||||
}
|
||||
},
|
||||
showGroupReports = {
|
||||
if (ModalManager.end.hasModalsOpen()) {
|
||||
ModalManager.end.closeModals()
|
||||
return@ChatLayout
|
||||
}
|
||||
hideKeyboard(view)
|
||||
ModalManager.end.showCustomModal(true) { close ->
|
||||
ModalView({}, showAppBar = false) {
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
val chatInfo = remember { activeChatInfo }.value
|
||||
if (chatInfo is ChatInfo.Group) {
|
||||
GroupReportsView(staleChatId)
|
||||
if (oneHandUI.value) {
|
||||
StatusBarBackground()
|
||||
}
|
||||
Box(Modifier.align(if (oneHandUI.value) Alignment.BottomStart else Alignment.TopStart)) {
|
||||
GroupReportsAppBar(groupReports, close, showArchived = { showHide ->
|
||||
showArchivedReports.value = showHide
|
||||
}, onSearchValueChanged)
|
||||
}
|
||||
} else {
|
||||
LaunchedEffect(Unit) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
|
||||
hideKeyboard(view)
|
||||
groupMembersJob.cancel()
|
||||
@@ -535,15 +575,7 @@ fun ChatView(staleChatId: State<String?>, onComposed: suspend (chatId: String) -
|
||||
}
|
||||
},
|
||||
changeNtfsState = { enabled, currentValue -> toggleNotifications(chatRh, chatInfo, enabled, chatModel, currentValue) },
|
||||
onSearchValueChanged = { value ->
|
||||
if (searchText.value == value) return@ChatLayout
|
||||
val c = chatModel.getChat(chatInfo.id) ?: return@ChatLayout
|
||||
if (chatModel.chatId.value != chatInfo.id) return@ChatLayout
|
||||
withBGApi {
|
||||
apiFindMessages(c, value)
|
||||
searchText.value = value
|
||||
}
|
||||
},
|
||||
onSearchValueChanged = onSearchValueChanged,
|
||||
onComposed,
|
||||
developerTools = chatModel.controller.appPrefs.developerTools.get(),
|
||||
showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(),
|
||||
@@ -603,6 +635,7 @@ fun ChatLayout(
|
||||
unreadCount: State<Int>,
|
||||
composeState: MutableState<ComposeState>,
|
||||
composeView: (@Composable () -> Unit),
|
||||
groupReports: State<GroupReports>,
|
||||
attachmentOption: MutableState<AttachmentOption?>,
|
||||
attachmentBottomSheetState: ModalBottomSheetState,
|
||||
searchValue: State<String>,
|
||||
@@ -611,6 +644,7 @@ fun ChatLayout(
|
||||
selectedChatItems: MutableState<Set<Long>?>,
|
||||
back: () -> Unit,
|
||||
info: () -> Unit,
|
||||
showGroupReports: () -> Unit,
|
||||
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
|
||||
loadMessages: suspend (ChatId, ChatPagination, ActiveChatState, visibleItemIndexesNonReversed: () -> IntRange) -> Unit,
|
||||
deleteMessage: (Long, CIDeleteMode) -> Unit,
|
||||
@@ -685,7 +719,7 @@ fun ChatLayout(
|
||||
}) {
|
||||
ChatItemsList(
|
||||
remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue,
|
||||
useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages,
|
||||
useLinkPreviews, linkMode, groupReports, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages,
|
||||
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
|
||||
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
|
||||
setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
|
||||
@@ -693,29 +727,50 @@ fun ChatLayout(
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.layoutId(CHAT_COMPOSE_LAYOUT_ID)
|
||||
.align(Alignment.BottomCenter)
|
||||
.imePadding()
|
||||
.navigationBarsPadding()
|
||||
.then(if (oneHandUI.value && chatBottomBar.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier)
|
||||
) {
|
||||
composeView()
|
||||
if (!groupReports.value.reportsView) {
|
||||
Box(
|
||||
Modifier
|
||||
.layoutId(CHAT_COMPOSE_LAYOUT_ID)
|
||||
.align(Alignment.BottomCenter)
|
||||
.imePadding()
|
||||
.navigationBarsPadding()
|
||||
.then(if (oneHandUI.value && chatBottomBar.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier)
|
||||
) {
|
||||
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) {
|
||||
StatusBarBackground()
|
||||
if (groupReports.value.showBar) {
|
||||
GroupReportsToolbar(groupReports, withStatusBar = true, showGroupReports)
|
||||
} else {
|
||||
StatusBarBackground()
|
||||
}
|
||||
} else {
|
||||
NavigationBarBackground(true, oneHandUI.value, noAlpha = true)
|
||||
}
|
||||
Box(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
|
||||
if (selectedChatItems.value == null) {
|
||||
if (chatInfo != null) {
|
||||
ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
|
||||
Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
|
||||
if (!groupReports.value.reportsView) {
|
||||
Box {
|
||||
if (selectedChatItems.value == null) {
|
||||
if (chatInfo != null) {
|
||||
ChatInfoToolbar(chatInfo, groupReports, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
|
||||
}
|
||||
} else {
|
||||
SelectedItemsTopToolbar(selectedChatItems)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SelectedItemsTopToolbar(selectedChatItems)
|
||||
}
|
||||
if (groupReports.value.showBar && (!oneHandUI.value || !chatBottomBar.value)) {
|
||||
GroupReportsToolbar(groupReports, withStatusBar = false, showGroupReports)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -726,6 +781,7 @@ fun ChatLayout(
|
||||
@Composable
|
||||
fun BoxScope.ChatInfoToolbar(
|
||||
chatInfo: ChatInfo,
|
||||
groupReports: State<GroupReports>,
|
||||
back: () -> Unit,
|
||||
info: () -> Unit,
|
||||
startCall: (CallMediaType) -> Unit,
|
||||
@@ -747,7 +803,7 @@ fun BoxScope.ChatInfoToolbar(
|
||||
showSearch.value = false
|
||||
}
|
||||
}
|
||||
if (appPlatform.isAndroid) {
|
||||
if (appPlatform.isAndroid && !groupReports.value.reportsView) {
|
||||
BackHandler(onBack = onBackClicked)
|
||||
}
|
||||
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
|
||||
@@ -941,6 +997,40 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun GroupReportsToolbar(
|
||||
groupReports: State<GroupReports>,
|
||||
withStatusBar: Boolean,
|
||||
showGroupReports: () -> Unit
|
||||
) {
|
||||
Box {
|
||||
val statusBarPadding = if (withStatusBar) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else 0.dp
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(AppBarHeight * fontSizeSqrtMultiplier + statusBarPadding)
|
||||
.background(MaterialTheme.colors.background)
|
||||
.clickable(onClick = showGroupReports)
|
||||
.padding(top = statusBarPadding),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
val reports = groupReports.value.activeReports
|
||||
Text(
|
||||
if (reports == 1) {
|
||||
stringResource(MR.strings.group_reports_active_one)
|
||||
} else {
|
||||
stringResource(MR.strings.group_reports_active).format(reports)
|
||||
},
|
||||
style = MaterialTheme.typography.button
|
||||
)
|
||||
}
|
||||
Divider(Modifier.align(Alignment.BottomStart))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContactVerifiedShield() {
|
||||
Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(18.dp * fontSizeSqrtMultiplier).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary)
|
||||
@@ -956,6 +1046,7 @@ fun BoxScope.ChatItemsList(
|
||||
searchValue: State<String>,
|
||||
useLinkPreviews: Boolean,
|
||||
linkMode: SimplexLinkMode,
|
||||
groupReports: State<GroupReports>,
|
||||
selectedChatItems: MutableState<Set<Long>?>,
|
||||
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
|
||||
showChatInfo: () -> Unit,
|
||||
@@ -987,7 +1078,7 @@ fun BoxScope.ChatItemsList(
|
||||
val reversedChatItems = remember { derivedStateOf { chatModel.chatItems.asReversed() } }
|
||||
val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf<Long>()) }
|
||||
val mergedItems = remember { derivedStateOf { MergedItems.create(reversedChatItems.value, unreadCount, revealedItems.value, chatModel.chatState) } }
|
||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() })
|
||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
|
||||
/** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of
|
||||
* [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears
|
||||
* */
|
||||
@@ -1286,12 +1377,13 @@ fun BoxScope.ChatItemsList(
|
||||
LazyColumnWithScrollBar(
|
||||
Modifier.align(Alignment.BottomCenter),
|
||||
state = listState.value,
|
||||
reverseLayout = true,
|
||||
contentPadding = PaddingValues(
|
||||
top = topPaddingToContent(true),
|
||||
top = topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar),
|
||||
bottom = composeViewHeight.value
|
||||
),
|
||||
reverseLayout = true,
|
||||
additionalBarOffset = composeViewHeight,
|
||||
additionalTopBar = remember { derivedStateOf { groupReports.value.showBar } },
|
||||
chatBottomBar = remember { appPrefs.chatBottomBar.state }
|
||||
) {
|
||||
val mergedItemsValue = mergedItems.value
|
||||
@@ -1335,8 +1427,8 @@ fun BoxScope.ChatItemsList(
|
||||
}
|
||||
}
|
||||
}
|
||||
FloatingButtons(loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState)
|
||||
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent(true)).align(Alignment.TopCenter), mergedItems, listState)
|
||||
FloatingButtons(loadingMoreItems, animatedScrollingInProgress, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, groupReports, markChatRead, listState)
|
||||
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar)).align(Alignment.TopCenter), mergedItems, listState, groupReports)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { listState.value.isScrollInProgress }
|
||||
@@ -1436,11 +1528,12 @@ fun BoxScope.FloatingButtons(
|
||||
maxHeight: State<Int>,
|
||||
composeViewHeight: State<Dp>,
|
||||
searchValue: State<String>,
|
||||
groupReports: State<GroupReports>,
|
||||
markChatRead: () -> Unit,
|
||||
listState: State<LazyListState>
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() })
|
||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
|
||||
val bottomUnreadCount = remember {
|
||||
derivedStateOf {
|
||||
if (unreadCount.value == 0) return@derivedStateOf 0
|
||||
@@ -1486,7 +1579,7 @@ fun BoxScope.FloatingButtons(
|
||||
val showDropDown = remember { mutableStateOf(false) }
|
||||
|
||||
TopEndFloatingButton(
|
||||
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent(true)).align(Alignment.TopEnd),
|
||||
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar)).align(Alignment.TopEnd),
|
||||
topUnreadCount,
|
||||
animatedScrollingInProgress,
|
||||
onClick = {
|
||||
@@ -1508,7 +1601,7 @@ fun BoxScope.FloatingButtons(
|
||||
DefaultDropdownMenu(
|
||||
showDropDown,
|
||||
modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) } },
|
||||
offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent(true))
|
||||
offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar))
|
||||
) {
|
||||
ItemAction(
|
||||
generalGetString(MR.strings.mark_read),
|
||||
@@ -1659,13 +1752,14 @@ private fun TopEndFloatingButton(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun topPaddingToContent(chatView: Boolean): Dp {
|
||||
fun topPaddingToContent(chatView: Boolean, additionalTopBar: Boolean = false): Dp {
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
val chatBottomBar = remember { appPrefs.chatBottomBar.state }
|
||||
val reportsPadding = if (additionalTopBar) AppBarHeight * fontSizeSqrtMultiplier else 0.dp
|
||||
return if (oneHandUI.value && (!chatView || chatBottomBar.value)) {
|
||||
WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + reportsPadding
|
||||
} else {
|
||||
AppBarHeight * fontSizeSqrtMultiplier + WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
|
||||
AppBarHeight * fontSizeSqrtMultiplier + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + reportsPadding
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1674,12 +1768,13 @@ private fun FloatingDate(
|
||||
modifier: Modifier,
|
||||
mergedItems: State<MergedItems>,
|
||||
listState: State<LazyListState>,
|
||||
groupReports: State<GroupReports>
|
||||
) {
|
||||
val isNearBottom = remember(chatModel.chatId) { mutableStateOf(listState.value.firstVisibleItemIndex == 0) }
|
||||
val nearBottomIndex = remember(chatModel.chatId) { mutableStateOf(if (isNearBottom.value) -1 else 0) }
|
||||
val showDate = remember(chatModel.chatId) { mutableStateOf(false) }
|
||||
val density = LocalDensity.current.density
|
||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() })
|
||||
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(chatView = !groupReports.value.reportsView, groupReports.value.showBar).roundToPx() })
|
||||
val fontSizeSqrtMultiplier = fontSizeSqrtMultiplier
|
||||
val lastVisibleItemDate = remember {
|
||||
derivedStateOf {
|
||||
@@ -2439,6 +2534,7 @@ fun PreviewChatLayout() {
|
||||
unreadCount = unreadCount,
|
||||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
||||
composeView = {},
|
||||
groupReports = remember { mutableStateOf(GroupReports(0, false)) },
|
||||
attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) },
|
||||
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
|
||||
searchValue,
|
||||
@@ -2447,6 +2543,7 @@ fun PreviewChatLayout() {
|
||||
selectedChatItems = remember { mutableStateOf(setOf()) },
|
||||
back = {},
|
||||
info = {},
|
||||
showGroupReports = {},
|
||||
showMemberInfo = { _, _ -> },
|
||||
loadMessages = { _, _, _, _ -> },
|
||||
deleteMessage = { _, _ -> },
|
||||
@@ -2512,6 +2609,7 @@ fun PreviewGroupChatLayout() {
|
||||
unreadCount = unreadCount,
|
||||
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
|
||||
composeView = {},
|
||||
groupReports = remember { mutableStateOf(GroupReports(0, false)) },
|
||||
attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) },
|
||||
attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden),
|
||||
searchValue,
|
||||
@@ -2520,6 +2618,7 @@ fun PreviewGroupChatLayout() {
|
||||
selectedChatItems = remember { mutableStateOf(setOf()) },
|
||||
back = {},
|
||||
info = {},
|
||||
showGroupReports = {},
|
||||
showMemberInfo = { _, _ -> },
|
||||
loadMessages = { _, _, _, _ -> },
|
||||
deleteMessage = { _, _ -> },
|
||||
|
||||
+2
-2
@@ -309,12 +309,12 @@ fun ModalData.GroupChatInfoLayout(
|
||||
Box {
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
LazyColumnWithScrollBar(
|
||||
state = listState,
|
||||
contentPadding = if (oneHandUI.value) {
|
||||
PaddingValues(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + DEFAULT_PADDING + 5.dp, bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding())
|
||||
} else {
|
||||
PaddingValues(top = topPaddingToContent(false))
|
||||
},
|
||||
state = listState
|
||||
}
|
||||
) {
|
||||
item {
|
||||
Row(
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package chat.simplex.common.views.chat.group
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.item.ItemAction
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
data class GroupReports(
|
||||
val activeReports: Int,
|
||||
val reportsView: Boolean,
|
||||
val showArchived: Boolean = false
|
||||
) {
|
||||
val showBar: Boolean = activeReports > 0 && !reportsView
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GroupReportsView(staleChatId: State<String?>) {
|
||||
ChatView(staleChatId, reportsView = true, onComposed = {})
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GroupReportsAppBar(
|
||||
groupReports: State<GroupReports>,
|
||||
close: () -> Unit,
|
||||
showArchived: (Boolean) -> Unit,
|
||||
onSearchValueChanged: (String) -> Unit
|
||||
) {
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
val showMenu = remember { mutableStateOf(false) }
|
||||
val showSearch = rememberSaveable { mutableStateOf(false) }
|
||||
val onBackClicked = {
|
||||
if (!showSearch.value) {
|
||||
close()
|
||||
} else {
|
||||
onSearchValueChanged("")
|
||||
showSearch.value = false
|
||||
}
|
||||
}
|
||||
BackHandler(onBack = onBackClicked)
|
||||
DefaultAppBar(
|
||||
navigationButton = { NavigationButtonBack(onBackClicked) },
|
||||
fixedTitleText = stringResource(MR.strings.group_reports_member_reports),
|
||||
onTitleClick = null,
|
||||
onTop = !oneHandUI.value,
|
||||
showSearch = showSearch.value,
|
||||
onSearchValueChanged = onSearchValueChanged,
|
||||
buttons = {
|
||||
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_hide_archived) else stringResource(MR.strings.group_reports_show_archived),
|
||||
painterResource(MR.images.ic_add),
|
||||
onClick = {
|
||||
onClosedAction.value = {
|
||||
showArchived(!groupReports.value.showArchived)
|
||||
onClosedAction.value = {}
|
||||
}
|
||||
showMenu.value = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -72,11 +72,11 @@ fun TagListView(rhId: Long?, chat: Chat? = null, close: () -> Unit, reorderMode:
|
||||
|
||||
LazyColumnWithScrollBar(
|
||||
modifier = if (reorderMode) Modifier.dragContainer(dragDropState) else Modifier,
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(
|
||||
top = if (oneHandUI.value) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else topPaddingToContent,
|
||||
bottom = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else 0.dp
|
||||
),
|
||||
state = listState,
|
||||
verticalArrangement = if (oneHandUI.value) Arrangement.Bottom else Arrangement.Top,
|
||||
) {
|
||||
@Composable fun CreateList() {
|
||||
|
||||
@@ -424,6 +424,11 @@
|
||||
<string name="chat_list_notes">Notes</string>
|
||||
<string name="chat_list_all">All</string>
|
||||
<string name="chat_list_add_list">Add list</string>
|
||||
<string name="group_reports_active_one">1 report</string>
|
||||
<string name="group_reports_active">%d reports</string>
|
||||
<string name="group_reports_member_reports">Member reports</string>
|
||||
<string name="group_reports_show_archived">Show archived</string>
|
||||
<string name="group_reports_hide_archived">Hide archived</string>
|
||||
|
||||
<!-- ShareListView.kt -->
|
||||
<string name="share_message">Share message…</string>
|
||||
|
||||
+7
-3
@@ -36,6 +36,7 @@ actual fun LazyColumnWithScrollBar(
|
||||
flingBehavior: FlingBehavior,
|
||||
userScrollEnabled: Boolean,
|
||||
additionalBarOffset: State<Dp>?,
|
||||
additionalTopBar: State<Boolean>,
|
||||
chatBottomBar: State<Boolean>,
|
||||
fillMaxSize: Boolean,
|
||||
content: LazyListScope.() -> Unit
|
||||
@@ -93,7 +94,7 @@ actual fun LazyColumnWithScrollBar(
|
||||
val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier
|
||||
Box(Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).nestedScroll(connection)) {
|
||||
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
|
||||
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, chatBottomBar)
|
||||
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, additionalTopBar, chatBottomBar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +109,7 @@ actual fun LazyColumnWithScrollBarNoAppBar(
|
||||
flingBehavior: FlingBehavior,
|
||||
userScrollEnabled: Boolean,
|
||||
additionalBarOffset: State<Dp>?,
|
||||
additionalTopBar: State<Boolean>,
|
||||
chatBottomBar: State<Boolean>,
|
||||
content: LazyListScope.() -> Unit
|
||||
) {
|
||||
@@ -135,7 +137,7 @@ actual fun LazyColumnWithScrollBarNoAppBar(
|
||||
val scrollBarDraggingState = remember { mutableStateOf(false) }
|
||||
Box {
|
||||
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
|
||||
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, chatBottomBar)
|
||||
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, additionalTopBar, chatBottomBar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,11 +149,13 @@ private fun ScrollBar(
|
||||
scrollJob: MutableState<Job>,
|
||||
scrollBarDraggingState: MutableState<Boolean>,
|
||||
additionalBarHeight: State<Dp>?,
|
||||
additionalTopBar: State<Boolean>,
|
||||
chatBottomBar: State<Boolean>,
|
||||
) {
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
val topBarPadding = if (additionalTopBar.value) AppBarHeight * fontSizeSqrtMultiplier else 0.dp
|
||||
val padding = if (additionalBarHeight != null) {
|
||||
PaddingValues(top = if (oneHandUI.value && chatBottomBar.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value)
|
||||
PaddingValues(top = topBarPadding + if (oneHandUI.value && chatBottomBar.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value)
|
||||
} else if (reverseLayout) {
|
||||
PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user