ui: show spinner on chat deletion (#5597)

* android: show spinner on group deletion

* ios
This commit is contained in:
spaced4ndy
2025-01-31 22:28:32 +04:00
committed by GitHub
parent 9e000d6bce
commit 5591b72feb
2 changed files with 175 additions and 159 deletions
@@ -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
@@ -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)
}
}
}