mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-24 21:45:38 +00:00
ui: show spinner on chat deletion (#5597)
* android: show spinner on group deletion * ios
This commit is contained in:
@@ -27,72 +27,80 @@ struct ChatPreviewView: View {
|
||||
|
||||
var body: some View {
|
||||
let cItem = chat.chatItems.last
|
||||
return HStack(spacing: 8) {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
ChatInfoImage(chat: chat, size: dynamicSize(userFont).profileImageSize)
|
||||
chatPreviewImageOverlayIcon()
|
||||
.padding([.bottom, .trailing], 1)
|
||||
}
|
||||
.padding(.leading, 4)
|
||||
|
||||
let chatTs = if let cItem {
|
||||
cItem.meta.itemTs
|
||||
} else {
|
||||
chat.chatInfo.chatTs
|
||||
}
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top) {
|
||||
chatPreviewTitle()
|
||||
Spacer()
|
||||
(formatTimestampText(chatTs))
|
||||
.font(.subheadline)
|
||||
.frame(minWidth: 60, alignment: .trailing)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.padding(.top, 4)
|
||||
return ZStack {
|
||||
HStack(spacing: 8) {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
ChatInfoImage(chat: chat, size: dynamicSize(userFont).profileImageSize)
|
||||
chatPreviewImageOverlayIcon()
|
||||
.padding([.bottom, .trailing], 1)
|
||||
}
|
||||
.padding(.bottom, 4)
|
||||
.padding(.horizontal, 8)
|
||||
|
||||
ZStack(alignment: .topTrailing) {
|
||||
let chat = activeContentPreview?.chat ?? chat
|
||||
let ci = activeContentPreview?.ci ?? chat.chatItems.last
|
||||
let mc = ci?.content.msgContent
|
||||
HStack(alignment: .top) {
|
||||
let deleted = ci?.isDeletedContent == true || ci?.meta.itemDeleted != nil
|
||||
let showContentPreview = (showChatPreviews && chatModel.draftChatId != chat.id && !deleted) || activeContentPreview != nil
|
||||
if let ci, showContentPreview {
|
||||
chatItemContentPreview(chat, ci)
|
||||
}
|
||||
let mcIsVoice = switch mc { case .voice: true; default: false }
|
||||
if !mcIsVoice || !showContentPreview || mc?.text != "" || chatModel.draftChatId == chat.id {
|
||||
let hasFilePreview = if case .file = mc { true } else { false }
|
||||
chatMessagePreview(cItem, hasFilePreview)
|
||||
} else {
|
||||
Spacer()
|
||||
chatInfoIcon(chat).frame(minWidth: 37, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.stopPreviousRecPlay?.path) { _ in
|
||||
checkActiveContentPreview(chat, ci, mc)
|
||||
}
|
||||
.onChange(of: activeContentPreview) { _ in
|
||||
checkActiveContentPreview(chat, ci, mc)
|
||||
}
|
||||
.onChange(of: showFullscreenGallery) { _ in
|
||||
checkActiveContentPreview(chat, ci, mc)
|
||||
}
|
||||
chatStatusImage()
|
||||
.padding(.top, dynamicChatInfoSize * 1.44)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.trailing, 8)
|
||||
.padding(.leading, 4)
|
||||
|
||||
Spacer()
|
||||
let chatTs = if let cItem {
|
||||
cItem.meta.itemTs
|
||||
} else {
|
||||
chat.chatInfo.chatTs
|
||||
}
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top) {
|
||||
chatPreviewTitle()
|
||||
Spacer()
|
||||
(formatTimestampText(chatTs))
|
||||
.font(.subheadline)
|
||||
.frame(minWidth: 60, alignment: .trailing)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.padding(.bottom, 4)
|
||||
.padding(.horizontal, 8)
|
||||
|
||||
ZStack(alignment: .topTrailing) {
|
||||
let chat = activeContentPreview?.chat ?? chat
|
||||
let ci = activeContentPreview?.ci ?? chat.chatItems.last
|
||||
let mc = ci?.content.msgContent
|
||||
HStack(alignment: .top) {
|
||||
let deleted = ci?.isDeletedContent == true || ci?.meta.itemDeleted != nil
|
||||
let showContentPreview = (showChatPreviews && chatModel.draftChatId != chat.id && !deleted) || activeContentPreview != nil
|
||||
if let ci, showContentPreview {
|
||||
chatItemContentPreview(chat, ci)
|
||||
}
|
||||
let mcIsVoice = switch mc { case .voice: true; default: false }
|
||||
if !mcIsVoice || !showContentPreview || mc?.text != "" || chatModel.draftChatId == chat.id {
|
||||
let hasFilePreview = if case .file = mc { true } else { false }
|
||||
chatMessagePreview(cItem, hasFilePreview)
|
||||
} else {
|
||||
Spacer()
|
||||
chatInfoIcon(chat).frame(minWidth: 37, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
.onChange(of: chatModel.stopPreviousRecPlay?.path) { _ in
|
||||
checkActiveContentPreview(chat, ci, mc)
|
||||
}
|
||||
.onChange(of: activeContentPreview) { _ in
|
||||
checkActiveContentPreview(chat, ci, mc)
|
||||
}
|
||||
.onChange(of: showFullscreenGallery) { _ in
|
||||
checkActiveContentPreview(chat, ci, mc)
|
||||
}
|
||||
chatStatusImage()
|
||||
.padding(.top, dynamicChatInfoSize * 1.44)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.trailing, 8)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.opacity(deleting ? 0.4 : 1)
|
||||
.padding(.bottom, -8)
|
||||
|
||||
if deleting {
|
||||
ProgressView()
|
||||
.scaleEffect(2)
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.padding(.bottom, -8)
|
||||
.onChange(of: chatModel.deletedChats.contains(chat.chatInfo.id)) { contains in
|
||||
deleting = contains
|
||||
// Stop voice when deleting the chat
|
||||
|
||||
+105
-97
@@ -366,106 +366,114 @@ fun ChatPreviewView(
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
Box(contentAlignment = Alignment.BottomEnd) {
|
||||
ChatInfoImage(cInfo, size = 72.dp * fontSizeSqrtMultiplier)
|
||||
Box(Modifier.padding(end = 6.sp.toDp(), bottom = 6.sp.toDp())) {
|
||||
chatPreviewImageOverlayIcon()
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Row {
|
||||
Box(contentAlignment = Alignment.BottomEnd) {
|
||||
ChatInfoImage(cInfo, size = 72.dp * fontSizeSqrtMultiplier)
|
||||
Box(Modifier.padding(end = 6.sp.toDp(), bottom = 6.sp.toDp())) {
|
||||
chatPreviewImageOverlayIcon()
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row {
|
||||
Box(Modifier.weight(1f)) {
|
||||
chatPreviewTitle()
|
||||
}
|
||||
Spacer(Modifier.width(8.sp.toDp()))
|
||||
val ts = getTimestampText(chat.chatItems.lastOrNull()?.meta?.itemTs ?: chat.chatInfo.chatTs)
|
||||
ChatListTimestampView(ts)
|
||||
}
|
||||
Row(Modifier.heightIn(min = 46.sp.toDp()).fillMaxWidth()) {
|
||||
Row(Modifier.padding(top = 3.sp.toDp()).weight(1f)) {
|
||||
val activeVoicePreview: MutableState<(ActiveVoicePreview)?> = remember(chat.id) { mutableStateOf(null) }
|
||||
val chat = activeVoicePreview.value?.chat ?: chat
|
||||
val ci = activeVoicePreview.value?.ci ?: chat.chatItems.lastOrNull()
|
||||
val mc = ci?.content?.msgContent
|
||||
val deleted = ci?.isDeletedContent == true || ci?.meta?.itemDeleted != null
|
||||
val showContentPreview = (showChatPreviews && chatModelDraftChatId != chat.id && !deleted) || activeVoicePreview.value != null
|
||||
if (ci != null && showContentPreview) {
|
||||
chatItemContentPreview(chat, ci)
|
||||
}
|
||||
if (mc !is MsgContent.MCVoice || !showContentPreview || mc.text.isNotEmpty() || chatModelDraftChatId == chat.id) {
|
||||
Box(Modifier.offset(x = if (mc is MsgContent.MCFile && ci.meta.itemDeleted == null) -15.sp.toDp() else 0.dp)) {
|
||||
chatPreviewText()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(AudioPlayer.currentlyPlaying.value, activeVoicePreview.value) {
|
||||
val playing = AudioPlayer.currentlyPlaying.value
|
||||
when {
|
||||
playing == null -> activeVoicePreview.value = null
|
||||
activeVoicePreview.value == null -> if (mc is MsgContent.MCVoice && playing.fileSource.filePath == ci.file?.fileSource?.filePath) {
|
||||
activeVoicePreview.value = ActiveVoicePreview(chat, ci, mc)
|
||||
}
|
||||
|
||||
else -> if (playing.fileSource.filePath != ci?.file?.fileSource?.filePath) {
|
||||
activeVoicePreview.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(chatModel.deletedChats.value) {
|
||||
val voicePreview = activeVoicePreview.value
|
||||
// Stop voice when deleting the chat
|
||||
if (chatModel.deletedChats.value.contains(chatModel.remoteHostId() to chat.id) && voicePreview?.ci != null) {
|
||||
AudioPlayer.stop(voicePreview.ci)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(8.sp.toDp()))
|
||||
|
||||
Box(Modifier.widthIn(min = 34.sp.toDp()), contentAlignment = Alignment.TopEnd) {
|
||||
val n = chat.chatStats.unreadCount
|
||||
val showNtfsIcon = !chat.chatInfo.ntfsEnabled && (chat.chatInfo is ChatInfo.Direct || chat.chatInfo is ChatInfo.Group)
|
||||
if (n > 0 || chat.chatStats.unreadChat) {
|
||||
Text(
|
||||
if (n > 0) unreadCountStr(n) else "",
|
||||
color = Color.White,
|
||||
fontSize = 10.sp,
|
||||
style = TextStyle(textAlign = TextAlign.Center),
|
||||
modifier = Modifier
|
||||
.offset(y = 3.sp.toDp())
|
||||
.background(if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape)
|
||||
.badgeLayout()
|
||||
.padding(horizontal = 2.sp.toDp())
|
||||
.padding(vertical = 1.sp.toDp())
|
||||
)
|
||||
} else if (showNtfsIcon) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_notifications_off_filled),
|
||||
contentDescription = generalGetString(MR.strings.notifications),
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier
|
||||
.padding(start = 2.sp.toDp())
|
||||
.size(18.sp.toDp())
|
||||
.offset(x = 2.5.sp.toDp(), y = 2.sp.toDp())
|
||||
)
|
||||
} else if (chat.chatInfo.chatSettings?.favorite == true) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_star_filled),
|
||||
contentDescription = generalGetString(MR.strings.favorite_chat),
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier
|
||||
.size(20.sp.toDp())
|
||||
.offset(x = 2.5.sp.toDp())
|
||||
)
|
||||
}
|
||||
Box(
|
||||
Modifier.offset(y = 28.sp.toDp()),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
chatStatusImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Row {
|
||||
Box(Modifier.weight(1f)) {
|
||||
chatPreviewTitle()
|
||||
}
|
||||
Spacer(Modifier.width(8.sp.toDp()))
|
||||
val ts = getTimestampText(chat.chatItems.lastOrNull()?.meta?.itemTs ?: chat.chatInfo.chatTs)
|
||||
ChatListTimestampView(ts)
|
||||
}
|
||||
Row(Modifier.heightIn(min = 46.sp.toDp()).fillMaxWidth()) {
|
||||
Row(Modifier.padding(top = 3.sp.toDp()).weight(1f)) {
|
||||
val activeVoicePreview: MutableState<(ActiveVoicePreview)?> = remember(chat.id) { mutableStateOf(null) }
|
||||
val chat = activeVoicePreview.value?.chat ?: chat
|
||||
val ci = activeVoicePreview.value?.ci ?: chat.chatItems.lastOrNull()
|
||||
val mc = ci?.content?.msgContent
|
||||
val deleted = ci?.isDeletedContent == true || ci?.meta?.itemDeleted != null
|
||||
val showContentPreview = (showChatPreviews && chatModelDraftChatId != chat.id && !deleted) || activeVoicePreview.value != null
|
||||
if (ci != null && showContentPreview) {
|
||||
chatItemContentPreview(chat, ci)
|
||||
}
|
||||
if (mc !is MsgContent.MCVoice || !showContentPreview || mc.text.isNotEmpty() || chatModelDraftChatId == chat.id) {
|
||||
Box(Modifier.offset(x = if (mc is MsgContent.MCFile && ci.meta.itemDeleted == null) -15.sp.toDp() else 0.dp)) {
|
||||
chatPreviewText()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(AudioPlayer.currentlyPlaying.value, activeVoicePreview.value) {
|
||||
val playing = AudioPlayer.currentlyPlaying.value
|
||||
when {
|
||||
playing == null -> activeVoicePreview.value = null
|
||||
activeVoicePreview.value == null -> if (mc is MsgContent.MCVoice && playing.fileSource.filePath == ci.file?.fileSource?.filePath) {
|
||||
activeVoicePreview.value = ActiveVoicePreview(chat, ci, mc)
|
||||
}
|
||||
else -> if (playing.fileSource.filePath != ci?.file?.fileSource?.filePath) {
|
||||
activeVoicePreview.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(chatModel.deletedChats.value) {
|
||||
val voicePreview = activeVoicePreview.value
|
||||
// Stop voice when deleting the chat
|
||||
if (chatModel.deletedChats.value.contains(chatModel.remoteHostId() to chat.id) && voicePreview?.ci != null) {
|
||||
AudioPlayer.stop(voicePreview.ci)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(8.sp.toDp()))
|
||||
|
||||
Box(Modifier.widthIn(min = 34.sp.toDp()), contentAlignment = Alignment.TopEnd) {
|
||||
val n = chat.chatStats.unreadCount
|
||||
val showNtfsIcon = !chat.chatInfo.ntfsEnabled && (chat.chatInfo is ChatInfo.Direct || chat.chatInfo is ChatInfo.Group)
|
||||
if (n > 0 || chat.chatStats.unreadChat) {
|
||||
Text(
|
||||
if (n > 0) unreadCountStr(n) else "",
|
||||
color = Color.White,
|
||||
fontSize = 10.sp,
|
||||
style = TextStyle(textAlign = TextAlign.Center),
|
||||
modifier = Modifier
|
||||
.offset(y = 3.sp.toDp())
|
||||
.background(if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape)
|
||||
.badgeLayout()
|
||||
.padding(horizontal = 2.sp.toDp())
|
||||
.padding(vertical = 1.sp.toDp())
|
||||
)
|
||||
} else if (showNtfsIcon) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_notifications_off_filled),
|
||||
contentDescription = generalGetString(MR.strings.notifications),
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier
|
||||
.padding(start = 2.sp.toDp())
|
||||
.size(18.sp.toDp())
|
||||
.offset(x = 2.5.sp.toDp(), y = 2.sp.toDp())
|
||||
)
|
||||
} else if (chat.chatInfo.chatSettings?.favorite == true) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_star_filled),
|
||||
contentDescription = generalGetString(MR.strings.favorite_chat),
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier
|
||||
.size(20.sp.toDp())
|
||||
.offset(x = 2.5.sp.toDp())
|
||||
)
|
||||
}
|
||||
Box(
|
||||
Modifier.offset(y = 28.sp.toDp()),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
chatStatusImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
val deleting by remember(disabled, chat.id) { mutableStateOf(chatModel.deletedChats.value.contains(chat.remoteHostId to chat.chatInfo.id)) }
|
||||
if (deleting) {
|
||||
DefaultProgressView(description = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user