diff --git a/ElementX/Resources/Assets.xcassets/images/media/microphone.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/images/media/microphone.imageset/Contents.json
new file mode 100644
index 000000000..d69e18648
--- /dev/null
+++ b/ElementX/Resources/Assets.xcassets/images/media/microphone.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "microphone.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/ElementX/Resources/Assets.xcassets/images/media/microphone.imageset/microphone.svg b/ElementX/Resources/Assets.xcassets/images/media/microphone.imageset/microphone.svg
new file mode 100644
index 000000000..7808332fc
--- /dev/null
+++ b/ElementX/Resources/Assets.xcassets/images/media/microphone.imageset/microphone.svg
@@ -0,0 +1,5 @@
+
diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings
index 3bfd0c95a..536f943ce 100644
--- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings
+++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings
@@ -148,6 +148,7 @@
"common_verification_cancelled" = "Verification cancelled";
"common_verification_complete" = "Verification complete";
"common_video" = "Video";
+"common_voice_message" = "Voice message";
"common_waiting" = "Waiting…";
"common_poll_end_confirmation" = "Are you sure you want to end this poll?";
"common_poll_summary" = "Poll: %1$@";
diff --git a/ElementX/Sources/Generated/Assets.swift b/ElementX/Sources/Generated/Assets.swift
index fd93f899b..5ccdf4761 100644
--- a/ElementX/Sources/Generated/Assets.swift
+++ b/ElementX/Sources/Generated/Assets.swift
@@ -65,6 +65,7 @@ internal enum Asset {
internal static let locationPointer = ImageAsset(name: "images/location-pointer")
internal static let mediaPause = ImageAsset(name: "images/media-pause")
internal static let mediaPlay = ImageAsset(name: "images/media-play")
+ internal static let microphone = ImageAsset(name: "images/microphone")
internal static let addReaction = ImageAsset(name: "images/add-reaction")
internal static let copy = ImageAsset(name: "images/copy")
internal static let editOutline = ImageAsset(name: "images/edit-outline")
diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift
index 2bf203476..c568229b7 100644
--- a/ElementX/Sources/Generated/Strings.swift
+++ b/ElementX/Sources/Generated/Strings.swift
@@ -332,6 +332,8 @@ public enum L10n {
public static var commonVerificationComplete: String { return L10n.tr("Localizable", "common_verification_complete") }
/// Video
public static var commonVideo: String { return L10n.tr("Localizable", "common_video") }
+ /// Voice message
+ public static var commonVoiceMessage: String { return L10n.tr("Localizable", "common_voice_message") }
/// Waiting…
public static var commonWaiting: String { return L10n.tr("Localizable", "common_waiting") }
/// %1$@ crashed the last time it was used. Would you like to share a crash report with us?
diff --git a/ElementX/Sources/Screens/RoomScreen/View/Replies/TimelineReplyView.swift b/ElementX/Sources/Screens/RoomScreen/View/Replies/TimelineReplyView.swift
index e4e7321ee..4be9b085e 100644
--- a/ElementX/Sources/Screens/RoomScreen/View/Replies/TimelineReplyView.swift
+++ b/ElementX/Sources/Screens/RoomScreen/View/Replies/TimelineReplyView.swift
@@ -30,7 +30,7 @@ struct TimelineReplyView: View {
switch timelineItemReplyDetails {
case .loaded(let sender, let content):
switch content {
- case .audio(let content), .voice(let content):
+ case .audio(let content):
ReplyView(sender: sender,
plainBody: content.body,
formattedBody: nil,
@@ -62,6 +62,11 @@ struct TimelineReplyView: View {
plainBody: content.body,
formattedBody: nil,
icon: content.thumbnailSource.map { .init(kind: .mediaSource($0), cornerRadii: iconCornerRadii) })
+ case .voice:
+ ReplyView(sender: sender,
+ plainBody: L10n.commonVoiceMessage,
+ formattedBody: nil,
+ icon: .init(kind: .icon(Asset.Images.microphone.name), cornerRadii: iconCornerRadii))
case .location:
ReplyView(sender: sender,
plainBody: L10n.commonSharedLocation,
@@ -182,23 +187,25 @@ struct TimelineReplyView: View {
struct TimelineReplyView_Previews: PreviewProvider, TestablePreview {
static let viewModel = RoomScreenViewModel.mock
- static var previews: some View {
- VStack(alignment: .leading, spacing: 20) {
- TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .notLoaded(eventID: ""))
+ static var previewItems: [TimelineReplyView] {
+ let imageSource = MediaSourceProxy(url: "https://mock.com", mimeType: "image/png")
+
+ return [
+ TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .notLoaded(eventID: "")),
- TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loading(eventID: ""))
+ TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loading(eventID: "")),
TimelineReplyView(placement: .timeline,
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
- contentType: .text(.init(body: "This is a reply"))))
+ contentType: .text(.init(body: "This is a reply")))),
TimelineReplyView(placement: .timeline,
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
- contentType: .emote(.init(body: "says hello"))))
+ contentType: .emote(.init(body: "says hello")))),
TimelineReplyView(placement: .timeline,
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Bot"),
- contentType: .notice(.init(body: "Hello world"))))
+ contentType: .notice(.init(body: "Hello world")))),
TimelineReplyView(placement: .timeline,
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
@@ -206,32 +213,46 @@ struct TimelineReplyView_Previews: PreviewProvider, TestablePreview {
duration: 0,
waveform: nil,
source: nil,
- contentType: nil))))
+ contentType: nil)))),
TimelineReplyView(placement: .timeline,
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .file(.init(body: "Some file",
source: nil,
thumbnailSource: nil,
- contentType: nil))))
-
- let imageSource = MediaSourceProxy(url: "https://mock.com", mimeType: "image/png")
+ contentType: nil)))),
TimelineReplyView(placement: .timeline,
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .image(.init(body: "Some image",
source: imageSource,
- thumbnailSource: imageSource))))
+ thumbnailSource: imageSource)))),
TimelineReplyView(placement: .timeline,
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
contentType: .video(.init(body: "Some video",
duration: 0,
source: nil,
- thumbnailSource: imageSource))))
+ thumbnailSource: imageSource)))),
TimelineReplyView(placement: .timeline,
timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
- contentType: .location(.init(body: ""))))
+ contentType: .location(.init(body: "")))),
+
+ TimelineReplyView(placement: .timeline,
+ timelineItemReplyDetails: .loaded(sender: .init(id: "", displayName: "Alice"),
+ contentType: .voice(.init(body: "Some voice message",
+ duration: 0,
+ waveform: nil,
+ source: nil,
+ contentType: nil))))
+ ]
+ }
+
+ static var previews: some View {
+ VStack(alignment: .leading, spacing: 20) {
+ ForEach(0..