diff --git a/Linphone/Assets.xcassets/download-simple.imageset/Contents.json b/Linphone/Assets.xcassets/download-simple.imageset/Contents.json
new file mode 100644
index 000000000..cc9e90c47
--- /dev/null
+++ b/Linphone/Assets.xcassets/download-simple.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "download-simple.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Linphone/Assets.xcassets/download-simple.imageset/download-simple.svg b/Linphone/Assets.xcassets/download-simple.imageset/download-simple.svg
new file mode 100644
index 000000000..60d202b45
--- /dev/null
+++ b/Linphone/Assets.xcassets/download-simple.imageset/download-simple.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Linphone/Assets.xcassets/file-audio.imageset/Contents.json b/Linphone/Assets.xcassets/file-audio.imageset/Contents.json
new file mode 100644
index 000000000..8c55b02f2
--- /dev/null
+++ b/Linphone/Assets.xcassets/file-audio.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "file-audio.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Linphone/Assets.xcassets/file-audio.imageset/file-audio.svg b/Linphone/Assets.xcassets/file-audio.imageset/file-audio.svg
new file mode 100644
index 000000000..a2bb0c309
--- /dev/null
+++ b/Linphone/Assets.xcassets/file-audio.imageset/file-audio.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Linphone/Assets.xcassets/file-pdf.imageset/Contents.json b/Linphone/Assets.xcassets/file-pdf.imageset/Contents.json
new file mode 100644
index 000000000..f946df429
--- /dev/null
+++ b/Linphone/Assets.xcassets/file-pdf.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "file-pdf.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Linphone/Assets.xcassets/file-pdf.imageset/file-pdf.svg b/Linphone/Assets.xcassets/file-pdf.imageset/file-pdf.svg
new file mode 100644
index 000000000..63fc1ae2e
--- /dev/null
+++ b/Linphone/Assets.xcassets/file-pdf.imageset/file-pdf.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Linphone/Assets.xcassets/file-text.imageset/file-text.svg b/Linphone/Assets.xcassets/file-text.imageset/file-text.svg
index 0b256101e..05971352c 100644
--- a/Linphone/Assets.xcassets/file-text.imageset/file-text.svg
+++ b/Linphone/Assets.xcassets/file-text.imageset/file-text.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/Linphone/Assets.xcassets/file.imageset/Contents.json b/Linphone/Assets.xcassets/file.imageset/Contents.json
new file mode 100644
index 000000000..d33e50463
--- /dev/null
+++ b/Linphone/Assets.xcassets/file.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "file.svg",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Linphone/Assets.xcassets/file.imageset/file.svg b/Linphone/Assets.xcassets/file.imageset/file.svg
new file mode 100644
index 000000000..85a754433
--- /dev/null
+++ b/Linphone/Assets.xcassets/file.imageset/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift
index 2da7dab3e..3cfb8e069 100644
--- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift
+++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift
@@ -33,6 +33,7 @@ struct ChatBubbleView: View {
@State private var ticker = Ticker()
@State private var isPressed: Bool = false
@State private var timePassed: TimeInterval?
+ @State private var sliderValue: Double = 0.5
var body: some View {
HStack {
@@ -394,6 +395,47 @@ struct ChatBubbleView: View {
}
.clipShape(RoundedRectangle(cornerRadius: 4))
.clipped()
+ } else if eventLogMessage.message.attachments.first!.type == .voiceRecording {
+ CustomSlider(
+ value: $sliderValue,
+ range: 0...1,
+ thumbColor: .blue,
+ trackColor: .gray,
+ trackHeight: 8,
+ cornerRadius: 10
+ )
+ .padding()
+ } else {
+ HStack {
+ VStack {
+ Image(getImageOfType(type: eventLogMessage.message.attachments.first!.type))
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(Color.grayMain2c700)
+ .frame(width: 60, height: 60, alignment: .leading)
+ }
+ .frame(width: 100, height: 100)
+ .background(Color.grayMain2c200)
+
+ VStack {
+ Text(eventLogMessage.message.attachments.first!.name)
+ .foregroundStyle(Color.grayMain2c700)
+ .default_text_style_600(styleSize: 16)
+ .truncationMode(.middle)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .lineLimit(1)
+
+ Text("2,2 Mo")
+ .default_text_style_300(styleSize: 16)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .lineLimit(1)
+ }
+ .padding(.horizontal, 10)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .frame(width: geometryProxy.size.width - 110, height: 100)
+ .background(.white)
+ .clipShape(RoundedRectangle(cornerRadius: 10))
}
} else if eventLogMessage.message.attachments.count > 1 {
let isGroup = conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup
@@ -472,6 +514,20 @@ struct ChatBubbleView: View {
}
return (100, 100)
}
+
+ func getImageOfType(type: AttachmentType) -> String {
+ if type == .audio {
+ return "file-audio"
+ } else if type == .pdf {
+ return "file-pdf"
+ } else if type == .text {
+ return "file-text"
+ } else if type == .fileTransfer {
+ return "download-simple"
+ } else {
+ return "file"
+ }
+ }
}
enum URLType {
diff --git a/Linphone/UI/Main/Conversations/Model/Attachment.swift b/Linphone/UI/Main/Conversations/Model/Attachment.swift
index 0e84668f1..6b57fd927 100644
--- a/Linphone/UI/Main/Conversations/Model/Attachment.swift
+++ b/Linphone/UI/Main/Conversations/Model/Attachment.swift
@@ -21,15 +21,35 @@ import Foundation
public enum AttachmentType: String, Codable {
case image
- case video
case gif
+ case video
+ case audio
+ case voiceRecording
+ case pdf
+ case text
+ case fileTransfer
+ case other
public var title: String {
switch self {
case .image:
return "Image"
- default:
+ case .gif:
+ return "GIF"
+ case .video:
return "Video"
+ case .audio:
+ return "Audio"
+ case .voiceRecording:
+ return "Voice Recording"
+ case .pdf:
+ return "PDF"
+ case .text:
+ return "Text"
+ case .fileTransfer:
+ return "File Transfer"
+ default:
+ return "Other"
}
}
@@ -37,8 +57,22 @@ public enum AttachmentType: String, Codable {
switch mediaType {
case .image:
self = .image
- default:
+ case .gif:
+ self = .gif
+ case .video:
self = .video
+ case .audio:
+ self = .audio
+ case .voiceRecording:
+ self = .voiceRecording
+ case .pdf:
+ self = .pdf
+ case .text:
+ self = .text
+ case .fileTransfer:
+ self = .fileTransfer
+ default:
+ self = .other
}
}
}
diff --git a/Linphone/UI/Main/Conversations/Model/Message.swift b/Linphone/UI/Main/Conversations/Model/Message.swift
index 4e53d314b..94644eaae 100644
--- a/Linphone/UI/Main/Conversations/Model/Message.swift
+++ b/Linphone/UI/Main/Conversations/Model/Message.swift
@@ -125,7 +125,7 @@ public struct Message: Identifiable, Hashable {
guard let thumbnailURL = await media.getThumbnailURL() else {
return nil
}
-
+
switch media.type {
case .image:
return Attachment(id: UUID().uuidString, name: "", url: thumbnailURL, type: .image)
@@ -134,6 +134,10 @@ public struct Message: Identifiable, Hashable {
return nil
}
return Attachment(id: UUID().uuidString, name: "", thumbnail: thumbnailURL, full: fullURL, type: .video)
+ case .audio:
+ return Attachment(id: UUID().uuidString, name: "", url: thumbnailURL, type: .audio)
+ default:
+ return Attachment(id: UUID().uuidString, name: "", url: thumbnailURL, type: .other)
}
}
@@ -272,7 +276,14 @@ public struct DraftMessage {
public enum MediaType {
case image
+ case gif
case video
+ case audio
+ case voiceRecording
+ case pdf
+ case text
+ case fileTransfer
+ case other
}
public struct Media: Identifiable, Equatable {
diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift
index 6d1adbedb..8c17d611b 100644
--- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift
+++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift
@@ -288,7 +288,7 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
name: content.name!,
url: path!,
- type: .image
+ type: .fileTransfer
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
@@ -296,6 +296,20 @@ class ConversationViewModel: ObservableObject {
} else {
if content.type != "video" {
let path = URL(string: self.getNewFilePath(name: content.name ?? ""))
+ var typeTmp: AttachmentType = .other
+
+ switch content.type {
+ case "image":
+ typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
+ case "audio":
+ typeTmp = content.isVoiceRecording ? .voiceRecording : .audio
+ case "application":
+ typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other
+ case "text":
+ typeTmp = .text
+ default:
+ typeTmp = .other
+ }
if path != nil {
let attachment =
@@ -303,7 +317,7 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
name: content.name!,
url: path!,
- type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
+ type: typeTmp
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
@@ -489,7 +503,7 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
name: content.name!,
url: path!,
- type: .image
+ type: .fileTransfer
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
@@ -497,6 +511,20 @@ class ConversationViewModel: ObservableObject {
} else {
if content.type != "video" {
let path = URL(string: self.getNewFilePath(name: content.name ?? ""))
+ var typeTmp: AttachmentType = .other
+
+ switch content.type {
+ case "image":
+ typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
+ case "audio":
+ typeTmp = content.isVoiceRecording ? .voiceRecording : .audio
+ case "application":
+ typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other
+ case "text":
+ typeTmp = .text
+ default:
+ typeTmp = .other
+ }
if path != nil {
let attachment =
@@ -504,7 +532,7 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
name: content.name!,
url: path!,
- type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
+ type: typeTmp
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
@@ -687,7 +715,7 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
name: content.name ?? "???",
url: path!,
- type: .image
+ type: .fileTransfer
)
attachmentNameList += ", \(content.name ?? "???")"
attachmentList.append(attachment)
@@ -695,6 +723,20 @@ class ConversationViewModel: ObservableObject {
} else if content.name != nil && !content.name!.isEmpty {
if content.type != "video" {
let path = URL(string: self.getNewFilePath(name: content.name ?? ""))
+ var typeTmp: AttachmentType = .other
+
+ switch content.type {
+ case "image":
+ typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
+ case "audio":
+ typeTmp = content.isVoiceRecording ? .voiceRecording : .audio
+ case "application":
+ typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other
+ case "text":
+ typeTmp = .text
+ default:
+ typeTmp = .other
+ }
if path != nil {
let attachment =
@@ -702,7 +744,7 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
name: content.name!,
url: path!,
- type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
+ type: typeTmp
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
@@ -958,7 +1000,7 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
name: content.name!,
url: path!,
- type: .image
+ type: .fileTransfer
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
@@ -966,6 +1008,20 @@ class ConversationViewModel: ObservableObject {
} else {
if content.type != "video" {
let path = URL(string: self.getNewFilePath(name: content.name ?? ""))
+ var typeTmp: AttachmentType = .other
+
+ switch content.type {
+ case "image":
+ typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
+ case "audio":
+ typeTmp = content.isVoiceRecording ? .voiceRecording : .audio
+ case "application":
+ typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other
+ case "text":
+ typeTmp = .text
+ default:
+ typeTmp = .other
+ }
if path != nil {
let attachment =
@@ -973,7 +1029,7 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
name: content.name!,
url: path!,
- type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
+ type: typeTmp
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
@@ -1566,6 +1622,48 @@ class ConversationViewModel: ObservableObject {
}
}
}
+
+struct CustomSlider: View {
+ @Binding var value: Double
+ var range: ClosedRange
+ var thumbColor: Color
+ var trackColor: Color
+ var trackHeight: CGFloat
+ var cornerRadius: CGFloat
+
+ var body: some View {
+ VStack {
+ ZStack {
+ // Slider track with rounded corners
+ Rectangle()
+ .fill(trackColor)
+ .frame(height: trackHeight)
+ .cornerRadius(cornerRadius)
+
+ // Progress track to show the current value
+ Rectangle()
+ .fill(thumbColor.opacity(0.5))
+ .frame(width: CGFloat((value - range.lowerBound) / (range.upperBound - range.lowerBound)) * UIScreen.main.bounds.width, height: trackHeight)
+ .cornerRadius(cornerRadius)
+
+ // Thumb (handle) with rounded appearance
+ Circle()
+ .fill(thumbColor)
+ .frame(width: 30, height: 30)
+ .offset(x: CGFloat((value - range.lowerBound) / (range.upperBound - range.lowerBound)) * UIScreen.main.bounds.width - 20)
+ .gesture(DragGesture(minimumDistance: 0)
+ .onChanged { gesture in
+ let sliderWidth = UIScreen.main.bounds.width
+ let dragX = gesture.location.x
+ let newValue = range.lowerBound + Double(dragX / sliderWidth) * (range.upperBound - range.lowerBound)
+ value = min(max(newValue, range.lowerBound), range.upperBound)
+ }
+ )
+ }
+ }
+ .padding(.horizontal, 20)
+ }
+}
// swiftlint:enable line_length
// swiftlint:enable type_body_length
// swiftlint:enable cyclomatic_complexity