core: send badge ZK-proofs to XFTP servers, add file expiry time (#7430)

* core: send badge ZK-proofs to XFTP servers

* update simplexmq, use keys and header from simplexmq

* core: add file expiry time

* refactor

* remove posix module

* rename migrations

* bot api

* update UI

* import

* show item from loaded information

* ios: update core library

* text

* send entitlements in handshake

* simplexmq

* query plans

* update simplexmq

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
EvgenyandEvgeny @ SimpleX Chat authored and GitHub committed 2026-09-01 23:16:15 +01:00
1 parent f71b132536
commit f99c474ca6
43 files changed
+328 -133

No files matched your search

+2 -2
View File
@@ -495,9 +495,9 @@ func loadChat(chatId: ChatId, im: ItemsModel, contentTag: MsgContentTag? = nil,
)
}
func apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64) async throws -> ChatItemInfo {
func apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64) async throws -> (AChatItem, ChatItemInfo) {
let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, scope: scope, itemId: itemId))
if case let .chatItemInfo(_, _, chatItemInfo) = r { return chatItemInfo }
if case let .chatItemInfo(_, aci, chatItemInfo) = r { return (aci, chatItemInfo) }
throw r.unexpected
}
@@ -126,7 +126,7 @@ struct CIFileView: View {
}
case let .rcvError(rcvFileError):
logger.debug("CIFileView fileAction - in .rcvError")
showFileErrorAlert(rcvFileError)
showFileErrorAlert(rcvFileError, file)
case let .rcvWarning(rcvFileError):
logger.debug("CIFileView fileAction - in .rcvWarning")
showFileErrorAlert(rcvFileError, temporary: true)
@@ -171,10 +171,12 @@ struct CIFileView: View {
case .sndError: fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
case .sndWarning: fileIcon("doc.fill", innerIcon: "exclamationmark.triangle.fill", innerIconSize: 10)
case .rcvInvitation:
if fileSizeValid(file, senderProfile) {
fileIcon("arrow.down.doc.fill", color: theme.colors.primary)
} else {
if !fileSizeValid(file, senderProfile) {
fileIcon("doc.fill", color: .orange, innerIcon: "exclamationmark", innerIconSize: 12)
} else if file.expired {
fileIcon("doc.fill", innerIcon: "xmark", innerIconSize: 10)
} else {
fileIcon("arrow.down.doc.fill", color: theme.colors.primary)
}
case .rcvAccepted: fileIcon("doc.fill", innerIcon: "ellipsis", innerIconSize: 12)
case let .rcvTransfer(rcvProgress, rcvTotal):
@@ -264,7 +266,14 @@ func saveCryptoFile(_ fileSource: CryptoFile) {
}
}
func showFileErrorAlert(_ err: FileError, temporary: Bool = false) {
func showFileErrorAlert(_ err: FileError, _ file: CIFile? = nil, temporary: Bool = false) {
if let file, file.expired, let fileExpires = file.fileExpires, err == .auth || err == .noFile {
showAlert(
NSLocalizedString("File expired", comment: "file error alert title"),
message: String.localizedStringWithFormat(NSLocalizedString("File was available until %@.", comment: "file error text"), localTimestamp(fileExpires))
)
return
}
let title: String = if temporary {
NSLocalizedString("Temporary file error", comment: "file error alert title")
} else {
@@ -83,7 +83,7 @@ struct CIImageView: View {
case .rcvComplete: () // ?
case .rcvCancelled: () // TODO
case let .rcvError(rcvFileError):
showFileErrorAlert(rcvFileError)
showFileErrorAlert(rcvFileError, file)
case let .rcvWarning(rcvFileError):
showFileErrorAlert(rcvFileError, temporary: true)
case let .sndError(sndFileError):
@@ -152,7 +152,7 @@ struct CIImageView: View {
case .sndCancelled: fileIcon("xmark", 10, 13)
case .sndError: fileIcon("xmark", 10, 13)
case .sndWarning: fileIcon("exclamationmark.triangle.fill", 10, 13)
case .rcvInvitation: fileIcon("arrow.down", 10, 13)
case .rcvInvitation: fileIcon(file.expired && fileSizeValid(file, senderProfile) ? "xmark" : "arrow.down", 10, 13)
case .rcvAccepted: fileIcon("ellipsis", 14, 11)
case .rcvTransfer: progressView()
case .rcvAborted: fileIcon("exclamationmark.arrow.circlepath", 14, 11)
@@ -368,7 +368,7 @@ struct CIVideoView: View {
.simultaneousGesture(TapGesture().onEnded {
showFileErrorAlert(sndFileError, temporary: true)
})
case .rcvInvitation: fileIcon("arrow.down", 10, 13)
case .rcvInvitation: fileIcon(file.expired && fileSizeValid(file, senderProfile) ? "xmark" : "arrow.down", 10, 13)
case .rcvAccepted: fileIcon("ellipsis", 14, 11)
case let .rcvTransfer(rcvProgress, rcvTotal):
if file.fileProtocol == .xftp && rcvProgress < rcvTotal {
@@ -382,7 +382,7 @@ struct CIVideoView: View {
case let .rcvError(rcvFileError):
fileIcon("xmark", 10, 13)
.simultaneousGesture(TapGesture().onEnded {
showFileErrorAlert(rcvFileError)
showFileErrorAlert(rcvFileError, file)
})
case let .rcvWarning(rcvFileError):
fileIcon("exclamationmark.triangle.fill", 10, 13)
@@ -178,7 +178,7 @@ struct VoiceMessagePlayer: View {
.simultaneousGesture(TapGesture().onEnded {
showFileErrorAlert(sndFileError, temporary: true)
})
case .rcvInvitation: downloadButton(recordingFile, "play.fill")
case .rcvInvitation: downloadButton(recordingFile, recordingFile.expired ? "multiply" : "play.fill")
case .rcvAccepted: loadingIcon()
case .rcvTransfer: loadingIcon()
case .rcvAborted: downloadButton(recordingFile, "exclamationmark.arrow.circlepath")
@@ -187,7 +187,7 @@ struct VoiceMessagePlayer: View {
case let .rcvError(rcvFileError):
fileStatusIcon("multiply", 14)
.simultaneousGesture(TapGesture().onEnded {
showFileErrorAlert(rcvFileError)
showFileErrorAlert(rcvFileError, recordingFile)
})
case let .rcvWarning(rcvFileError):
fileStatusIcon("exclamationmark.triangle.fill", 16)
@@ -162,6 +162,9 @@ struct ChatItemInfoView: View {
if let deleteAt = meta.itemTimed?.deleteAt {
infoRow("Disappears at", localTimestamp(deleteAt))
}
if let file = ci.file, let fileExpires = file.fileExpires {
infoRow(file.expired ? "File was available until" : "File available until", localTimestamp(fileExpires))
}
if meta.msgVerified?.verified == true {
let signedText: LocalizedStringKey = ci.chatDir.sent ? "Signed" : "Signed & verified"
HStack {
+5 -2
View File
@@ -1719,6 +1719,7 @@ struct ChatView: View {
@State private var showArchivingReports = false
@State private var showChatItemInfoSheet: Bool = false
@State private var chatItemInfo: ChatItemInfo?
@State private var chatItemInfoItem: ChatItem?
@State private var msgWidth: CGFloat = 0
@State private var touchInProgress: Bool = false
@@ -2230,8 +2231,9 @@ struct ChatView: View {
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
.sheet(isPresented: $showChatItemInfoSheet, onDismiss: {
chatItemInfo = nil
chatItemInfoItem = nil
}) {
ChatItemInfoView(ci: ci, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, chatItemInfo: $chatItemInfo)
ChatItemInfoView(ci: chatItemInfoItem ?? ci, userMemberId: chat.chatInfo.groupInfo?.membership.memberId, chatItemInfo: $chatItemInfo)
}
}
@@ -2574,8 +2576,9 @@ struct ChatView: View {
Task {
do {
let cInfo = chat.chatInfo
let ciInfo = try await apiGetChatItemInfo(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemId: ci.id)
let (aci, ciInfo) = try await apiGetChatItemInfo(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemId: ci.id)
await MainActor.run {
chatItemInfoItem = aci.chatItem
chatItemInfo = ciInfo
}
if case let .group(gInfo, _) = chat.chatInfo {
+8 -8
View File
@@ -183,8 +183,8 @@
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD-ghc9.6.3.a */; };
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD.a */; };
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs-ghc9.6.3.a */; };
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs.a */; };
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
@@ -563,8 +563,8 @@
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD-ghc9.6.3.a"; sourceTree = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD.a"; sourceTree = "<group>"; };
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs-ghc9.6.3.a"; sourceTree = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs.a"; sourceTree = "<group>"; };
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
@@ -735,8 +735,8 @@
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD-ghc9.6.3.a in Frameworks */,
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD.a in Frameworks */,
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs-ghc9.6.3.a in Frameworks */,
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs.a in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -822,8 +822,8 @@
64C829992D54AEEE006B9E89 /* libffi.a */,
64C829982D54AEED006B9E89 /* libgmp.a */,
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD-ghc9.6.3.a */,
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-9pyEF8uuax6HMofQg4cpqD.a */,
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs-ghc9.6.3.a */,
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-7.1.0.3-LojM8yKwVXIArAtVCk2ZRs.a */,
);
path = Libraries;
sourceTree = "<group>";
+6 -1
View File
@@ -4685,6 +4685,7 @@ public struct CIFile: Decodable, Hashable {
public var fileSource: CryptoFile?
public var fileStatus: CIFileStatus
public var fileProtocol: FileProtocol
public var fileExpires: Date? = nil
public static func getSample(fileId: Int64 = 1, fileName: String = "test.txt", fileSize: Int64 = 100, filePath: String? = "test.txt", fileStatus: CIFileStatus = .rcvComplete) -> CIFile {
let f: CryptoFile?
@@ -4718,6 +4719,10 @@ public struct CIFile: Decodable, Hashable {
}
}
public var expired: Bool {
if let fileExpires { fileExpires < Date.now } else { false }
}
public var cancelAction: CancelAction? {
get {
switch self.fileStatus {
@@ -4754,7 +4759,7 @@ public struct CIFile: Decodable, Hashable {
case .sndCancelled: true
case .sndError: true
case .sndWarning: true
case .rcvInvitation: false
case .rcvInvitation: expired
case .rcvAccepted: true
case .rcvTransfer: true
case .rcvAborted: true
@@ -4252,8 +4252,11 @@ data class CIFile(
val fileSize: Long,
val fileSource: CryptoFile? = null,
val fileStatus: CIFileStatus,
val fileProtocol: FileProtocol
val fileProtocol: FileProtocol,
val fileExpires: Instant? = null
) {
val expired: Boolean = fileExpires != null && fileExpires < Clock.System.now()
val loaded: Boolean = when (fileStatus) {
is CIFileStatus.SndStored -> true
is CIFileStatus.SndTransfer -> true
@@ -4303,7 +4306,7 @@ data class CIFile(
is CIFileStatus.SndCancelled -> true
is CIFileStatus.SndError -> true
is CIFileStatus.SndWarning -> true
is CIFileStatus.RcvInvitation -> false
is CIFileStatus.RcvInvitation -> expired
is CIFileStatus.RcvAccepted -> true
is CIFileStatus.RcvTransfer -> true
is CIFileStatus.RcvAborted -> true
@@ -1161,9 +1161,9 @@ object ChatController {
return null
}
suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long): ChatItemInfo? {
suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long): Pair<ChatItem, ChatItemInfo>? {
val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, scope, itemId))
if (r is API.Result && r.res is CR.ApiChatItemInfo) return r.res.chatItemInfo
if (r is API.Result && r.res is CR.ApiChatItemInfo) return r.res.chatItem.chatItem to r.res.chatItemInfo
apiErrorAlert("apiGetChatItemInfo", generalGetString(MR.strings.error_loading_details), r)
return null
}
@@ -273,6 +273,11 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
if (deleteAt != null) {
InfoRow(stringResource(MR.strings.info_row_disappears_at), localTimestamp(deleteAt))
}
val file = ci.file
if (file?.fileExpires != null) {
val expiresRes = if (file.expired) MR.strings.info_row_file_expired else MR.strings.info_row_file_expires
InfoRow(stringResource(expiresRes), localTimestamp(file.fileExpires))
}
if (ci.meta.msgVerified?.verified == true) {
val signedRes = if (sent) MR.strings.info_row_signed else MR.strings.info_row_signed_verified
InfoRow(stringResource(signedRes), "", icon = painterResource(MR.images.ic_verified))
@@ -699,7 +699,7 @@ fun ChatView(
}
},
showItemDetails = { cInfo, cItem ->
suspend fun loadChatItemInfo(): ChatItemInfo? = coroutineScope {
suspend fun loadChatItemInfo(): Pair<ChatItem, ChatItemInfo>? = coroutineScope {
val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cInfo.groupChatScope(), cItem.id)
if (ciInfo != null) {
if (chatInfo is ChatInfo.Group) {
@@ -717,11 +717,12 @@ fun ChatView(
}
ModalManager.end.showModalCloseable(endButtons = {
ShareButton {
clipboard.shareText(itemInfoShareText(chatModel, cItem, initialCiInfo, chatModel.controller.appPrefs.developerTools.get()))
clipboard.shareText(itemInfoShareText(chatModel, initialCiInfo.first, initialCiInfo.second, chatModel.controller.appPrefs.developerTools.get()))
}
}) { close ->
var ciInfo by remember(cItem.id) { mutableStateOf(initialCiInfo) }
ChatItemInfoView(chatRh, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get(), chatInfo)
val (item, info) = ciInfo
ChatItemInfoView(chatRh, item, info, devTools = chatModel.controller.appPrefs.developerTools.get(), chatInfo)
LaunchedEffect(cItem.id) {
withContext(Dispatchers.Default) {
for (msg in controller.messagesChannel) {
@@ -101,7 +101,7 @@ fun CIFileView(
FileProtocol.LOCAL -> {}
}
file.fileStatus is CIFileStatus.RcvError ->
showFileErrorAlert(file.fileStatus.rcvFileError)
showFileErrorAlert(file.fileStatus.rcvFileError, file)
file.fileStatus is CIFileStatus.RcvWarning ->
showFileErrorAlert(file.fileStatus.rcvFileError, temporary = true)
file.fileStatus is CIFileStatus.SndError ->
@@ -157,10 +157,12 @@ fun CIFileView(
is CIFileStatus.SndError -> fileIcon(innerIcon = painterResource(MR.images.ic_close))
is CIFileStatus.SndWarning -> fileIcon(innerIcon = painterResource(MR.images.ic_warning_filled))
is CIFileStatus.RcvInvitation ->
if (fileSizeValid(file, senderProfile))
fileIcon(innerIcon = painterResource(MR.images.ic_arrow_downward), color = MaterialTheme.colors.primary, topPadding = 10.sp.toDp())
else
if (!fileSizeValid(file, senderProfile))
fileIcon(innerIcon = painterResource(MR.images.ic_priority_high), color = WarningOrange)
else if (file.expired)
fileIcon(innerIcon = painterResource(MR.images.ic_close))
else
fileIcon(innerIcon = painterResource(MR.images.ic_arrow_downward), color = MaterialTheme.colors.primary, topPadding = 10.sp.toDp())
is CIFileStatus.RcvAccepted -> fileIcon(innerIcon = painterResource(MR.images.ic_more_horiz))
is CIFileStatus.RcvTransfer ->
if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) {
@@ -241,7 +243,15 @@ fun CIFileView(
fun fileSizeValid(file: CIFile, senderProfile: LocalProfile?): Boolean =
file.fileSize <= getMaxFileSize(file.fileProtocol, senderProfile)
fun showFileErrorAlert(err: FileError, temporary: Boolean = false) {
fun showFileErrorAlert(err: FileError, file: CIFile? = null, temporary: Boolean = false) {
val fileExpires = file?.fileExpires
if (file != null && fileExpires != null && file.expired && (err is FileError.Auth || err is FileError.NoFile)) {
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.file_expired),
String.format(generalGetString(MR.strings.file_error_expired), localTimestamp(fileExpires))
)
return
}
val title: String = generalGetString(if (temporary) MR.strings.temporary_file_error else MR.strings.file_error)
val btn = err.moreInfoButton
if (btn != null) {
@@ -83,7 +83,11 @@ fun CIImageView(
is CIFileStatus.SndCancelled -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
is CIFileStatus.SndError -> fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
is CIFileStatus.SndWarning -> fileIcon(painterResource(MR.images.ic_warning_filled), MR.strings.icon_descr_file)
is CIFileStatus.RcvInvitation -> fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_asked_to_receive)
is CIFileStatus.RcvInvitation ->
if (file.expired && fileSizeValid(file, senderProfile))
fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
else
fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_asked_to_receive)
is CIFileStatus.RcvAccepted -> fileIcon(painterResource(MR.images.ic_more_horiz), MR.strings.icon_descr_waiting_for_image)
is CIFileStatus.RcvTransfer -> progressIndicator()
is CIFileStatus.RcvComplete -> {}
@@ -239,7 +243,7 @@ fun CIImageView(
FileProtocol.LOCAL -> {}
}
file.fileStatus is CIFileStatus.RcvError ->
showFileErrorAlert(file.fileStatus.rcvFileError)
showFileErrorAlert(file.fileStatus.rcvFileError, file)
file.fileStatus is CIFileStatus.RcvWarning ->
showFileErrorAlert(file.fileStatus.rcvFileError, temporary = true)
file.fileStatus is CIFileStatus.SndError ->
@@ -135,10 +135,10 @@ fun CIVideoView(
}
// Do not show download icon when the view is blurred
if (!smallView && (!showDownloadButton(file?.fileStatus) || !blurred.value)) {
fileStatusIcon(file, false)
fileStatusIcon(file, false, senderProfile)
} else if (smallView && file?.showStatusIconInSmallView == true) {
Box(Modifier.align(Alignment.Center)) {
fileStatusIcon(file, true)
fileStatusIcon(file, true, senderProfile)
}
}
}
@@ -486,7 +486,7 @@ private fun progressCircle(progress: Long, total: Long) {
}
@Composable
private fun fileStatusIcon(file: CIFile?, smallView: Boolean) {
private fun fileStatusIcon(file: CIFile?, smallView: Boolean, senderProfile: LocalProfile?) {
if (file != null) {
Box(
Modifier
@@ -525,7 +525,11 @@ private fun fileStatusIcon(file: CIFile?, smallView: Boolean) {
showFileErrorAlert(file.fileStatus.sndFileError, temporary = true)
}
)
is CIFileStatus.RcvInvitation -> fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_video_asked_to_receive)
is CIFileStatus.RcvInvitation ->
if (file.expired && fileSizeValid(file, senderProfile))
fileIcon(painterResource(MR.images.ic_close), MR.strings.icon_descr_file)
else
fileIcon(painterResource(MR.images.ic_arrow_downward), MR.strings.icon_descr_video_asked_to_receive)
is CIFileStatus.RcvAccepted -> fileIcon(painterResource(MR.images.ic_more_horiz), MR.strings.icon_descr_waiting_for_video)
is CIFileStatus.RcvTransfer ->
if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) {
@@ -541,7 +545,7 @@ private fun fileStatusIcon(file: CIFile?, smallView: Boolean) {
painterResource(MR.images.ic_close),
MR.strings.icon_descr_file,
onClick = {
showFileErrorAlert(file.fileStatus.rcvFileError)
showFileErrorAlert(file.fileStatus.rcvFileError, file)
}
)
is CIFileStatus.RcvWarning ->
@@ -412,7 +412,7 @@ private fun VoiceMsgIndicator(
}
)
file?.fileStatus is CIFileStatus.RcvInvitation ->
PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, true, error, sizeMultiplier, { receiveFile(file.fileId) }, {}, longClick = longClick)
PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, true, error, sizeMultiplier, { receiveFile(file.fileId) }, {}, longClick = longClick, icon = if (file.expired) MR.images.ic_close else MR.images.ic_play_arrow_filled)
file?.fileStatus is CIFileStatus.RcvTransfer || file?.fileStatus is CIFileStatus.RcvAccepted ->
VoiceMsgLoadingProgressIndicator(sizeMultiplier)
file?.fileStatus is CIFileStatus.RcvAborted ->
@@ -424,7 +424,7 @@ private fun VoiceMsgIndicator(
sizeMultiplier,
longClick,
onClick = {
showFileErrorAlert(file.fileStatus.rcvFileError)
showFileErrorAlert(file.fileStatus.rcvFileError, file)
}
)
file != null && file.fileStatus is CIFileStatus.RcvWarning ->
@@ -697,6 +697,8 @@
<string name="loading_remote_file_title">Loading the file </string>
<string name="loading_remote_file_desc">Please, wait while the file is being loaded from the linked mobile</string>
<string name="file_error">File error</string>
<string name="file_expired">File expired</string>
<string name="file_error_expired">File was available until %1$s.</string>
<string name="temporary_file_error">Temporary file error</string>
<string name="open_with_app">Open with %s</string>
@@ -2044,6 +2046,8 @@
<string name="info_row_deleted_at">Deleted at</string>
<string name="info_row_moderated_at">Moderated at</string>
<string name="info_row_disappears_at">Disappears at</string>
<string name="info_row_file_expires">File available until</string>
<string name="info_row_file_expired">File was available until</string>
<string name="share_text_database_id">Database ID: %d</string>
<string name="share_text_updated_at">Record updated at: %s</string>
<string name="share_text_message_status">Message status: %s</string>