ios, android: cancel file UI; core: cancel file fixes (#2100)

backend fixes:
- check file is not complete on CancelFile,
- check file is not cancelled when processing XFTP events,
- mark SMP file cancelled if recipient cancelled in direct chat.
This commit is contained in:
spaced4ndy
2023-03-30 14:10:13 +04:00
committed by GitHub
parent cbcdeb2b43
commit 6b725a8ef7
14 changed files with 255 additions and 89 deletions

View File

@@ -1726,6 +1726,18 @@ class CIFile(
is CIFileStatus.RcvComplete -> true
}
val cancellable: Boolean = when (fileStatus) {
is CIFileStatus.SndStored -> fileProtocol != FileProtocol.XFTP // TODO true - enable when XFTP send supports cancel
is CIFileStatus.SndTransfer -> fileProtocol != FileProtocol.XFTP // TODO true
is CIFileStatus.SndComplete -> false
is CIFileStatus.SndCancelled -> false
is CIFileStatus.RcvInvitation -> false
is CIFileStatus.RcvAccepted -> true
is CIFileStatus.RcvTransfer -> true
is CIFileStatus.RcvCancelled -> false
is CIFileStatus.RcvComplete -> false
}
companion object {
fun getSample(
fileId: Long = 1,

View File

@@ -998,6 +998,25 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
}
}
suspend fun cancelFile(user: User, fileId: Long) {
val chatItem = apiCancelFile(fileId)
if (chatItem != null) {
chatItemSimpleUpdate(user, chatItem)
}
}
suspend fun apiCancelFile(fileId: Long): AChatItem? {
val r = sendCmd(CC.CancelFile(fileId))
return when (r) {
is CR.SndFileCancelled -> r.chatItem
is CR.RcvFileCancelled -> r.chatItem
else -> {
Log.d(TAG, "apiCancelFile bad response: ${r.responseType} ${r.details}")
null
}
}
}
suspend fun apiNewGroup(p: GroupProfile): GroupInfo? {
val userId = kotlin.runCatching { currentUserId("apiNewGroup") }.getOrElse { return null }
val r = sendCmd(CC.ApiNewGroup(userId, p))
@@ -1394,14 +1413,12 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
if (active(r.user)) {
chatModel.updateGroup(r.toGroup)
}
is CR.MemberRole ->
if (active(r.user)) {
chatModel.updateGroup(r.groupInfo)
}
is CR.RcvFileStart ->
chatItemSimpleUpdate(r.user, r.chatItem)
is CR.RcvFileComplete ->
chatItemSimpleUpdate(r.user, r.chatItem)
is CR.RcvFileSndCancelled ->
chatItemSimpleUpdate(r.user, r.chatItem)
is CR.RcvFileProgressXFTP ->
chatItemSimpleUpdate(r.user, r.chatItem)
is CR.SndFileStart ->
@@ -1419,6 +1436,8 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
removeFile(appContext, fileName)
}
}
is CR.SndFileRcvCancelled ->
chatItemSimpleUpdate(r.user, r.chatItem)
is CR.SndFileProgressXFTP ->
chatItemSimpleUpdate(r.user, r.chatItem)
is CR.CallInvitation -> {
@@ -1870,6 +1889,7 @@ sealed class CC {
class ApiChatRead(val type: ChatType, val id: Long, val range: ItemRange): CC()
class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC()
class ReceiveFile(val fileId: Long, val inline: Boolean?): CC()
class CancelFile(val fileId: Long): CC()
class ShowVersion(): CC()
val cmdString: String get() = when (this) {
@@ -1953,6 +1973,7 @@ sealed class CC {
is ApiChatRead -> "/_read chat ${chatRef(type, id)} from=${range.from} to=${range.to}"
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
is ReceiveFile -> if (inline == null) "/freceive $fileId" else "/freceive $fileId inline=${onOff(inline)}"
is CancelFile -> "/fcancel $fileId"
is ShowVersion -> "/version"
}
@@ -2037,6 +2058,7 @@ sealed class CC {
is ApiChatRead -> "apiChatRead"
is ApiChatUnread -> "apiChatUnread"
is ReceiveFile -> "receiveFile"
is CancelFile -> "cancelFile"
is ShowVersion -> "showVersion"
}
@@ -3046,13 +3068,14 @@ sealed class CR {
@Serializable @SerialName("rcvFileAcceptedSndCancelled") class RcvFileAcceptedSndCancelled(val user: User, val rcvFileTransfer: RcvFileTransfer): CR()
@Serializable @SerialName("rcvFileStart") class RcvFileStart(val user: User, val chatItem: AChatItem): CR()
@Serializable @SerialName("rcvFileComplete") class RcvFileComplete(val user: User, val chatItem: AChatItem): CR()
@Serializable @SerialName("rcvFileCancelled") class RcvFileCancelled(val user: User, val chatItem: AChatItem, val rcvFileTransfer: RcvFileTransfer): CR()
@Serializable @SerialName("rcvFileSndCancelled") class RcvFileSndCancelled(val user: User, val chatItem: AChatItem, val rcvFileTransfer: RcvFileTransfer): CR()
@Serializable @SerialName("rcvFileProgressXFTP") class RcvFileProgressXFTP(val user: User, val chatItem: AChatItem, val receivedSize: Long, val totalSize: Long): CR()
// sending file events
@Serializable @SerialName("sndFileStart") class SndFileStart(val user: User, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR()
@Serializable @SerialName("sndFileComplete") class SndFileComplete(val user: User, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR()
@Serializable @SerialName("sndFileCancelled") class SndFileCancelled(val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR()
@Serializable @SerialName("sndFileCancelled") class SndFileCancelled(val user: User, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta, val sndFileTransfers: List<SndFileTransfer>): CR()
@Serializable @SerialName("sndFileRcvCancelled") class SndFileRcvCancelled(val user: User, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR()
@Serializable @SerialName("sndGroupFileCancelled") class SndGroupFileCancelled(val user: User, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta, val sndFileTransfers: List<SndFileTransfer>): CR()
@Serializable @SerialName("sndFileProgressXFTP") class SndFileProgressXFTP(val user: User, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta, val sentSize: Long, val totalSize: Long): CR()
@Serializable @SerialName("callInvitation") class CallInvitation(val callInvitation: RcvCallInvitation): CR()
@Serializable @SerialName("callOffer") class CallOffer(val user: User, val contact: Contact, val callType: CallType, val offer: WebRTCSession, val sharedKey: String? = null, val askConfirmation: Boolean): CR()
@@ -3150,12 +3173,13 @@ sealed class CR {
is RcvFileAccepted -> "rcvFileAccepted"
is RcvFileStart -> "rcvFileStart"
is RcvFileComplete -> "rcvFileComplete"
is RcvFileCancelled -> "rcvFileCancelled"
is RcvFileSndCancelled -> "rcvFileSndCancelled"
is RcvFileProgressXFTP -> "rcvFileProgressXFTP"
is SndFileCancelled -> "sndFileCancelled"
is SndFileComplete -> "sndFileComplete"
is SndFileRcvCancelled -> "sndFileRcvCancelled"
is SndFileStart -> "sndFileStart"
is SndGroupFileCancelled -> "sndGroupFileCancelled"
is SndFileProgressXFTP -> "sndFileProgressXFTP"
is CallInvitation -> "callInvitation"
is CallOffer -> "callOffer"
@@ -3255,12 +3279,13 @@ sealed class CR {
is RcvFileAccepted -> withUser(user, json.encodeToString(chatItem))
is RcvFileStart -> withUser(user, json.encodeToString(chatItem))
is RcvFileComplete -> withUser(user, json.encodeToString(chatItem))
is RcvFileCancelled -> withUser(user, json.encodeToString(chatItem))
is RcvFileSndCancelled -> withUser(user, json.encodeToString(chatItem))
is RcvFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\nreceivedSize: $receivedSize\ntotalSize: $totalSize")
is SndFileCancelled -> json.encodeToString(chatItem)
is SndFileComplete -> withUser(user, json.encodeToString(chatItem))
is SndFileRcvCancelled -> withUser(user, json.encodeToString(chatItem))
is SndFileStart -> withUser(user, json.encodeToString(chatItem))
is SndGroupFileCancelled -> withUser(user, json.encodeToString(chatItem))
is SndFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\nsentSize: $sentSize\ntotalSize: $totalSize")
is CallInvitation -> "contact: ${callInvitation.contact.id}\ncallType: $callInvitation.callType\nsharedKey: ${callInvitation.sharedKey ?: ""}"
is CallOffer -> withUser(user, "contact: ${contact.id}\ncallType: $callType\nsharedKey: ${sharedKey ?: ""}\naskConfirmation: $askConfirmation\noffer: ${json.encodeToString(offer)}")

View File

@@ -230,10 +230,10 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) {
}
},
receiveFile = { fileId ->
val user = chatModel.currentUser.value
if (user != null) {
withApi { chatModel.controller.receiveFile(user, fileId) }
}
withApi { chatModel.controller.receiveFile(user, fileId) }
},
cancelFile = { fileId ->
withApi { chatModel.controller.cancelFile(user, fileId) }
},
joinGroup = { groupId ->
withApi { chatModel.controller.apiJoinGroup(groupId) }
@@ -313,6 +313,7 @@ fun ChatLayout(
loadPrevMessages: (ChatInfo) -> Unit,
deleteMessage: (Long, CIDeleteMode) -> Unit,
receiveFile: (Long) -> Unit,
cancelFile: (Long) -> Unit,
joinGroup: (Long) -> Unit,
startCall: (CallMediaType) -> Unit,
acceptCall: (Contact) -> Unit,
@@ -357,7 +358,7 @@ fun ChatLayout(
ChatItemsList(
chat, unreadCount, composeState, chatItems, searchValue,
useLinkPreviews, linkMode, chatModelIncognito, showMemberInfo, loadPrevMessages, deleteMessage,
receiveFile, joinGroup, acceptCall, acceptFeature, markRead, setFloatingButton, onComposed,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, markRead, setFloatingButton, onComposed,
)
}
}
@@ -530,6 +531,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
loadPrevMessages: (ChatInfo) -> Unit,
deleteMessage: (Long, CIDeleteMode) -> Unit,
receiveFile: (Long) -> Unit,
cancelFile: (Long) -> Unit,
joinGroup: (Long) -> Unit,
acceptCall: (Contact) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
@@ -638,11 +640,11 @@ fun BoxWithConstraintsScope.ChatItemsList(
} else {
Spacer(Modifier.size(42.dp))
}
ChatItemView(chat.chatInfo, cItem, composeState, provider, showMember = showMember, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
ChatItemView(chat.chatInfo, cItem, composeState, provider, showMember = showMember, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
}
} else {
Box(Modifier.padding(start = 104.dp, end = 12.dp).then(swipeableModifier)) {
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
}
}
} else { // direct message
@@ -653,7 +655,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
end = if (sent) 12.dp else 76.dp,
).then(swipeableModifier)
) {
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, scrollToItem = scrollToItem)
}
}
@@ -1060,6 +1062,7 @@ fun PreviewChatLayout() {
loadPrevMessages = { _ -> },
deleteMessage = { _, _ -> },
receiveFile = {},
cancelFile = {},
joinGroup = {},
startCall = {},
acceptCall = { _ -> },
@@ -1119,6 +1122,7 @@ fun PreviewGroupChatLayout() {
loadPrevMessages = { _ -> },
deleteMessage = { _, _ -> },
receiveFile = {},
cancelFile = {},
joinGroup = {},
startCall = {},
acceptCall = { _ -> },

View File

@@ -43,6 +43,7 @@ fun ChatItemView(
linkMode: SimplexLinkMode,
deleteMessage: (Long, CIDeleteMode) -> Unit,
receiveFile: (Long) -> Unit,
cancelFile: (Long) -> Unit,
joinGroup: (Long) -> Unit,
acceptCall: (Contact) -> Unit,
scrollToItem: (Long) -> Unit,
@@ -168,6 +169,9 @@ fun ChatItemView(
}
)
}
if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancellable) {
CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile)
}
if (!(live && cItem.meta.isLive)) {
DeleteItemAction(cItem, showMenu, questionText = deleteMessageQuestionText(), deleteMessage)
}
@@ -270,6 +274,23 @@ fun ChatItemView(
}
}
@Composable
fun CancelFileItemAction(
fileId: Long,
showMenu: MutableState<Boolean>,
cancelFile: (Long) -> Unit
) {
ItemAction(
stringResource(R.string.cancel_verb),
Icons.Outlined.Close,
onClick = {
showMenu.value = false
cancelFileAlertDialog(fileId, cancelFile = cancelFile)
},
color = Color.Red
)
}
@Composable
fun DeleteItemAction(
cItem: ChatItem,
@@ -323,6 +344,18 @@ fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, color: Colo
}
}
fun cancelFileAlertDialog(fileId: Long, cancelFile: (Long) -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.cancel_file__question),
text = generalGetString(R.string.file_transfer_will_be_cancelled_warning),
confirmText = generalGetString(R.string.confirm_verb),
destructive = true,
onConfirm = {
cancelFile(fileId)
}
)
}
fun deleteMessageAlertDialog(chatItem: ChatItem, questionText: String, deleteMessage: (Long, CIDeleteMode) -> Unit) {
AlertManager.shared.showAlertDialogButtons(
title = generalGetString(R.string.delete_message__question),
@@ -383,6 +416,7 @@ fun PreviewChatItemView() {
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
deleteMessage = { _, _ -> },
receiveFile = {},
cancelFile = {},
joinGroup = {},
acceptCall = { _ -> },
scrollToItem = {},
@@ -403,6 +437,7 @@ fun PreviewChatItemViewDeletedContent() {
composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) },
deleteMessage = { _, _ -> },
receiveFile = {},
cancelFile = {},
joinGroup = {},
acceptCall = { _ -> },
scrollToItem = {},

View File

@@ -191,6 +191,8 @@
<string name="delete_member_message__question">Delete member message?</string>
<string name="moderate_message_will_be_deleted_warning">The message will be deleted for all members.</string>
<string name="moderate_message_will_be_marked_warning">The message will be marked as moderated for all members.</string>
<string name="cancel_file__question">Cancel file transfer?</string>
<string name="file_transfer_will_be_cancelled_warning">File transfer will be cancelled. If it\'s in progress it will be stoppped.</string>
<string name="for_me_only">Delete for me</string>
<string name="for_everybody">For everyone</string>

View File

@@ -750,6 +750,23 @@ func apiReceiveFile(fileId: Int64, inline: Bool? = nil) async -> AChatItem? {
return nil
}
func cancelFile(user: User, fileId: Int64) async {
if let chatItem = await apiCancelFile(fileId: fileId) {
DispatchQueue.main.async { chatItemSimpleUpdate(user, chatItem) }
}
}
func apiCancelFile(fileId: Int64) async -> AChatItem? {
let r = await chatSendCmd(.cancelFile(fileId: fileId))
switch r {
case let .sndFileCancelled(_, chatItem, _, _) : return chatItem
case let .rcvFileCancelled(_, chatItem, _) : return chatItem
default:
logger.error("apiCancelFile error: \(String(describing: r))")
return nil
}
}
func networkErrorAlert(_ r: ChatResponse) -> Bool {
let am = AlertManager.shared
switch r {
@@ -1321,6 +1338,8 @@ func processReceivedMsg(_ res: ChatResponse) async {
chatItemSimpleUpdate(user, aChatItem)
case let .rcvFileComplete(user, aChatItem):
chatItemSimpleUpdate(user, aChatItem)
case let .rcvFileSndCancelled(user, aChatItem, _):
chatItemSimpleUpdate(user, aChatItem)
case let .rcvFileProgressXFTP(user, aChatItem, _, _):
chatItemSimpleUpdate(user, aChatItem)
case let .sndFileStart(user, aChatItem, _):
@@ -1334,6 +1353,8 @@ func processReceivedMsg(_ res: ChatResponse) async {
let fileName = cItem.file?.filePath {
removeFile(fileName)
}
case let .sndFileRcvCancelled(user, aChatItem, _):
chatItemSimpleUpdate(user, aChatItem)
case let .sndFileProgressXFTP(user, aChatItem, _, _, _):
chatItemSimpleUpdate(user, aChatItem)
case let .callInvitation(invitation):

View File

@@ -489,6 +489,11 @@ struct ChatView: View {
if revealed {
menu.append(hideUIAction())
}
if ci.meta.itemDeleted == nil,
let file = ci.file,
file.cancellable {
menu.append(cancelFileUIAction(file.fileId))
}
if !live || !ci.meta.isLive {
menu.append(deleteUIAction())
}
@@ -579,6 +584,27 @@ struct ChatView: View {
}
}
private func cancelFileUIAction(_ fileId: Int64) -> UIAction {
UIAction(
title: NSLocalizedString("Cancel", comment: "chat item action"),
image: UIImage(systemName: "xmark"),
attributes: [.destructive]
) { _ in
AlertManager.shared.showAlert(Alert(
title: Text("Cancel file transfer?"),
message: Text("File transfer will be cancelled. If it's in progress it will be stoppped."),
primaryButton: .destructive(Text("Confirm")) {
Task {
if let user = ChatModel.shared.currentUser {
await cancelFile(user: user, fileId: fileId)
}
}
},
secondaryButton: .cancel()
))
}
}
private func hideUIAction() -> UIAction {
UIAction(
title: NSLocalizedString("Hide", comment: "chat item action"),

View File

@@ -100,6 +100,7 @@ public enum ChatCommand {
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
case receiveFile(fileId: Int64, inline: Bool?)
case cancelFile(fileId: Int64)
case showVersion
case string(String)
@@ -205,6 +206,7 @@ public enum ChatCommand {
return "/freceive \(fileId) inline=\(onOff(inline))"
}
return "/freceive \(fileId)"
case let .cancelFile(fileId): return "/fcancel \(fileId)"
case .showVersion: return "/version"
case let .string(str): return str
}
@@ -300,6 +302,7 @@ public enum ChatCommand {
case .apiChatRead: return "apiChatRead"
case .apiChatUnread: return "apiChatUnread"
case .receiveFile: return "receiveFile"
case .cancelFile: return "cancelFile"
case .showVersion: return "showVersion"
case .string: return "console command"
}
@@ -448,12 +451,13 @@ public enum ChatResponse: Decodable, Error {
case rcvFileStart(user: User, chatItem: AChatItem)
case rcvFileProgressXFTP(user: User, chatItem: AChatItem, receivedSize: Int64, totalSize: Int64)
case rcvFileComplete(user: User, chatItem: AChatItem)
case rcvFileCancelled(user: User, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer)
case rcvFileSndCancelled(user: User, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer)
// sending file events
case sndFileStart(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
case sndFileComplete(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
case sndFileCancelled(chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
case sndFileCancelled(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer])
case sndFileRcvCancelled(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
case sndGroupFileCancelled(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer])
case sndFileProgressXFTP(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64)
case callInvitation(callInvitation: RcvCallInvitation)
case callOffer(user: User, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool)
@@ -557,11 +561,12 @@ public enum ChatResponse: Decodable, Error {
case .rcvFileStart: return "rcvFileStart"
case .rcvFileProgressXFTP: return "rcvFileProgressXFTP"
case .rcvFileComplete: return "rcvFileComplete"
case .rcvFileCancelled: return "rcvFileCancelled"
case .rcvFileSndCancelled: return "rcvFileSndCancelled"
case .sndFileStart: return "sndFileStart"
case .sndFileComplete: return "sndFileComplete"
case .sndFileCancelled: return "sndFileCancelled"
case .sndFileRcvCancelled: return "sndFileRcvCancelled"
case .sndGroupFileCancelled: return "sndGroupFileCancelled"
case .sndFileProgressXFTP: return "sndFileProgressXFTP"
case .callInvitation: return "callInvitation"
case .callOffer: return "callOffer"
@@ -668,11 +673,12 @@ public enum ChatResponse: Decodable, Error {
case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem))
case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)")
case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem))
case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .sndFileCancelled(chatItem, _): return String(describing: chatItem)
case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem))
case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .sndGroupFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem))
case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)")
case let .callInvitation(inv): return String(describing: inv)
case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))")

View File

@@ -2236,6 +2236,22 @@ public struct CIFile: Decodable {
}
}
}
public var cancellable: Bool {
get {
switch self.fileStatus {
case .sndStored: return self.fileProtocol != .xftp // TODO true - enable when XFTP send supports cancel
case .sndTransfer: return self.fileProtocol != .xftp // TODO true
case .sndComplete: return false
case .sndCancelled: return false
case .rcvInvitation: return false
case .rcvAccepted: return true
case .rcvTransfer: return true
case .rcvCancelled: return false
case .rcvComplete: return false
}
}
}
}
public enum FileProtocol: String, Decodable {