Video messages on timeline (#304)

* Create timeline item and view for video

* Create video timeline items in factory, do not bubble them

* Add changelog

* Update packages

* Revert DTCoreText update
This commit is contained in:
ismailgulek
2022-11-10 13:45:35 +03:00
committed by GitHub
parent 6c6c188f73
commit 9f12b34a98
13 changed files with 285 additions and 24 deletions
@@ -20,12 +20,7 @@ import Foundation
public extension AnalyticsEvent.UserProperties {
// Initializer for Element. Strips all Web properties.
init(ftueUseCaseSelection: FtueUseCaseSelection?, numFavouriteRooms: Int?, numSpaces: Int?, allChatsActiveFilter: AllChatsActiveFilter?) {
self.init(WebMetaSpaceFavouritesEnabled: nil,
WebMetaSpaceHomeAllRooms: nil,
WebMetaSpaceHomeEnabled: nil,
WebMetaSpaceOrphansEnabled: nil,
WebMetaSpacePeopleEnabled: nil,
allChatsActiveFilter: nil,
self.init(allChatsActiveFilter: nil,
ftueUseCaseSelection: ftueUseCaseSelection,
numFavouriteRooms: numFavouriteRooms,
numSpaces: numSpaces)
@@ -98,7 +98,9 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
switch timelineItem {
case let item as ImageRoomTimelineItem:
await loadImageForTimelineItem(item)
await loadImageForImageTimelineItem(item)
case let item as VideoRoomTimelineItem:
await loadImageForVideoTimelineItem(item)
default:
break
}
@@ -235,7 +237,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
return .middle
}
private func loadImageForTimelineItem(_ timelineItem: ImageRoomTimelineItem) async {
private func loadImageForImageTimelineItem(_ timelineItem: ImageRoomTimelineItem) async {
if timelineItem.image != nil {
return
}
@@ -258,6 +260,30 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
break
}
}
private func loadImageForVideoTimelineItem(_ timelineItem: VideoRoomTimelineItem) async {
if timelineItem.image != nil {
return
}
guard let source = timelineItem.thumbnailSource else {
return
}
switch await mediaProvider.loadImageFromSource(source) {
case .success(let image):
guard let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
var item = timelineItems[index] as? ImageRoomTimelineItem else {
return
}
item.image = image
timelineItems[index] = item
callbacks.send(.updatedTimelineItem(timelineItem.id))
case .failure:
break
}
}
private func loadUserAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) async {
if timelineItem.shouldShowSenderDetails == false {
@@ -109,3 +109,35 @@ extension MessageTimelineItem where Content == MatrixRustSDK.ImageMessageContent
content.info?.blurhash
}
}
extension MatrixRustSDK.VideoMessageContent: MessageContentProtocol { }
/// A timeline item that represents an `m.room.message` event with a `msgtype` of `m.image`.
extension MessageTimelineItem where Content == MatrixRustSDK.VideoMessageContent {
var source: MediaSource {
MediaSource(source: content.source)
}
var thumbnailSource: MediaSource? {
guard let src = content.info?.thumbnailSource else {
return nil
}
return MediaSource(source: src)
}
var duration: UInt64 {
content.info?.duration ?? 0
}
var width: CGFloat? {
content.info?.width.map(CGFloat.init)
}
var height: CGFloat? {
content.info?.height.map(CGFloat.init)
}
var blurhash: String? {
content.info?.blurhash
}
}
@@ -0,0 +1,42 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import UIKit
struct VideoRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equatable {
let id: String
let text: String
let timestamp: String
let inGroupState: TimelineItemInGroupState
let isOutgoing: Bool
let senderId: String
var senderDisplayName: String?
var senderAvatar: UIImage?
let duration: UInt64
let source: MediaSource?
let thumbnailSource: MediaSource?
var image: UIImage?
var width: CGFloat?
var height: CGFloat?
var aspectRatio: CGFloat?
var blurhash: String?
var properties = RoomTimelineItemProperties()
}
@@ -59,6 +59,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
case .image(content: let content):
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
return buildImageTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
case .video(let content):
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
return buildVideoTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
case .notice(content: let content):
let message = MessageTimelineItem(item: eventItemProxy.item, content: content)
return buildNoticeTimelineItemFromMessage(message, isOutgoing, inGroupState, displayName, avatarImage)
@@ -187,6 +190,37 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
properties: RoomTimelineItemProperties(isEdited: message.isEdited,
reactions: aggregateReactions(message.reactions)))
}
private func buildVideoTimelineItemFromMessage(_ message: MessageTimelineItem<VideoMessageContent>,
_ isOutgoing: Bool,
_ inGroupState: TimelineItemInGroupState,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
var aspectRatio: CGFloat?
if let width = message.width,
let height = message.height {
aspectRatio = width / height
}
return VideoRoomTimelineItem(id: message.id,
text: message.body,
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
inGroupState: inGroupState,
isOutgoing: isOutgoing,
senderId: message.sender,
senderDisplayName: displayName,
senderAvatar: avatarImage,
duration: message.duration,
source: message.source,
thumbnailSource: message.thumbnailSource,
image: mediaProvider.imageFromSource(message.thumbnailSource),
width: message.width,
height: message.height,
aspectRatio: aspectRatio,
blurhash: message.blurhash,
properties: RoomTimelineItemProperties(isEdited: message.isEdited,
reactions: aggregateReactions(message.reactions)))
}
private func buildNoticeTimelineItemFromMessage(_ message: MessageTimelineItem<NoticeMessageContent>,
_ isOutgoing: Bool,
@@ -23,6 +23,8 @@ struct RoomTimelineViewFactory: RoomTimelineViewFactoryProtocol {
return .text(item)
case let item as ImageRoomTimelineItem:
return .image(item)
case let item as VideoRoomTimelineItem:
return .video(item)
case let item as SeparatorRoomTimelineItem:
return .separator(item)
case let item as NoticeRoomTimelineItem:
@@ -21,6 +21,7 @@ enum RoomTimelineViewProvider: Identifiable, Equatable {
case text(TextRoomTimelineItem)
case separator(SeparatorRoomTimelineItem)
case image(ImageRoomTimelineItem)
case video(VideoRoomTimelineItem)
case emote(EmoteRoomTimelineItem)
case notice(NoticeRoomTimelineItem)
case redacted(RedactedRoomTimelineItem)
@@ -34,6 +35,8 @@ enum RoomTimelineViewProvider: Identifiable, Equatable {
return item.id
case .image(let item):
return item.id
case .video(let item):
return item.id
case .emote(let item):
return item.id
case .notice(let item):
@@ -55,6 +58,8 @@ extension RoomTimelineViewProvider: View {
SeparatorRoomTimelineView(timelineItem: item)
case .image(let item):
ImageRoomTimelineView(timelineItem: item)
case .video(let item):
VideoRoomTimelineView(timelineItem: item)
case .emote(let item):
EmoteRoomTimelineView(timelineItem: item)
case .notice(let item):