mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
core, mobile: file error statuses, cancel sent file (#2193)
This commit is contained in:
@@ -1333,6 +1333,9 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
cleanupFile(aChatItem)
|
||||
case let .rcvFileProgressXFTP(user, aChatItem, _, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
case let .rcvFileError(user, aChatItem):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
cleanupFile(aChatItem)
|
||||
case let .sndFileStart(user, aChatItem, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
case let .sndFileComplete(user, aChatItem, _):
|
||||
@@ -1346,6 +1349,9 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
case let .sndFileCompleteXFTP(user, aChatItem, _):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
cleanupFile(aChatItem)
|
||||
case let .sndFileError(user, aChatItem):
|
||||
chatItemSimpleUpdate(user, aChatItem)
|
||||
cleanupFile(aChatItem)
|
||||
case let .callInvitation(invitation):
|
||||
m.callInvitations[invitation.contact.id] = invitation
|
||||
activateCall(invitation)
|
||||
|
||||
@@ -55,11 +55,13 @@ struct CIFileView: View {
|
||||
case .sndTransfer: return false
|
||||
case .sndComplete: return false
|
||||
case .sndCancelled: return false
|
||||
case .sndError: return false
|
||||
case .rcvInvitation: return true
|
||||
case .rcvAccepted: return true
|
||||
case .rcvTransfer: return false
|
||||
case .rcvComplete: return true
|
||||
case .rcvCancelled: return false
|
||||
case .rcvError: return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -130,6 +132,7 @@ struct CIFileView: View {
|
||||
}
|
||||
case .sndComplete: fileIcon("doc.fill", innerIcon: "checkmark", innerIconSize: 10)
|
||||
case .sndCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
|
||||
case .sndError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
|
||||
case .rcvInvitation:
|
||||
if fileSizeValid() {
|
||||
fileIcon("arrow.down.doc.fill", color: .accentColor)
|
||||
@@ -145,6 +148,7 @@ struct CIFileView: View {
|
||||
}
|
||||
case .rcvComplete: fileIcon("doc.fill")
|
||||
case .rcvCancelled: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
|
||||
case .rcvError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
|
||||
}
|
||||
} else {
|
||||
fileIcon("doc.fill")
|
||||
|
||||
@@ -93,10 +93,12 @@ struct CIImageView: View {
|
||||
case .sndTransfer: progressView()
|
||||
case .sndComplete: fileIcon("checkmark", 10, 13)
|
||||
case .sndCancelled: fileIcon("xmark", 10, 13)
|
||||
case .sndError: fileIcon("xmark", 10, 13)
|
||||
case .rcvInvitation: fileIcon("arrow.down", 10, 13)
|
||||
case .rcvAccepted: fileIcon("ellipsis", 14, 11)
|
||||
case .rcvTransfer: progressView()
|
||||
case .rcvCancelled: fileIcon("xmark", 10, 13)
|
||||
case .rcvError: fileIcon("xmark", 10, 13)
|
||||
default: EmptyView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,38 +199,33 @@ struct CIVideoView: View {
|
||||
case .xftp: progressCircle(sndProgress, sndTotal)
|
||||
case .smp: progressView()
|
||||
}
|
||||
case .sndComplete:
|
||||
Image(systemName: "checkmark")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 10, height: 10)
|
||||
.foregroundColor(.white)
|
||||
.padding(13)
|
||||
case .rcvInvitation:
|
||||
Image(systemName: "arrow.down")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 14, height: 14)
|
||||
.foregroundColor(.white)
|
||||
.padding(11)
|
||||
case .rcvAccepted:
|
||||
Image(systemName: "ellipsis")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 14, height: 14)
|
||||
.foregroundColor(.white)
|
||||
.padding(11)
|
||||
case .sndComplete: fileIcon("checkmark", 10, 13)
|
||||
case .sndCancelled: fileIcon("xmark", 10, 13)
|
||||
case .sndError: fileIcon("xmark", 10, 13)
|
||||
case .rcvInvitation: fileIcon("arrow.down", 10, 13)
|
||||
case .rcvAccepted: fileIcon("ellipsis", 14, 11)
|
||||
case let .rcvTransfer(rcvProgress, rcvTotal):
|
||||
if file.fileProtocol == .xftp && rcvProgress < rcvTotal {
|
||||
progressCircle(rcvProgress, rcvTotal)
|
||||
} else {
|
||||
progressView()
|
||||
}
|
||||
case .rcvCancelled: fileIcon("xmark", 10, 13)
|
||||
case .rcvError: fileIcon("xmark", 10, 13)
|
||||
default: EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func fileIcon(_ icon: String, _ size: CGFloat, _ padding: CGFloat) -> some View {
|
||||
Image(systemName: icon)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: size, height: size)
|
||||
.foregroundColor(.white)
|
||||
.padding(padding)
|
||||
}
|
||||
|
||||
private func progressView() -> some View {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
|
||||
@@ -108,11 +108,13 @@ struct VoiceMessagePlayer: View {
|
||||
case .sndTransfer: playbackButton()
|
||||
case .sndComplete: playbackButton()
|
||||
case .sndCancelled: playbackButton()
|
||||
case .sndError: playbackButton()
|
||||
case .rcvInvitation: loadingIcon()
|
||||
case .rcvAccepted: loadingIcon()
|
||||
case .rcvTransfer: loadingIcon()
|
||||
case .rcvComplete: playbackButton()
|
||||
case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
|
||||
case .rcvError: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
|
||||
}
|
||||
} else {
|
||||
playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel))
|
||||
|
||||
@@ -496,8 +496,8 @@ struct ChatView: View {
|
||||
}
|
||||
if ci.meta.itemDeleted == nil,
|
||||
let file = ci.file,
|
||||
file.cancellable {
|
||||
menu.append(cancelFileUIAction(file.fileId, sent: ci.chatDir.sent))
|
||||
let cancelAction = file.cancelAction {
|
||||
menu.append(cancelFileUIAction(file.fileId, cancelAction))
|
||||
}
|
||||
if !live || !ci.meta.isLive {
|
||||
menu.append(deleteUIAction())
|
||||
@@ -589,15 +589,16 @@ struct ChatView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func cancelFileUIAction(_ fileId: Int64, sent: Bool) -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Stop file", comment: "chat item action"),
|
||||
image: UIImage(systemName: "xmark")
|
||||
private func cancelFileUIAction(_ fileId: Int64, _ cancelAction: CancelAction) -> UIAction {
|
||||
return UIAction(
|
||||
title: cancelAction.uiAction,
|
||||
image: UIImage(systemName: "xmark"),
|
||||
attributes: [.destructive]
|
||||
) { _ in
|
||||
AlertManager.shared.showAlert(Alert(
|
||||
title: Text(sent ? "Stop sending file?" : "Stop receiving file?"),
|
||||
message: Text(sent ? "Sending file will be stopped." : "Receiving file will be stopped."),
|
||||
primaryButton: .destructive(Text("Stop")) {
|
||||
title: Text(cancelAction.alert.title),
|
||||
message: Text(cancelAction.alert.message),
|
||||
primaryButton: .destructive(Text(cancelAction.alert.confirm)) {
|
||||
Task {
|
||||
if let user = ChatModel.shared.currentUser {
|
||||
await cancelFile(user: user, fileId: fileId)
|
||||
|
||||
@@ -453,6 +453,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case rcvFileComplete(user: User, chatItem: AChatItem)
|
||||
case rcvFileCancelled(user: User, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer)
|
||||
case rcvFileSndCancelled(user: User, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer)
|
||||
case rcvFileError(user: User, chatItem: AChatItem)
|
||||
// sending file events
|
||||
case sndFileStart(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndFileComplete(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
@@ -460,6 +461,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case sndFileRcvCancelled(user: User, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
|
||||
case sndFileProgressXFTP(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64)
|
||||
case sndFileCompleteXFTP(user: User, chatItem: AChatItem, fileTransferMeta: FileTransferMeta)
|
||||
case sndFileError(user: User, chatItem: AChatItem)
|
||||
// call events
|
||||
case callInvitation(callInvitation: RcvCallInvitation)
|
||||
case callOffer(user: User, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool)
|
||||
case callAnswer(user: User, contact: Contact, answer: WebRTCSession)
|
||||
@@ -564,12 +567,14 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .rcvFileComplete: return "rcvFileComplete"
|
||||
case .rcvFileCancelled: return "rcvFileCancelled"
|
||||
case .rcvFileSndCancelled: return "rcvFileSndCancelled"
|
||||
case .rcvFileError: return "rcvFileError"
|
||||
case .sndFileStart: return "sndFileStart"
|
||||
case .sndFileComplete: return "sndFileComplete"
|
||||
case .sndFileCancelled: return "sndFileCancelled"
|
||||
case .sndFileRcvCancelled: return "sndFileRcvCancelled"
|
||||
case .sndFileProgressXFTP: return "sndFileProgressXFTP"
|
||||
case .sndFileCompleteXFTP: return "sndFileCompleteXFTP"
|
||||
case .sndFileError: return "sndFileError"
|
||||
case .callInvitation: return "callInvitation"
|
||||
case .callOffer: return "callOffer"
|
||||
case .callAnswer: return "callAnswer"
|
||||
@@ -677,12 +682,14 @@ public enum ChatResponse: Decodable, Error {
|
||||
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 .rcvFileError(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(u, chatItem, _, _): return withUser(u, String(describing: chatItem))
|
||||
case let .sndFileRcvCancelled(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 .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem))
|
||||
case let .sndFileError(u, chatItem): return withUser(u, String(describing: chatItem))
|
||||
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))")
|
||||
case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))")
|
||||
|
||||
@@ -2281,32 +2281,79 @@ public struct CIFile: Decodable {
|
||||
case .sndTransfer: return true
|
||||
case .sndComplete: return true
|
||||
case .sndCancelled: return true
|
||||
case .sndError: return true
|
||||
case .rcvInvitation: return false
|
||||
case .rcvAccepted: return false
|
||||
case .rcvTransfer: return false
|
||||
case .rcvCancelled: return false
|
||||
case .rcvComplete: return true
|
||||
case .rcvError: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var cancellable: Bool {
|
||||
public var cancelAction: CancelAction? {
|
||||
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
|
||||
case .sndStored: return sndCancelAction
|
||||
case .sndTransfer: return sndCancelAction
|
||||
case .sndComplete:
|
||||
if self.fileProtocol == .xftp {
|
||||
return revokeCancelAction
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .sndCancelled: return nil
|
||||
case .sndError: return nil
|
||||
case .rcvInvitation: return nil
|
||||
case .rcvAccepted: return rcvCancelAction
|
||||
case .rcvTransfer: return rcvCancelAction
|
||||
case .rcvCancelled: return nil
|
||||
case .rcvComplete: return nil
|
||||
case .rcvError: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct CancelAction {
|
||||
public var uiAction: String
|
||||
public var alert: AlertInfo
|
||||
}
|
||||
|
||||
public struct AlertInfo {
|
||||
public var title: LocalizedStringKey
|
||||
public var message: LocalizedStringKey
|
||||
public var confirm: LocalizedStringKey
|
||||
}
|
||||
|
||||
private var sndCancelAction = CancelAction(
|
||||
uiAction: NSLocalizedString("Stop file", comment: "cancel file action"),
|
||||
alert: AlertInfo(
|
||||
title: "Stop sending file?",
|
||||
message: "Sending file will be stopped.",
|
||||
confirm: "Stop"
|
||||
)
|
||||
)
|
||||
|
||||
private var revokeCancelAction = CancelAction(
|
||||
uiAction: NSLocalizedString("Revoke file", comment: "cancel file action"),
|
||||
alert: AlertInfo(
|
||||
title: "Revoke file?",
|
||||
message: "File will be deleted from servers.",
|
||||
confirm: "Revoke"
|
||||
)
|
||||
)
|
||||
|
||||
private var rcvCancelAction = CancelAction(
|
||||
uiAction: NSLocalizedString("Stop file", comment: "cancel file action"),
|
||||
alert: AlertInfo(
|
||||
title: "Stop receiving file?",
|
||||
message: "Receiving file will be stopped.",
|
||||
confirm: "Stop"
|
||||
)
|
||||
)
|
||||
|
||||
public enum FileProtocol: String, Decodable {
|
||||
case smp = "smp"
|
||||
case xftp = "xftp"
|
||||
@@ -2317,11 +2364,13 @@ public enum CIFileStatus: Decodable {
|
||||
case sndTransfer(sndProgress: Int64, sndTotal: Int64)
|
||||
case sndComplete
|
||||
case sndCancelled
|
||||
case sndError
|
||||
case rcvInvitation
|
||||
case rcvAccepted
|
||||
case rcvTransfer(rcvProgress: Int64, rcvTotal: Int64)
|
||||
case rcvComplete
|
||||
case rcvCancelled
|
||||
case rcvError
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
@@ -2329,11 +2378,13 @@ public enum CIFileStatus: Decodable {
|
||||
case let .sndTransfer(sndProgress, sndTotal): return "sndTransfer \(sndProgress) \(sndTotal)"
|
||||
case .sndComplete: return "sndComplete"
|
||||
case .sndCancelled: return "sndCancelled"
|
||||
case .sndError: return "sndError"
|
||||
case .rcvInvitation: return "rcvInvitation"
|
||||
case .rcvAccepted: return "rcvAccepted"
|
||||
case let .rcvTransfer(rcvProgress, rcvTotal): return "rcvTransfer \(rcvProgress) \(rcvTotal)"
|
||||
case .rcvComplete: return "rcvComplete"
|
||||
case .rcvCancelled: return "rcvCancelled"
|
||||
case .rcvError: return "rcvError"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user