Add support for copying a caption. (#3563)

* Add timeline item action for copying a media caption.

* Fix the action colours when long pressing a timeline item.

Nice side effect: Reasonable sized buttons when button shapes are enabled.

* Re-arrange the Timeline Item Menu.

* Add timeline item menu snapshots for media items.
This commit is contained in:
Doug
2024-11-29 12:26:59 +00:00
committed by GitHub
parent fdbacbc22d
commit c8627cfd64
56 changed files with 401 additions and 199 deletions
@@ -250,28 +250,16 @@ enum RoomTimelineItemFixtures {
static var mediaChunk: [RoomTimelineItemProtocol] {
[
VideoRoomTimelineItem(id: .randomEvent,
timestamp: "10:47 am",
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: ""),
content: .init(filename: "video.mp4",
videoInfo: .mockVideo,
thumbnailInfo: .mockThumbnail,
blurhash: "KtI~70X5V?yss9oyrYs:t6")),
ImageRoomTimelineItem(id: .randomEvent,
timestamp: "10:47 am",
isOutgoing: false,
isEditable: false,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: ""),
content: .init(filename: "image.jpg",
imageInfo: .mockImage,
thumbnailInfo: nil,
blurhash: "KpE4oyayR5|GbHb];3j@of"))
AudioRoomTimelineItem(isOutgoing: false, caption: "Listen to this!"),
AudioRoomTimelineItem(isOutgoing: true),
FileRoomTimelineItem(isOutgoing: false),
FileRoomTimelineItem(isOutgoing: true, caption: "Please check this ASAP!"),
ImageRoomTimelineItem(isOutgoing: false),
ImageRoomTimelineItem(isOutgoing: true, caption: "Isn't this pretty!"),
VideoRoomTimelineItem(isOutgoing: false, caption: "Woah, it was incredible!"),
VideoRoomTimelineItem(isOutgoing: true),
VoiceMessageRoomTimelineItem(isOutgoing: false),
VoiceMessageRoomTimelineItem(isOutgoing: true)
]
}
}
@@ -294,3 +282,92 @@ private extension TextRoomTimelineItem {
return newSelf
}
}
private extension AudioRoomTimelineItem {
init(isOutgoing: Bool, caption: String? = nil) {
self.init(id: .randomEvent,
timestamp: "10:47 am",
isOutgoing: isOutgoing,
isEditable: isOutgoing,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
content: .init(filename: "audio.mp3",
caption: caption,
duration: 60,
waveform: nil,
source: .init(url: .mockMXCAudio, mimeType: nil),
fileSize: nil,
contentType: .mp3))
}
}
private extension FileRoomTimelineItem {
init(isOutgoing: Bool, caption: String? = nil) {
self.init(id: .randomEvent,
timestamp: "10:47 am",
isOutgoing: isOutgoing,
isEditable: isOutgoing,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
content: .init(filename: "file.pdf",
caption: caption,
source: .init(url: .mockMXCFile, mimeType: nil),
fileSize: nil,
thumbnailSource: nil,
contentType: .pdf))
}
}
private extension ImageRoomTimelineItem {
init(isOutgoing: Bool, caption: String? = nil) {
self.init(id: .randomEvent,
timestamp: "10:47 am",
isOutgoing: isOutgoing,
isEditable: isOutgoing,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
content: .init(filename: "image.jpg",
caption: caption,
imageInfo: .mockImage,
thumbnailInfo: nil,
blurhash: "KpE4oyayR5|GbHb];3j@of"))
}
}
private extension VideoRoomTimelineItem {
init(isOutgoing: Bool, caption: String? = nil) {
self.init(id: .randomEvent,
timestamp: "10:47 am",
isOutgoing: isOutgoing,
isEditable: isOutgoing,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
content: .init(filename: "video.mp4",
caption: caption,
videoInfo: .mockVideo,
thumbnailInfo: .mockThumbnail,
blurhash: "KtI~70X5V?yss9oyrYs:t6"))
}
}
private extension VoiceMessageRoomTimelineItem {
init(isOutgoing: Bool) {
self.init(id: .randomEvent,
timestamp: "10:47 am",
isOutgoing: isOutgoing,
isEditable: isOutgoing,
canBeRepliedTo: true,
isThreaded: false,
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
content: .init(filename: "message.ogg",
duration: 10,
waveform: .mockWaveform,
source: .init(url: .mockMXCAudio, mimeType: nil),
fileSize: nil,
contentType: .audio))
}
}
@@ -35,18 +35,22 @@ extension EventBasedMessageTimelineItemProtocol {
}
}
var hasMediaCaption: Bool {
var mediaCaption: String? {
switch contentType {
case .audio(let content):
content.caption != nil
content.caption
case .file(let content):
content.caption != nil
content.caption
case .image(let content):
content.caption != nil
content.caption
case .video(let content):
content.caption != nil
content.caption
case .emote, .notice, .text, .location, .voice:
false
nil
}
}
var hasMediaCaption: Bool {
mediaCaption != nil
}
}
@@ -99,4 +99,14 @@ extension EventBasedTimelineItemProtocol {
return true
}
}
var supportsMediaCaption: Bool {
guard let messageBasedItem = self as? EventBasedMessageTimelineItemProtocol else { return false }
return messageBasedItem.supportsMediaCaption
}
var hasMediaCaption: Bool {
guard let messageBasedItem = self as? EventBasedMessageTimelineItemProtocol else { return false }
return messageBasedItem.hasMediaCaption
}
}