android, desktop: knocking UI improvements and fixes (#5841)

This commit is contained in:
spaced4ndy
2025-04-22 16:01:37 +00:00
committed by GitHub
parent 4a103c94c1
commit ef12bb9a6c
11 changed files with 65 additions and 38 deletions
@@ -376,7 +376,7 @@ object ChatModel {
updateContact(rhId, updatedContact)
}
suspend fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo, groupChatScope = null)) // TODO [knocking] review
suspend fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo, groupChatScope = null))
private suspend fun updateChat(rhId: Long?, cInfo: ChatInfo, addMissing: Boolean = true) {
if (hasChat(rhId, cInfo.id)) {
@@ -567,7 +567,6 @@ object ChatModel {
}
}
// TODO [knocking] why does this function not use `withContext(Dispatchers.Main) { ... }` ?
fun removeChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
// update chat list
if (cInfo.groupChatScope() == null) {
@@ -191,6 +191,7 @@ class AppPreferences {
val oneHandUICardShown = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN, false)
val addressCreationCardShown = mkBoolPreference(SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN, false)
val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true)
val showReportsInSupportChatAlert = mkBoolPreference(SHARED_PREFS_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT, true)
val appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null)
val appUpdateChannel = mkEnumPreference(SHARED_PREFS_APP_UPDATE_CHANNEL, AppUpdatesChannel.DISABLED) { AppUpdatesChannel.entries.firstOrNull { it.name == this } }
val appSkippedUpdate = mkStrPreference(SHARED_PREFS_APP_SKIPPED_UPDATE, "")
@@ -273,6 +274,7 @@ class AppPreferences {
liveMessageAlertShown to false,
showHiddenProfilesNotice to true,
showMuteProfileAlert to true,
showReportsInSupportChatAlert to true,
showDeleteConversationNotice to true,
showDeleteContactNotice to true,
)
@@ -431,6 +433,7 @@ class AppPreferences {
private const val SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN = "OneHandUICardShown"
private const val SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN = "AddressCreationCardShown"
private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert"
private const val SHARED_PREFS_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT = "ShowReportsInSupportChatAlert"
private const val SHARED_PREFS_STORE_DB_PASSPHRASE = "StoreDBPassphrase"
private const val SHARED_PREFS_INITIAL_RANDOM_DB_PASSPHRASE = "InitialRandomDBPassphrase"
private const val SHARED_PREFS_ENCRYPTED_DB_PASSPHRASE = "EncryptedDBPassphrase"
@@ -2711,7 +2714,6 @@ object ChatController {
}
is CR.MemberAcceptedByOther ->
if (active(r.user)) {
// TODO [knocking] update secondary context?
withContext(Dispatchers.Main) {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
@@ -124,7 +124,7 @@ fun ChatView(
val supportChatInfo = ChatInfo.Group(chatInfo.groupInfo, groupChatScope = scopeInfo)
showMemberSupportChatView(
chatModel.chatId,
scrollToItemId = mutableStateOf(null),
scrollToItemId,
supportChatInfo,
scopeInfo
)
@@ -371,7 +371,7 @@ fun ChatView(
}
ModalManager.end.showModalCloseable(true) { close ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close)
GroupMemberInfoView(chatRh, groupInfo, mem, scrollToItemId, stats, code, chatModel, close, close)
}
}
}
@@ -806,7 +806,7 @@ fun ChatLayout(
}
}
}
if (chatsCtx.contentTag == MsgContentTag.Report) { // TODO [knocking] similar bar for support chats, without archiveItems?
if (chatsCtx.contentTag == MsgContentTag.Report) {
Column(
Modifier
.layoutId(CHAT_COMPOSE_LAYOUT_ID)
@@ -1322,8 +1322,8 @@ fun BoxScope.ChatItemsList(
val chatInfoUpdated = rememberUpdatedState(chatInfo)
val scope = rememberCoroutineScope()
val scrollToItem: (Long) -> Unit = remember {
// In group reports just set the itemId to scroll to so the main ChatView will handle scrolling
if (chatsCtx.contentTag == MsgContentTag.Report) return@remember { scrollToItemId.value = it }
// In secondary chat just set the itemId to scroll to so the main ChatView will handle scrolling
if (chatsCtx.contentTag == MsgContentTag.Report || chatsCtx.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext) return@remember { scrollToItemId.value = it }
scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages)
}
val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(chatsCtx, remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem) }
@@ -2167,7 +2167,6 @@ private fun FloatingDate(
}
}
// TODO [knocking] same for member support chats?
@Composable
private fun SaveReportsStateOnDispose(chatsCtx: ChatModel.ChatsContext, listState: State<LazyListState>) {
DisposableEffect(Unit) {
@@ -568,17 +568,21 @@ fun ComposeView(
}
}
fun showReportsInSupportChatAlert() {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.report_sent_alert_title),
text = generalGetString(MR.strings.report_sent_alert_msg_view_in_support_chat),
confirmText = generalGetString(MR.strings.ok),
dismissText = generalGetString(MR.strings.dont_show_again),
onDismiss = {
chatModel.controller.appPrefs.showReportsInSupportChatAlert.set(false)
},
)
}
suspend fun sendReport(reportReason: ReportReason, chatItemId: Long): List<ChatItem>? {
val cItems = chatModel.controller.apiReportMessage(chat.remoteHostId, chat.chatInfo.apiId, chatItemId, reportReason, msgText)
if (cItems != null) {
// TODO [knocking] create report chat items in support scope
withContext(Dispatchers.Main) {
cItems.forEach { chatItem ->
chatsCtx.addChatItem(chat.remoteHostId, chat.chatInfo, chatItem.chatItem)
}
}
}
if (chatModel.controller.appPrefs.showReportsInSupportChatAlert.get()) showReportsInSupportChatAlert()
return cItems?.map { it.chatItem }
}
@@ -101,21 +101,21 @@ fun SelectedItemsButtonsToolbar(
)
}
IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value) {
IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value && chatsCtx.secondaryContextFilter == null) {
Icon(
painterResource(MR.images.ic_flag),
null,
Modifier.size(22.dp),
tint = if (!moderateEnabled.value || deleteCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error
tint = if (!moderateEnabled.value || deleteCountProhibited.value || chatsCtx.secondaryContextFilter != null) MaterialTheme.colors.secondary else MaterialTheme.colors.error
)
}
IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value) {
IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value && chatsCtx.secondaryContextFilter == null) {
Icon(
painterResource(MR.images.ic_forward),
null,
Modifier.size(22.dp),
tint = if (!forwardEnabled.value || forwardCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
tint = if (!forwardEnabled.value || forwardCountProhibited.value || chatsCtx.secondaryContextFilter != null) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
)
}
}
@@ -78,8 +78,6 @@ fun ModalData.GroupChatInfoView(
.filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
.sortedByDescending { it.memberRole }
Log.e(TAG, "######### GroupChatInfoView chatModel.groupMembers length = ${chatModel.groupMembers.value.count()}")
GroupChatInfoLayout(
chat,
groupInfo,
@@ -129,7 +127,7 @@ fun ModalData.GroupChatInfoView(
}
ModalManager.end.showModalCloseable(true) { closeCurrent ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(rhId, groupInfo, mem, stats, code, chatModel, closeCurrent) {
GroupMemberInfoView(rhId, groupInfo, mem, scrollToItemId, stats, code, chatModel, closeCurrent) {
closeCurrent()
close()
}
@@ -148,6 +146,7 @@ fun ModalData.GroupChatInfoView(
MemberSupportView(
chat,
groupInfo,
scrollToItemId,
close
)
}
@@ -330,9 +329,11 @@ fun AddGroupMembersButton(
}
@Composable
fun UserSupportChatButton(groupInfo: GroupInfo) {
fun UserSupportChatButton(
groupInfo: GroupInfo,
scrollToItemId: MutableState<Long?>
) {
val scope = rememberCoroutineScope()
val scrollToItemId: MutableState<Long?> = remember { mutableStateOf(null) }
SettingsActionItem(
painterResource(MR.images.ic_flag),
@@ -463,7 +464,7 @@ fun ModalData.GroupChatInfoLayout(
SectionView {
if (groupInfo.membership.supportChat != null) {
anyTopSectionRowShow = true
UserSupportChatButton(groupInfo)
UserSupportChatButton(groupInfo, scrollToItemId)
}
if (groupInfo.businessChat == null && groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
anyTopSectionRowShow = true
@@ -738,7 +739,7 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) {
@Composable
private fun MemberSupportButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_flag), // TODO [knocking] change icon
painterResource(MR.images.ic_flag),
stringResource(MR.strings.member_support),
click = onClick
)
@@ -45,6 +45,7 @@ fun GroupMemberInfoView(
rhId: Long?,
groupInfo: GroupInfo,
member: GroupMember,
scrollToItemId: MutableState<Long?>,
connectionStats: ConnectionStats?,
connectionCode: String?,
chatModel: ChatModel,
@@ -79,6 +80,7 @@ fun GroupMemberInfoView(
rhId = rhId,
groupInfo,
member,
scrollToItemId,
connStats,
newRole,
developerTools,
@@ -269,6 +271,7 @@ fun GroupMemberInfoLayout(
rhId: Long?,
groupInfo: GroupInfo,
member: GroupMember,
scrollToItemId: MutableState<Long?>,
connStats: MutableState<ConnectionStats?>,
newRole: MutableState<GroupMemberRole>,
developerTools: Boolean,
@@ -302,7 +305,6 @@ fun GroupMemberInfoLayout(
@Composable
fun SupportChatButton() {
val scope = rememberCoroutineScope()
val scrollToItemId: MutableState<Long?> = remember { mutableStateOf(null) } // TODO [knocking] scroll to report from support chat?
SettingsActionItem(
painterResource(MR.images.ic_flag),
@@ -908,6 +910,7 @@ fun PreviewGroupMemberInfoLayout() {
rhId = null,
groupInfo = GroupInfo.sampleData,
member = GroupMember.sampleData,
scrollToItemId = remember { mutableStateOf(null) },
connStats = remember { mutableStateOf(null) },
newRole = remember { mutableStateOf(GroupMemberRole.Member) },
developerTools = false,
@@ -15,7 +15,15 @@ import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.*
@Composable
private fun GroupReportsView(reportsChatsCtx: ChatModel.ChatsContext, staleChatId: State<String?>, scrollToItemId: MutableState<Long?>) {
private fun GroupReportsView(
reportsChatsCtx: ChatModel.ChatsContext,
staleChatId: State<String?>,
scrollToItemId: MutableState<Long?>,
close: () -> Unit
) {
KeyChangeEffect(chatModel.chatId.value) {
close()
}
ChatView(reportsChatsCtx, staleChatId, scrollToItemId, onComposed = {})
}
@@ -75,7 +83,7 @@ suspend fun showGroupReportsView(staleChatId: State<String?>, scrollToItemId: Mu
ModalView({}, showAppBar = false) {
val chatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chatModel.chatId.value }?.chatInfo } }.value
if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) {
GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId)
GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId, close)
} else {
LaunchedEffect(Unit) {
close()
@@ -21,7 +21,14 @@ import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@Composable
private fun MemberSupportChatView(memberSupportChatsCtx: ChatModel.ChatsContext, staleChatId: State<String?>, scrollToItemId: MutableState<Long?>) {
private fun MemberSupportChatView(
memberSupportChatsCtx: ChatModel.ChatsContext,
staleChatId: State<String?>,
scrollToItemId: MutableState<Long?>
) {
KeyChangeEffect(chatModel.chatId.value) {
ModalManager.end.closeModals()
}
ChatView(memberSupportChatsCtx, staleChatId, scrollToItemId, onComposed = {})
}
@@ -34,15 +34,17 @@ import kotlinx.coroutines.launch
fun ModalData.MemberSupportView(
chat: Chat,
groupInfo: GroupInfo,
scrollToItemId: MutableState<Long?>,
close: () -> Unit
) {
KeyChangeEffect(chat.id) {
close()
KeyChangeEffect(chatModel.chatId.value) {
ModalManager.end.closeModals()
}
ModalView(close = close) {
MemberSupportViewLayout(
chat,
groupInfo
groupInfo,
scrollToItemId
)
}
}
@@ -50,11 +52,11 @@ fun ModalData.MemberSupportView(
@Composable
private fun ModalData.MemberSupportViewLayout(
chat: Chat,
groupInfo: GroupInfo
groupInfo: GroupInfo,
scrollToItemId: MutableState<Long?>
) {
val oneHandUI = remember { ChatController.appPrefs.oneHandUI.state }
val scope = rememberCoroutineScope()
val scrollToItemId: MutableState<Long?> = remember { mutableStateOf(null) } // TODO [knocking] scroll to report from support chat?
val membersWithChats = remember { chatModel.groupMembers }.value
.filter { it.supportChat != null && it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
@@ -509,6 +509,8 @@
<string name="report_compose_reason_header_community">Report violation: only group moderators will see it.</string>
<string name="report_compose_reason_header_illegal">Report content: only group moderators will see it.</string>
<string name="report_compose_reason_header_other">Report other: only group moderators will see it.</string>
<string name="report_sent_alert_title">Report sent to moderators</string>
<string name="report_sent_alert_msg_view_in_support_chat">You can view your reports in Support Chat.</string>
<!-- Images - chat.simplex.app.views.chat.item.CIImageView.kt -->
<string name="image_descr">Image</string>