mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-17 02:58:07 +00:00
Added file picker, updated unsecured chatroom icon, and replaced floating button with a scroll-down icon in the chatroom
This commit is contained in:
parent
7d6b2d8e0b
commit
4f1fcbbcf6
23 changed files with 568 additions and 180 deletions
|
|
@ -7,7 +7,7 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4ED1F0A881A9ACB5977A8987 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
|
||||
4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
|
||||
660AAF7F2B839272004C0FA6 /* msgNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
660D8A712B517D260092694D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 660D8A702B517D260092694D /* GoogleService-Info.plist */; };
|
||||
6613A0AE2BAEB7DF008923A4 /* MeetingFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0AD2BAEB7DF008923A4 /* MeetingFragment.swift */; };
|
||||
|
|
@ -56,6 +56,7 @@
|
|||
C6A5A9482C10B6A30070FEA4 /* AuthState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A5A9472C10B6A30070FEA4 /* AuthState.swift */; };
|
||||
C6DC4E3D2C199C4E009096FD /* BundleExtenion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DC4E3C2C199C4E009096FD /* BundleExtenion.swift */; };
|
||||
C6DC4E3F2C19C289009096FD /* SideMenuEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DC4E3E2C19C289009096FD /* SideMenuEntry.swift */; };
|
||||
D703F7082DC8C605005B8F75 /* FilePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D703F7072DC8C5FF005B8F75 /* FilePicker.swift */; };
|
||||
D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */; };
|
||||
D70959F12B8DF3EC0014AC0B /* ConversationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70959F02B8DF3EC0014AC0B /* ConversationModel.swift */; };
|
||||
D70A26EE2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A26ED2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift */; };
|
||||
|
|
@ -262,6 +263,7 @@
|
|||
C6A5A9472C10B6A30070FEA4 /* AuthState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthState.swift; sourceTree = "<group>"; };
|
||||
C6DC4E3C2C199C4E009096FD /* BundleExtenion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtenion.swift; sourceTree = "<group>"; };
|
||||
C6DC4E3E2C19C289009096FD /* SideMenuEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuEntry.swift; sourceTree = "<group>"; };
|
||||
D703F7072DC8C5FF005B8F75 /* FilePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePicker.swift; sourceTree = "<group>"; };
|
||||
D706BA812ADD72D100278F45 /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = "<group>"; };
|
||||
D70959F02B8DF3EC0014AC0B /* ConversationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationModel.swift; sourceTree = "<group>"; };
|
||||
D70A26ED2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationsListBottomSheet.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -413,7 +415,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4ED1F0A881A9ACB5977A8987 /* (null) in Frameworks */,
|
||||
4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -542,6 +544,7 @@
|
|||
D717071C2AC591EF0037746F /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D703F7072DC8C5FF005B8F75 /* FilePicker.swift */,
|
||||
D717A10D2CEB770D00849D92 /* ShareSheetController.swift */,
|
||||
66C491F72B24D25A00CEA16D /* Extensions */,
|
||||
66C491FC2B24D36500CEA16D /* AudioRouteUtils.swift */,
|
||||
|
|
@ -1346,6 +1349,7 @@
|
|||
D74DA0122C047F0700A8561D /* HistoryModel.swift in Sources */,
|
||||
66D382052CEB7E0A0063E1C5 /* ShortcutModel.swift in Sources */,
|
||||
D72250692ADFBF2D008FB426 /* SideMenu.swift in Sources */,
|
||||
D703F7082DC8C605005B8F75 /* FilePicker.swift in Sources */,
|
||||
C6DC4E3F2C19C289009096FD /* SideMenuEntry.swift in Sources */,
|
||||
D714DE622C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift in Sources */,
|
||||
66C468FB2D2BE54800A836F7 /* PIPViewModel.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#4e6074" viewBox="0 0 256 256"><path d="M208,56H180.28L166.65,35.56A8,8,0,0,0,160,32H96a8,8,0,0,0-6.65,3.56L75.71,56H48A24,24,0,0,0,24,80V192a24,24,0,0,0,24,24H208a24,24,0,0,0,24-24V80A24,24,0,0,0,208,56Zm8,136a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V80a8,8,0,0,1,8-8H80a8,8,0,0,0,6.66-3.56L100.28,48h55.43l13.63,20.44A8,8,0,0,0,176,72h32a8,8,0,0,1,8,8ZM128,88a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,88Zm0,72a28,28,0,1,1,28-28A28,28,0,0,1,128,160Z"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M208,56H180.28L166.65,35.56A8,8,0,0,0,160,32H96a8,8,0,0,0-6.65,3.56L75.71,56H48A24,24,0,0,0,24,80V192a24,24,0,0,0,24,24H208a24,24,0,0,0,24-24V80A24,24,0,0,0,208,56Zm8,136a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V80a8,8,0,0,1,8-8H80a8,8,0,0,0,6.66-3.56L100.28,48h55.43l13.63,20.44A8,8,0,0,0,176,72h32a8,8,0,0,1,8,8ZM128,88a44,44,0,1,0,44,44A44.05,44.05,0,0,0,128,88Zm0,72a28,28,0,1,1,28-28A28,28,0,0,1,128,160Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 523 B |
21
Linphone/Assets.xcassets/caret-double-down.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/caret-double-down.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "caret-double-down.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/caret-double-down.imageset/caret-double-down.svg
vendored
Normal file
1
Linphone/Assets.xcassets/caret-double-down.imageset/caret-double-down.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M213.66,130.34a8,8,0,0,1,0,11.32l-80,80a8,8,0,0,1-11.32,0l-80-80a8,8,0,0,1,11.32-11.32L128,204.69l74.34-74.35A8,8,0,0,1,213.66,130.34Zm-91.32,11.32a8,8,0,0,0,11.32,0l80-80a8,8,0,0,0-11.32-11.32L128,124.69,53.66,50.34A8,8,0,0,0,42.34,61.66Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 363 B |
21
Linphone/Assets.xcassets/image.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/image.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "image.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/image.imageset/image.svg
vendored
Normal file
1
Linphone/Assets.xcassets/image.imageset/image.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V158.75l-26.07-26.06a16,16,0,0,0-22.63,0l-20,20-44-44a16,16,0,0,0-22.62,0L40,149.37V56ZM40,172l52-52,80,80H40Zm176,28H194.63l-36-36,20-20L216,181.38V200ZM144,100a12,12,0,1,1,12,12A12,12,0,0,1,144,100Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 424 B |
21
Linphone/Assets.xcassets/lock-simple-open-bold.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/lock-simple-open-bold.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lock-simple-open-bold.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/lock-simple-open-bold.imageset/lock-simple-open-bold.svg
vendored
Normal file
1
Linphone/Assets.xcassets/lock-simple-open-bold.imageset/lock-simple-open-bold.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M208,76H100V56a28,28,0,0,1,28-28c13.51,0,25.65,9.62,28.24,22.39a12,12,0,1,0,23.52-4.78C174.87,21.5,153.1,4,128,4A52.06,52.06,0,0,0,76,56V76H48A20,20,0,0,0,28,96V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V96A20,20,0,0,0,208,76Zm-4,128H52V100H204Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 370 B |
21
Linphone/Assets.xcassets/lock-simple-open.imageset/Contents.json
vendored
Normal file
21
Linphone/Assets.xcassets/lock-simple-open.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lock-simple-open.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
1
Linphone/Assets.xcassets/lock-simple-open.imageset/lock-simple-open.svg
vendored
Normal file
1
Linphone/Assets.xcassets/lock-simple-open.imageset/lock-simple-open.svg
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#000000" viewBox="0 0 256 256"><path d="M208,80H96V56a32,32,0,0,1,32-32c15.37,0,29.2,11,32.16,25.59a8,8,0,0,0,15.68-3.18C171.32,24.15,151.2,8,128,8A48.05,48.05,0,0,0,80,56V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80Zm0,128H48V96H208V208Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 367 B |
|
|
@ -46,6 +46,7 @@ final class CoreContext: ObservableObject {
|
|||
var mCore: Core!
|
||||
|
||||
var bearerAuthInfoPendingPasswordUpdate: AuthInfo?
|
||||
var imdnToEverybodyThreshold: Bool = true
|
||||
|
||||
let monitor = NWPathMonitor()
|
||||
var networkStatusIsConnected: Bool = true // updated on core queue
|
||||
|
|
@ -154,7 +155,7 @@ final class CoreContext: ObservableObject {
|
|||
self.mCore.config!.setString(section: "misc", key: "version_check_url_root", value: "https://download.linphone.org/releases")
|
||||
|
||||
self.mCore.imdnToEverybodyThreshold = 1
|
||||
|
||||
self.imdnToEverybodyThreshold = self.mCore.imdnToEverybodyThreshold == 1
|
||||
//self.copyDatabaseFileToDocumentsDirectory()
|
||||
|
||||
let shortcutsCount = self.mCore.config!.getInt(section: "ui", key: "shortcut_count", defaultValue: 0)
|
||||
|
|
|
|||
|
|
@ -340,7 +340,7 @@ struct ChatBubbleView: View {
|
|||
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
|
||||
.frame(width: 10, height: 10)
|
||||
.padding(.top, 1)
|
||||
} else if eventLogMessage.message.status != nil {
|
||||
} else if eventLogMessage.message.status != nil && !(CoreContext.shared.imdnToEverybodyThreshold && !eventLogMessage.message.isOutgoing) {
|
||||
Image(conversationViewModel.getImageIMDN(status: eventLogMessage.message.status!))
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
|
|
@ -374,8 +374,10 @@ struct ChatBubbleView: View {
|
|||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
conversationViewModel.selectedMessageToDisplayDetails = eventLogMessage
|
||||
conversationViewModel.prepareBottomSheetForDeliveryStatus()
|
||||
if !(CoreContext.shared.imdnToEverybodyThreshold && !eventLogMessage.message.isOutgoing) {
|
||||
conversationViewModel.selectedMessageToDisplayDetails = eventLogMessage
|
||||
conversationViewModel.prepareBottomSheetForDeliveryStatus()
|
||||
}
|
||||
}
|
||||
.disabled(conversationViewModel.selectedMessage != nil)
|
||||
.padding(.top, -4)
|
||||
|
|
@ -542,7 +544,6 @@ struct ChatBubbleView: View {
|
|||
onImageTapped: {
|
||||
selectedURLAttachment = eventLogMessage.message.attachments.first!.full
|
||||
})
|
||||
|
||||
.overlay(
|
||||
Group {
|
||||
if eventLogMessage.message.attachments.first!.type == .video {
|
||||
|
|
@ -645,10 +646,24 @@ struct ChatBubbleView: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(eventLogMessage.message.attachments.first!.size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
if eventLogMessage.message.attachments.first!.size > 0 {
|
||||
Text(eventLogMessage.message.attachments.first!.size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
if let size = self.getFileSize(atPath: eventLogMessage.message.attachments.first!.full.path) {
|
||||
Text(size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
Text(eventLogMessage.message.attachments.first!.size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
|
@ -762,10 +777,24 @@ struct ChatBubbleView: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(attachment.size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
if attachment.size > 0 {
|
||||
Text(attachment.size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
if let size = self.getFileSize(atPath: attachment.full.path) {
|
||||
Text(size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
} else {
|
||||
Text(attachment.size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
|
@ -830,6 +859,18 @@ struct ChatBubbleView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getFileSize(atPath path: String) -> Int? {
|
||||
do {
|
||||
let attributes = try FileManager.default.attributesOfItem(atPath: path)
|
||||
if let fileSize = attributes[.size] as? Int {
|
||||
return fileSize
|
||||
}
|
||||
} catch {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct DynamicLinkText: View {
|
||||
|
|
|
|||
|
|
@ -55,8 +55,11 @@ struct ConversationFragment: View {
|
|||
|
||||
@State private var displayFloatingButton = false
|
||||
|
||||
@State private var areFilePickersOpen = false
|
||||
|
||||
@State private var isShowPhotoLibrary = false
|
||||
@State private var isShowCamera = false
|
||||
@State private var isShowFilePicker = false
|
||||
|
||||
@State private var mediasIsLoading = false
|
||||
@State private var voiceRecordingInProgress = false
|
||||
|
|
@ -117,6 +120,23 @@ struct ConversationFragment: View {
|
|||
}
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
})
|
||||
.sheet(isPresented: $isShowFilePicker, onDismiss: {
|
||||
isShowFilePicker = false
|
||||
}, content: {
|
||||
FilePicker(onDocumentsPicked: { urlList in
|
||||
FilePicker.convertToAttachmentArray(fromResults: urlList) { mediasOrNil, errorOrNil in
|
||||
if let error = errorOrNil {
|
||||
print(error)
|
||||
}
|
||||
|
||||
if let medias = mediasOrNil {
|
||||
conversationViewModel.mediasToSend.append(contentsOf: medias)
|
||||
}
|
||||
self.mediasIsLoading = false
|
||||
}
|
||||
})
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
})
|
||||
.fullScreenCover(isPresented: $isShowCamera) {
|
||||
ImagePicker(conversationViewModel: conversationViewModel, selectedMedia: self.$conversationViewModel.mediasToSend)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
|
|
@ -421,7 +441,7 @@ struct ConversationFragment: View {
|
|||
} label: {
|
||||
ZStack {
|
||||
|
||||
Image("caret-down")
|
||||
Image("caret-double-down")
|
||||
.renderingMode(.template)
|
||||
.foregroundStyle(.white)
|
||||
.padding()
|
||||
|
|
@ -555,23 +575,40 @@ struct ConversationFragment: View {
|
|||
.fill(Color(.white))
|
||||
.frame(width: 100, height: 100)
|
||||
|
||||
AsyncImage(url: attachment.thumbnail) { image in
|
||||
ZStack {
|
||||
image
|
||||
.resizable()
|
||||
.interpolation(.medium)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
||||
if attachment.type == .video {
|
||||
Image("play-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 40, height: 40, alignment: .leading)
|
||||
VStack {
|
||||
if attachment.type == .image || attachment.type == .gif || attachment.type == .video {
|
||||
AsyncImage(url: attachment.thumbnail) { image in
|
||||
ZStack {
|
||||
image
|
||||
.resizable()
|
||||
.interpolation(.medium)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
||||
if attachment.type == .video {
|
||||
Image("play-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 40, height: 40, alignment: .leading)
|
||||
}
|
||||
}
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
Spacer()
|
||||
Text(attachment.name)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.default_text_style_800(styleSize: 14)
|
||||
.truncationMode(.middle)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineLimit(2)
|
||||
Spacer()
|
||||
}
|
||||
.background(Color.grayMain2c200)
|
||||
}
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
.layoutPriority(-1)
|
||||
.onTapGesture {
|
||||
|
|
@ -620,46 +657,94 @@ struct ConversationFragment: View {
|
|||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
|
||||
if areFilePickersOpen {
|
||||
ZStack(alignment: .top) {
|
||||
HStack {
|
||||
Button {
|
||||
self.areFilePickersOpen.toggle()
|
||||
self.isShowCamera = true
|
||||
} label: {
|
||||
VStack {
|
||||
Image("camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
|
||||
Text("conversation_take_picture_label")
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.default_text_style_300(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading)
|
||||
|
||||
Button {
|
||||
self.areFilePickersOpen.toggle()
|
||||
self.isShowPhotoLibrary = true
|
||||
self.mediasIsLoading = true
|
||||
} label: {
|
||||
VStack {
|
||||
Image("image")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
|
||||
Text("conversation_pick_file_from_gallery_label")
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.default_text_style_300(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading)
|
||||
|
||||
Button {
|
||||
self.areFilePickersOpen.toggle()
|
||||
self.isShowFilePicker = true
|
||||
self.mediasIsLoading = true
|
||||
} label: {
|
||||
VStack {
|
||||
Image("file")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.frame(width: 25, height: 25, alignment: .leading)
|
||||
|
||||
Text("conversation_pick_any_file_label")
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.default_text_style_300(styleSize: 15)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.all, 20)
|
||||
.background(Color.gray100)
|
||||
}
|
||||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
if !voiceRecordingInProgress {
|
||||
Button {
|
||||
withAnimation {
|
||||
areFilePickersOpen.toggle()
|
||||
}
|
||||
} label: {
|
||||
Image("smiley")
|
||||
Image(areFilePickersOpen ? "x" : "paperclip")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.frame(width: 28, height: 28, alignment: .leading)
|
||||
.padding(.all, 6)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.padding(.horizontal, isMessageTextFocused ? 0 : 2)
|
||||
|
||||
Button {
|
||||
self.isShowPhotoLibrary = true
|
||||
self.mediasIsLoading = true
|
||||
} label: {
|
||||
Image("paperclip")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.frame(width: isMessageTextFocused ? 0 : 28, height: isMessageTextFocused ? 0 : 28, alignment: .leading)
|
||||
.padding(.all, isMessageTextFocused ? 0 : 6)
|
||||
.padding(.top, 4)
|
||||
.disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading)
|
||||
}
|
||||
.padding(.horizontal, isMessageTextFocused ? 0 : 2)
|
||||
|
||||
Button {
|
||||
self.isShowCamera = true
|
||||
} label: {
|
||||
Image("camera")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500)
|
||||
.frame(width: isMessageTextFocused ? 0 : 28, height: isMessageTextFocused ? 0 : 28, alignment: .leading)
|
||||
.padding(.all, isMessageTextFocused ? 0 : 6)
|
||||
.padding(.top, 4)
|
||||
.disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading)
|
||||
.animation(.none, value: areFilePickersOpen)
|
||||
}
|
||||
.padding(.horizontal, isMessageTextFocused ? 0 : 2)
|
||||
|
||||
|
|
|
|||
|
|
@ -126,14 +126,6 @@ struct ConversationRow: View {
|
|||
Spacer()
|
||||
|
||||
HStack {
|
||||
if !conversation.encryptionEnabled {
|
||||
Image("warning-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.redDanger500)
|
||||
.frame(width: 18, height: 18, alignment: .trailing)
|
||||
}
|
||||
|
||||
Text(conversationsListViewModel.getCallTime(startDate: conversation.lastUpdateTime))
|
||||
.foregroundStyle(Color.grayMain2c400)
|
||||
.default_text_style(styleSize: 14)
|
||||
|
|
@ -151,6 +143,14 @@ struct ConversationRow: View {
|
|||
.frame(width: 18, height: 18, alignment: .trailing)
|
||||
}
|
||||
|
||||
if !conversation.encryptionEnabled {
|
||||
Image("lock-simple-open-bold")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.orangeWarning600)
|
||||
.frame(width: 18, height: 18, alignment: .trailing)
|
||||
}
|
||||
|
||||
if conversation.isMuted {
|
||||
Image("bell-slash")
|
||||
.renderingMode(.template)
|
||||
|
|
|
|||
|
|
@ -49,8 +49,19 @@ class FloatingButton: UIButton {
|
|||
|
||||
private func setupButton() {
|
||||
// Set the button's appearance
|
||||
self.setImage(UIImage(named: "caret-down")?.withRenderingMode(.alwaysTemplate), for: .normal)
|
||||
self.tintColor = .white
|
||||
if let originalImage = UIImage(named: "caret-double-down")?.withRenderingMode(.alwaysTemplate) {
|
||||
let newSize = CGSize(width: originalImage.size.width / 1.5, height: originalImage.size.height / 1.5)
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0)
|
||||
originalImage.draw(in: CGRect(origin: .zero, size: newSize))
|
||||
var resizedImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
resizedImage = resizedImage?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
self.setImage(resizedImage, for: .normal)
|
||||
self.tintColor = .white
|
||||
}
|
||||
self.backgroundColor = UIColor(Color.orangeMain500)
|
||||
self.layer.cornerRadius = 30
|
||||
self.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
private var contactsManager = ContactsManager.shared
|
||||
|
||||
var chatRoom: ChatRoom
|
||||
var lastMessage: ChatMessage?
|
||||
|
||||
let isDisabledBecauseNotSecured: Bool = false
|
||||
|
||||
static let TAG = "[Conversation Model]"
|
||||
|
|
@ -51,6 +53,7 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
@Published var avatarModel: ContactAvatarModel
|
||||
|
||||
private var conferenceDelegate: ConferenceDelegate?
|
||||
private var chatMessageDelegate: ChatMessageDelegate?
|
||||
|
||||
init(chatRoom: ChatRoom) {
|
||||
self.chatRoom = chatRoom
|
||||
|
|
@ -77,6 +80,8 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
|
||||
self.encryptionEnabled = chatRoom.currentParams != nil && chatRoom.currentParams!.encryptionEnabled
|
||||
|
||||
self.lastMessage = nil
|
||||
|
||||
self.lastMessageText = ""
|
||||
|
||||
self.lastMessageIsOutgoing = false
|
||||
|
|
@ -182,9 +187,37 @@ class ConversationModel: ObservableObject, Identifiable {
|
|||
}
|
||||
}
|
||||
|
||||
func chatMessageAddDelegate() {
|
||||
if self.lastMessage != nil && self.chatMessageDelegate != nil {
|
||||
self.lastMessage!.removeDelegate(delegate: self.chatMessageDelegate!)
|
||||
}
|
||||
|
||||
self.chatMessageDelegate = ChatMessageDelegateStub(onMsgStateChanged: { (message: ChatMessage, msgState: ChatMessage.State) in
|
||||
let lastMessageStateTmp = msgState.rawValue
|
||||
DispatchQueue.main.async {
|
||||
self.lastMessageState = lastMessageStateTmp
|
||||
}
|
||||
})
|
||||
|
||||
if self.lastMessage != nil && self.chatMessageDelegate != nil {
|
||||
self.lastMessage!.addDelegate(delegate: self.chatMessageDelegate!)
|
||||
}
|
||||
}
|
||||
|
||||
func chatMessageRemoveDelegate() {
|
||||
if self.lastMessage != nil && chatMessageDelegate != nil {
|
||||
self.lastMessage!.removeDelegate(delegate: chatMessageDelegate!)
|
||||
}
|
||||
}
|
||||
|
||||
func getContentTextMessage(chatRoom: ChatRoom) {
|
||||
let lastMessage = chatRoom.lastMessageInHistory
|
||||
if lastMessage != nil {
|
||||
if !(CoreContext.shared.imdnToEverybodyThreshold && !lastMessage!.isOutgoing) {
|
||||
self.lastMessage = lastMessage
|
||||
self.chatMessageAddDelegate()
|
||||
}
|
||||
|
||||
var fromAddressFriend = lastMessage!.fromAddress != nil
|
||||
? self.contactsManager.getFriendWithAddress(address: lastMessage!.fromAddress)?.name ?? nil
|
||||
: nil
|
||||
|
|
|
|||
|
|
@ -452,23 +452,19 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func getHistorySize() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
if self.displayedConversation != nil {
|
||||
let historySize = self.displayedConversation!.chatRoom.historyEventsSize
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversationHistorySize = historySize
|
||||
}
|
||||
if self.displayedConversation != nil {
|
||||
let historySize = self.displayedConversation!.chatRoom.historyEventsSize
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversationHistorySize = historySize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getUnreadMessagesCount() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
if self.displayedConversation != nil {
|
||||
let unreadMessagesCount = self.displayedConversation!.chatRoom.unreadMessagesCount
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversationUnreadMessagesCount = unreadMessagesCount
|
||||
}
|
||||
if self.displayedConversation != nil {
|
||||
let unreadMessagesCount = self.displayedConversation!.chatRoom.unreadMessagesCount
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversationUnreadMessagesCount = unreadMessagesCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -490,44 +486,42 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func getParticipantConversationModel() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
if self.displayedConversation != nil {
|
||||
DispatchQueue.main.async {
|
||||
self.isUserAdmin = false
|
||||
self.participantConversationModelAdmin.removeAll()
|
||||
self.participantConversationModel.removeAll()
|
||||
}
|
||||
self.displayedConversation!.chatRoom.participants.forEach { participant in
|
||||
if participant.address != nil {
|
||||
ContactAvatarModel.getAvatarModelFromAddress(address: participant.address!) { avatarResult in
|
||||
let avatarModelTmp = avatarResult
|
||||
if participant.isAdmin {
|
||||
DispatchQueue.main.async {
|
||||
self.participantConversationModelAdmin.append(avatarModelTmp)
|
||||
self.participantConversationModel.append(avatarModelTmp)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.participantConversationModel.append(avatarModelTmp)
|
||||
}
|
||||
if self.displayedConversation != nil {
|
||||
DispatchQueue.main.async {
|
||||
self.isUserAdmin = false
|
||||
self.participantConversationModelAdmin.removeAll()
|
||||
self.participantConversationModel.removeAll()
|
||||
}
|
||||
self.displayedConversation!.chatRoom.participants.forEach { participant in
|
||||
if participant.address != nil {
|
||||
ContactAvatarModel.getAvatarModelFromAddress(address: participant.address!) { avatarResult in
|
||||
let avatarModelTmp = avatarResult
|
||||
if participant.isAdmin {
|
||||
DispatchQueue.main.async {
|
||||
self.participantConversationModelAdmin.append(avatarModelTmp)
|
||||
self.participantConversationModel.append(avatarModelTmp)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
self.participantConversationModel.append(avatarModelTmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.displayedConversation!.isReadOnly {
|
||||
if let currentUser = self.displayedConversation?.chatRoom.me,
|
||||
let address = currentUser.address {
|
||||
ContactAvatarModel.getAvatarModelFromAddress(address: address) { avatarResult in
|
||||
let avatarModelTmp = avatarResult
|
||||
DispatchQueue.main.async {
|
||||
if currentUser.isAdmin {
|
||||
self.isUserAdmin = true
|
||||
self.participantConversationModelAdmin.append(avatarModelTmp)
|
||||
}
|
||||
self.participantConversationModel.append(avatarModelTmp)
|
||||
self.myParticipantConversationModel = avatarModelTmp
|
||||
}
|
||||
|
||||
if !self.displayedConversation!.isReadOnly {
|
||||
if let currentUser = self.displayedConversation?.chatRoom.me,
|
||||
let address = currentUser.address {
|
||||
ContactAvatarModel.getAvatarModelFromAddress(address: address) { avatarResult in
|
||||
let avatarModelTmp = avatarResult
|
||||
DispatchQueue.main.async {
|
||||
if currentUser.isAdmin {
|
||||
self.isUserAdmin = true
|
||||
self.participantConversationModelAdmin.append(avatarModelTmp)
|
||||
}
|
||||
self.participantConversationModel.append(avatarModelTmp)
|
||||
self.myParticipantConversationModel = avatarModelTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -549,12 +543,6 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func getMessages() {
|
||||
self.getHistorySize()
|
||||
self.getUnreadMessagesCount()
|
||||
self.getParticipantConversationModel()
|
||||
self.computeComposingLabel()
|
||||
self.getEphemeralTime()
|
||||
|
||||
self.mediasToSend.removeAll()
|
||||
self.messageToReply = nil
|
||||
|
||||
|
|
@ -563,6 +551,12 @@ class ConversationViewModel: ObservableObject {
|
|||
self.attachments.removeAll()
|
||||
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
self.getHistorySize()
|
||||
self.getUnreadMessagesCount()
|
||||
self.getParticipantConversationModel()
|
||||
self.computeComposingLabel()
|
||||
self.getEphemeralTime()
|
||||
|
||||
if self.displayedConversation != nil {
|
||||
let historyEvents = self.displayedConversation!.chatRoom.getHistoryRangeEvents(begin: 0, end: 30)
|
||||
|
||||
|
|
@ -1930,18 +1924,14 @@ class ConversationViewModel: ObservableObject {
|
|||
switch attachment.type {
|
||||
case .image:
|
||||
content.type = "image"
|
||||
/*
|
||||
case .audio:
|
||||
content.type = "audio"
|
||||
*/
|
||||
case .audio:
|
||||
content.type = "audio"
|
||||
case .video:
|
||||
content.type = "video"
|
||||
/*
|
||||
case .pdf:
|
||||
content.type = "application"
|
||||
case .plainText:
|
||||
content.type = "text"
|
||||
*/
|
||||
case .pdf:
|
||||
content.type = "application"
|
||||
case .text:
|
||||
content.type = "text"
|
||||
default:
|
||||
content.type = "file"
|
||||
}
|
||||
|
|
@ -1953,25 +1943,21 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
if message != nil {
|
||||
|
||||
let path = FileManager.default.temporaryDirectory.appendingPathComponent((attachment.full.lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""))
|
||||
let newPath = URL(string: FileUtil.sharedContainerUrl().appendingPathComponent("Library/Images").absoluteString
|
||||
+ (attachment.full.lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""))
|
||||
/*
|
||||
let data = try Data(contentsOf: path)
|
||||
let decodedData: () = try data.write(to: path)
|
||||
*/
|
||||
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: newPath!.path) {
|
||||
try FileManager.default.removeItem(atPath: newPath!.path)
|
||||
let path = FileManager.default.temporaryDirectory.appendingPathComponent(attachment.full.lastPathComponent)
|
||||
if let newPath = URL(string: FileUtil.sharedContainerUrl().appendingPathComponent("Library/Images").absoluteString
|
||||
+ (attachment.full.lastPathComponent)) {
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: newPath.path) {
|
||||
try FileManager.default.removeItem(atPath: newPath.path)
|
||||
}
|
||||
try FileManager.default.moveItem(atPath: path.path, toPath: newPath.path)
|
||||
|
||||
content.filePath = newPath.path
|
||||
|
||||
message!.addFileContent(content: content)
|
||||
} catch {
|
||||
Log.error(error.localizedDescription)
|
||||
}
|
||||
try FileManager.default.moveItem(atPath: path.path, toPath: newPath!.path)
|
||||
|
||||
let filePathTmp = newPath?.absoluteString
|
||||
content.filePath = String(filePathTmp!.dropFirst(7))
|
||||
message!.addFileContent(content: content)
|
||||
} catch {
|
||||
Log.error(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -2027,10 +2013,9 @@ class ConversationViewModel: ObservableObject {
|
|||
if let newChatRoom = core.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: conversationModel.chatRoom.peerAddress, participants: nil) {
|
||||
if LinphoneUtils.getChatRoomId(room: newChatRoom) == conversationModel.id {
|
||||
self.addConversationDelegate(chatRoom: newChatRoom)
|
||||
let conversation = ConversationModel(chatRoom: newChatRoom)
|
||||
DispatchQueue.main.async {
|
||||
withAnimation {
|
||||
self.displayedConversation = conversation
|
||||
self.displayedConversation = conversationModel
|
||||
}
|
||||
self.getMessages()
|
||||
}
|
||||
|
|
@ -2596,31 +2581,29 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func getEphemeralTime() {
|
||||
coreContext.doOnCoreQueue { _ in
|
||||
if self.displayedConversation != nil {
|
||||
|
||||
let lifetime = self.displayedConversation!.chatRoom.ephemeralLifetime
|
||||
DispatchQueue.main.async {
|
||||
switch lifetime {
|
||||
case 60:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_one_minute", comment: "")
|
||||
case 3600:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_one_hour", comment: "")
|
||||
case 86400:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_one_day", comment: "")
|
||||
case 259200:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_three_days", comment: "")
|
||||
case 604800:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_one_week", comment: "")
|
||||
default:
|
||||
self.isEphemeral = false
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_disabled", comment: "")
|
||||
}
|
||||
if self.displayedConversation != nil {
|
||||
|
||||
let lifetime = self.displayedConversation!.chatRoom.ephemeralLifetime
|
||||
DispatchQueue.main.async {
|
||||
switch lifetime {
|
||||
case 60:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_one_minute", comment: "")
|
||||
case 3600:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_one_hour", comment: "")
|
||||
case 86400:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_one_day", comment: "")
|
||||
case 259200:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_three_days", comment: "")
|
||||
case 604800:
|
||||
self.isEphemeral = true
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_one_week", comment: "")
|
||||
default:
|
||||
self.isEphemeral = false
|
||||
self.ephemeralTime = NSLocalizedString("conversation_ephemeral_messages_duration_disabled", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,6 +198,9 @@ class ConversationsListViewModel: ObservableObject {
|
|||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
let index = self.conversationsList.firstIndex(where: { $0.id == idTmp })
|
||||
if index != nil {
|
||||
self.conversationsList[index!].chatMessageRemoveDelegate()
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if index != nil {
|
||||
self.conversationsList.remove(at: index!)
|
||||
|
|
|
|||
111
Linphone/Utils/FilePicker.swift
Normal file
111
Linphone/Utils/FilePicker.swift
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-iphone
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct FilePicker: UIViewControllerRepresentable {
|
||||
var onDocumentsPicked: ([URL]) -> Void
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(onDocumentsPicked: onDocumentsPicked)
|
||||
}
|
||||
|
||||
func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
|
||||
let picker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.item], asCopy: true)
|
||||
picker.allowsMultipleSelection = true
|
||||
picker.delegate = context.coordinator
|
||||
return picker
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {}
|
||||
|
||||
class Coordinator: NSObject, UIDocumentPickerDelegate {
|
||||
let onDocumentsPicked: ([URL]) -> Void
|
||||
|
||||
init(onDocumentsPicked: @escaping ([URL]) -> Void) {
|
||||
self.onDocumentsPicked = onDocumentsPicked
|
||||
}
|
||||
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
onDocumentsPicked(urls)
|
||||
}
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||
onDocumentsPicked([])
|
||||
}
|
||||
}
|
||||
|
||||
static func convertToAttachmentArray(fromResults results: [URL], onComplete: @escaping ([Attachment]?, Error?) -> Void) {
|
||||
var medias = [Attachment]()
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
for urlFile in results {
|
||||
dispatchGroup.enter()
|
||||
do {
|
||||
let mimeType = urlFile.mimeType()
|
||||
if !mimeType.isEmpty {
|
||||
let type = mimeType.components(separatedBy: "/").first ?? ""
|
||||
let subtype = mimeType.components(separatedBy: "/").last ?? ""
|
||||
let dataResult = try Data(contentsOf: urlFile)
|
||||
|
||||
var typeTmp: AttachmentType = .other
|
||||
switch type {
|
||||
case "image":
|
||||
typeTmp = urlFile.lastPathComponent.lowercased().hasSuffix("gif") ? .gif : .image
|
||||
case "audio":
|
||||
typeTmp = .audio
|
||||
case "application":
|
||||
typeTmp = subtype.lowercased() == "pdf" ? .pdf : .other
|
||||
case "text":
|
||||
typeTmp = .text
|
||||
case "video":
|
||||
typeTmp = .video
|
||||
default:
|
||||
typeTmp = .other
|
||||
}
|
||||
|
||||
if typeTmp == .video {
|
||||
let urlImage = PhotoPicker.saveMedia(name: urlFile.lastPathComponent, data: dataResult, type: .video)
|
||||
let urlThumbnail = PhotoPicker.getURLThumbnail(name: urlFile.lastPathComponent)
|
||||
|
||||
if urlImage != nil {
|
||||
let attachment = Attachment(id: UUID().uuidString, name: urlImage!.lastPathComponent, thumbnail: urlThumbnail, full: urlImage!, type: .video)
|
||||
medias.append(attachment)
|
||||
}
|
||||
} else {
|
||||
let urlImage = PhotoPicker.saveMedia(name: urlFile.lastPathComponent, data: dataResult, type: typeTmp)
|
||||
|
||||
if urlImage != nil {
|
||||
let attachment = Attachment(id: UUID().uuidString, name: urlImage!.lastPathComponent, url: urlImage!, type: typeTmp)
|
||||
medias.append(attachment)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
onComplete(medias, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,27 @@ class FileUtil: NSObject {
|
|||
case unknown
|
||||
}
|
||||
|
||||
public class func formUrlEncode(_ inputString: String) -> String {
|
||||
// https://www.w3.org/TR/html5/sec-forms.html#application-x-www-form-urlencoded-encoding-algorithm
|
||||
// Encode tous les caractères sauf *-._A-Za-z0-9, remplace les espaces par '+'
|
||||
|
||||
guard !inputString.isEmpty else {
|
||||
return inputString
|
||||
}
|
||||
|
||||
// Définir les caractères autorisés (non encodés)
|
||||
var allowed = CharacterSet.alphanumerics
|
||||
allowed.insert(charactersIn: "*-._")
|
||||
|
||||
// Appliquer l'encodage pour les autres caractères
|
||||
var encoded = inputString.addingPercentEncoding(withAllowedCharacters: allowed) ?? ""
|
||||
|
||||
// Remplacer les espaces (déjà encodés en %20) par '+'
|
||||
encoded = encoded.replacingOccurrences(of: "%20", with: "+")
|
||||
|
||||
return encoded
|
||||
}
|
||||
|
||||
public class func bundleFilePath(_ file: NSString) -> String? {
|
||||
return Bundle.main.path(forResource: file.deletingPathExtension, ofType: file.pathExtension)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,8 +122,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
|
|||
|
||||
static func saveMedia(name: String, data: Data, type: AttachmentType) -> URL? {
|
||||
do {
|
||||
let path = FileManager.default.temporaryDirectory.appendingPathComponent((name.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""))
|
||||
|
||||
let path = FileManager.default.temporaryDirectory.appendingPathComponent(name)
|
||||
_ = try data.write(to: path)
|
||||
|
||||
if type == .video {
|
||||
|
|
|
|||
|
|
@ -241,6 +241,10 @@
|
|||
"conversation_reply_to_message_title" = "Replying to: ";
|
||||
"conversation_text_field_hint" = "Say something…";
|
||||
"conversations_list_empty" = "No conversation for the moment…";
|
||||
"conversation_take_picture_label" = "Take picture";
|
||||
"conversation_pick_file_from_gallery_label" = "Open gallery";
|
||||
"conversation_pick_any_file_label" = "Pick file";
|
||||
"conversation_file_cant_be_opened_error_toast" = "File can't be opened!";
|
||||
"debug_logs_copied_to_clipboard_toast" = "Debug logs copied to clipboard";
|
||||
"Default" = "Default";
|
||||
"Default mode" = "Default mode";
|
||||
|
|
|
|||
|
|
@ -241,6 +241,10 @@
|
|||
"conversation_reply_to_message_title" = "En réponse à : ";
|
||||
"conversation_text_field_hint" = "Dites quelque chose…";
|
||||
"conversations_list_empty" = "Aucune conversation pour le moment…";
|
||||
"conversation_take_picture_label" = "Prendre une photo";
|
||||
"conversation_pick_file_from_gallery_label" = "Ouvrir la gallerie";
|
||||
"conversation_pick_any_file_label" = "Choisir un fichier";
|
||||
"conversation_file_cant_be_opened_error_toast" = "Impossible d'ouvrir le fichier!";
|
||||
"debug_logs_copied_to_clipboard_toast" = "Les journaux ont été ajoutés au presse-papier";
|
||||
"Default" = "Default";
|
||||
"Default mode" = "Default mode";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue