diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index da69e599b7..0c43ebe41a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -62,6 +62,7 @@ struct CIFileView: View { case .rcvComplete: return true case .rcvCancelled: return false case .rcvError: return false + case .invalid: return false } } return false @@ -149,6 +150,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) + case .invalid: fileIcon("doc.fill", innerIcon: "questionmark", innerIconSize: 10) } } else { fileIcon("doc.fill") diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index a9eadc5aa2..b13ee52829 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -99,6 +99,7 @@ struct CIImageView: View { case .rcvTransfer: progressView() case .rcvCancelled: fileIcon("xmark", 10, 13) case .rcvError: fileIcon("xmark", 10, 13) + case .invalid: fileIcon("questionmark", 10, 13) default: EmptyView() } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index 0232be700b..4387614918 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -212,6 +212,7 @@ struct CIVideoView: View { } case .rcvCancelled: fileIcon("xmark", 10, 13) case .rcvError: fileIcon("xmark", 10, 13) + case .invalid: fileIcon("questionmark", 10, 13) default: EmptyView() } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index 513723224a..167823934e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -144,6 +144,7 @@ struct VoiceMessagePlayer: View { case .rcvComplete: playbackButton() case .rcvCancelled: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) case .rcvError: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) + case .invalid: playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) } } else { playPauseIcon("play.fill", Color(uiColor: .tertiaryLabel)) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 7c309deb79..368961492b 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2339,6 +2339,7 @@ public enum CIStatus: Decodable { case sndError(agentError: String) case rcvNew case rcvRead + case invalid(text: String) var id: String { switch self { @@ -2349,6 +2350,7 @@ public enum CIStatus: Decodable { case .sndError: return "sndError" case .rcvNew: return "rcvNew" case .rcvRead: return "rcvRead" + case .invalid: return "invalid" } } @@ -2365,6 +2367,7 @@ public enum CIStatus: Decodable { case .sndError: return ("exclamationmark.triangle.fill", .yellow) case .rcvNew: return ("circlebadge.fill", Color.accentColor) case .rcvRead: return nil + case .invalid: return ("questionmark", metaColor) } } @@ -2377,6 +2380,7 @@ public enum CIStatus: Decodable { case .sndError: return NSLocalizedString("Error sending message", comment: "item status text") case .rcvNew: return NSLocalizedString("Message received", comment: "item status text") case .rcvRead: return NSLocalizedString("Message read", comment: "item status text") + case .invalid: return NSLocalizedString("Invalid status", comment: "item status text") } } @@ -2389,6 +2393,7 @@ public enum CIStatus: Decodable { case let .sndError(agentError): return String.localizedStringWithFormat(NSLocalizedString("Unexpected message delivery error: %@", comment: "item status description"), agentError) case .rcvNew: return NSLocalizedString("New message from this sender.", comment: "item status description") case .rcvRead: return NSLocalizedString("You've read this received message.", comment: "item status description") + case let .invalid(text): return text } } } @@ -2656,6 +2661,7 @@ public struct CIFile: Decodable { case .rcvCancelled: return false case .rcvComplete: return true case .rcvError: return false + case .invalid: return false } } } @@ -2679,6 +2685,7 @@ public struct CIFile: Decodable { case .rcvCancelled: return nil case .rcvComplete: return nil case .rcvError: return nil + case .invalid: return nil } } } @@ -2739,6 +2746,7 @@ public enum CIFileStatus: Decodable, Equatable { case rcvComplete case rcvCancelled case rcvError + case invalid(text: String) var id: String { switch self { @@ -2753,6 +2761,7 @@ public enum CIFileStatus: Decodable, Equatable { case .rcvComplete: return "rcvComplete" case .rcvCancelled: return "rcvCancelled" case .rcvError: return "rcvError" + case .invalid: return "invalid" } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index efafa22a6a..3e83ef76fe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1715,6 +1715,7 @@ sealed class CIStatus { @Serializable @SerialName("sndError") class SndError(val agentError: String): CIStatus() @Serializable @SerialName("rcvNew") class RcvNew: CIStatus() @Serializable @SerialName("rcvRead") class RcvRead: CIStatus() + @Serializable @SerialName("invalid") class Invalid(val text: String): CIStatus() fun statusIcon( primaryColor: Color, @@ -1738,6 +1739,7 @@ sealed class CIStatus { is SndError -> MR.images.ic_warning_filled to WarningYellow is RcvNew -> MR.images.ic_circle_filled to primaryColor is RcvRead -> null + is CIStatus.Invalid -> MR.images.ic_question_mark to metaColor } val statusText: String get() = when (this) { @@ -1748,6 +1750,7 @@ sealed class CIStatus { is SndError -> generalGetString(MR.strings.item_status_snd_error_text) is RcvNew -> generalGetString(MR.strings.item_status_rcv_new_text) is RcvRead -> generalGetString(MR.strings.item_status_rcv_read_text) + is Invalid -> "Invalid status" } val statusDescription: String get() = when (this) { @@ -1758,6 +1761,7 @@ sealed class CIStatus { is SndError -> String.format(generalGetString(MR.strings.item_status_snd_error_unexpected_desc), this.agentError) is RcvNew -> generalGetString(MR.strings.item_status_rcv_new_desc) is RcvRead -> generalGetString(MR.strings.item_status_rcv_read_desc) + is Invalid -> this.text } } @@ -2002,6 +2006,7 @@ class CIFile( is CIFileStatus.RcvCancelled -> false is CIFileStatus.RcvComplete -> true is CIFileStatus.RcvError -> false + is CIFileStatus.Invalid -> false } @Transient @@ -2022,6 +2027,7 @@ class CIFile( is CIFileStatus.RcvCancelled -> null is CIFileStatus.RcvComplete -> null is CIFileStatus.RcvError -> null + is CIFileStatus.Invalid -> null } companion object { @@ -2091,6 +2097,7 @@ sealed class CIFileStatus { @Serializable @SerialName("rcvComplete") object RcvComplete: CIFileStatus() @Serializable @SerialName("rcvCancelled") object RcvCancelled: CIFileStatus() @Serializable @SerialName("rcvError") object RcvError: CIFileStatus() + @Serializable @SerialName("invalid") class Invalid(val text: String): CIFileStatus() } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index 6207c1648e..54e6ffbd6a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -169,6 +169,7 @@ fun CIFileView( is CIFileStatus.RcvComplete -> fileIcon() is CIFileStatus.RcvCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.RcvError -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) + is CIFileStatus.Invalid -> fileIcon(innerIcon = painterResource(MR.images.ic_question_mark)) } } else { fileIcon() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt index 605a95d6d8..73fc3f41ac 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt @@ -76,6 +76,7 @@ fun CIImageView( is CIFileStatus.RcvTransfer -> progressIndicator() is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + is CIFileStatus.Invalid -> fileIcon(painterResource(MR.images.ic_question_mark), MR.strings.icon_descr_file) else -> {} } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt index 95bd6557a6..5d2d581b1d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt @@ -287,6 +287,7 @@ private fun loadingIndicator(file: CIFile?) { } is CIFileStatus.RcvCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) is CIFileStatus.RcvError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file) + is CIFileStatus.Invalid -> fileIcon(painterResource(MR.images.ic_question_mark), MR.strings.icon_descr_file) else -> {} } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_question_mark.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_question_mark.svg new file mode 100644 index 0000000000..9c2e7e110b --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_question_mark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index daf59d82ac..65148e74ea 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -501,6 +501,7 @@ data CIFileStatus (d :: MsgDirection) where CIFSRcvComplete :: CIFileStatus 'MDRcv CIFSRcvCancelled :: CIFileStatus 'MDRcv CIFSRcvError :: CIFileStatus 'MDRcv + CIFSInvalid :: {text :: Text} -> CIFileStatus 'MDSnd deriving instance Eq (CIFileStatus d) @@ -519,6 +520,7 @@ ciFileEnded = \case CIFSRcvCancelled -> True CIFSRcvComplete -> True CIFSRcvError -> True + CIFSInvalid {} -> True instance ToJSON (CIFileStatus d) where toJSON = J.toJSON . jsonCIFileStatus @@ -545,6 +547,7 @@ instance MsgDirectionI d => StrEncoding (CIFileStatus d) where CIFSRcvComplete -> "rcv_complete" CIFSRcvCancelled -> "rcv_cancelled" CIFSRcvError -> "rcv_error" + CIFSInvalid {} -> "invalid" strP = (\(AFS _ st) -> checkDirection st) <$?> strP instance StrEncoding ACIFileStatus where @@ -562,7 +565,7 @@ instance StrEncoding ACIFileStatus where "rcv_complete" -> pure $ AFS SMDRcv CIFSRcvComplete "rcv_cancelled" -> pure $ AFS SMDRcv CIFSRcvCancelled "rcv_error" -> pure $ AFS SMDRcv CIFSRcvError - _ -> fail "bad file status" + text -> pure $ AFS SMDSnd (CIFSInvalid $ safeDecodeUtf8 text) where progress :: (Int64 -> Int64 -> a) -> A.Parser a progress f = f <$> num <*> num <|> pure (f 0 1) @@ -580,6 +583,7 @@ data JSONCIFileStatus | JCIFSRcvComplete | JCIFSRcvCancelled | JCIFSRcvError + | JCIFSInvalid {text :: Text} deriving (Generic) instance ToJSON JSONCIFileStatus where @@ -599,6 +603,7 @@ jsonCIFileStatus = \case CIFSRcvComplete -> JCIFSRcvComplete CIFSRcvCancelled -> JCIFSRcvCancelled CIFSRcvError -> JCIFSRcvError + CIFSInvalid text -> JCIFSInvalid text aciFileStatusJSON :: JSONCIFileStatus -> ACIFileStatus aciFileStatusJSON = \case @@ -613,6 +618,7 @@ aciFileStatusJSON = \case JCIFSRcvComplete -> AFS SMDRcv CIFSRcvComplete JCIFSRcvCancelled -> AFS SMDRcv CIFSRcvCancelled JCIFSRcvError -> AFS SMDRcv CIFSRcvError + JCIFSInvalid text -> AFS SMDSnd $ CIFSInvalid text -- to conveniently read file data from db data CIFileInfo = CIFileInfo @@ -630,6 +636,7 @@ data CIStatus (d :: MsgDirection) where CISSndError :: String -> CIStatus 'MDSnd CISRcvNew :: CIStatus 'MDRcv CISRcvRead :: CIStatus 'MDRcv + CISInvalid :: Text -> CIStatus 'MDSnd deriving instance Eq (CIStatus d) @@ -658,6 +665,7 @@ instance MsgDirectionI d => StrEncoding (CIStatus d) where CISSndError e -> "snd_error " <> encodeUtf8 (T.pack e) CISRcvNew -> "rcv_new" CISRcvRead -> "rcv_read" + CISInvalid {} -> "invalid" strP = (\(ACIStatus _ st) -> checkDirection st) <$?> strP instance StrEncoding ACIStatus where @@ -671,7 +679,7 @@ instance StrEncoding ACIStatus where "snd_error" -> ACIStatus SMDSnd . CISSndError . T.unpack . safeDecodeUtf8 <$> (A.space *> A.takeByteString) "rcv_new" -> pure $ ACIStatus SMDRcv CISRcvNew "rcv_read" -> pure $ ACIStatus SMDRcv CISRcvRead - _ -> fail "bad status" + text -> pure $ ACIStatus SMDSnd (CISInvalid $ safeDecodeUtf8 text) data JSONCIStatus = JCISSndNew @@ -681,6 +689,7 @@ data JSONCIStatus | JCISSndError {agentError :: String} | JCISRcvNew | JCISRcvRead + | JCISInvalid {text :: Text} deriving (Show, Generic) instance ToJSON JSONCIStatus where @@ -696,6 +705,7 @@ jsonCIStatus = \case CISSndError e -> JCISSndError e CISRcvNew -> JCISRcvNew CISRcvRead -> JCISRcvRead + CISInvalid text -> JCISInvalid text ciStatusNew :: forall d. MsgDirectionI d => CIStatus d ciStatusNew = case msgDirection @d of diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 03b7956274..d6e443401d 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1369,6 +1369,7 @@ viewFileTransferStatusXFTP (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId CIFSRcvComplete -> ["receiving " <> fstr <> " complete" <> maybe "" (\fp -> ", path: " <> plain fp) filePath] CIFSRcvCancelled -> ["receiving " <> fstr <> " cancelled"] CIFSRcvError -> ["receiving " <> fstr <> " error"] + CIFSInvalid text -> [fstr <> " invalid status: " <> plain text] where fstr = fileTransferStr fileId fileName viewFileTransferStatusXFTP _ = ["no file status"]