Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vector-im/element-x-ios/issues/134 - Prevent home screen room last me… #138

Merged
merged 3 commits into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
}
}

func fromPlain(_ string: String?) async -> AttributedString? {
await Task.detached {
fromPlain(string)
}.value
}

func fromPlain(_ string: String?) -> AttributedString? {
guard let string = string else {
return nil
Expand All @@ -44,6 +50,12 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
return try? AttributedString(mutableAttributedString, including: \.elementX)
}

func fromHTML(_ htmlString: String?) async -> AttributedString? {
await Task.detached {
fromHTML(htmlString)
}.value
}

// Do not use the default HTML renderer of NSAttributedString because this method
// runs on the UI thread which we want to avoid because renderHTMLString is called
// most of the time from a background thread.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ struct AttributedStringBuilderComponent: Hashable {

protocol AttributedStringBuilderProtocol {
func fromPlain(_ string: String?) -> AttributedString?
func fromPlain(_ string: String?) async -> AttributedString?

func fromHTML(_ htmlString: String?) -> AttributedString?
func fromHTML(_ htmlString: String?) async -> AttributedString?

func blockquoteCoalescedComponentsFrom(_ attributedString: AttributedString?) -> [AttributedStringBuilderComponent]?
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ struct HomeScreenRoom: Identifiable, Equatable {

var displayName: String?

let topic: String?
var topic: String?
var lastMessage: String?

var avatar: UIImage?
Expand Down
54 changes: 39 additions & 15 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
private let attributedStringBuilder: AttributedStringBuilderProtocol

private var roomUpdateListeners = Set<AnyCancellable>()
private var roomsUpdateTask: Task<Void, Never>? {
willSet {
roomsUpdateTask?.cancel()
}
}

private var roomSummaries: [RoomSummaryProtocol]? {
didSet {
Expand Down Expand Up @@ -56,14 +61,29 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
}

func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol]) {
self.roomSummaries = roomSummaries
roomsUpdateTask = Task {
stefanceriu marked this conversation as resolved.
Show resolved Hide resolved
await updateWithRoomSummaries(roomSummaries)
}
}

private func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol]) async {
var rooms = [HomeScreenRoom]()
for summary in roomSummaries {
if Task.isCancelled {
return
}

rooms.append(await buildOrUpdateRoomForSummary(summary))
}

state.rooms = roomSummaries.map { roomSummary in
buildOrUpdateRoomFromSummary(roomSummary)
if Task.isCancelled {
return
}

roomUpdateListeners.removeAll()
state.rooms = rooms
self.roomSummaries = roomSummaries

roomUpdateListeners.removeAll()
roomSummaries.forEach { roomSummary in
roomSummary.callbacks
.receive(on: DispatchQueue.main)
Expand All @@ -72,10 +92,16 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
return
}

switch callback {
case .updatedData:
Task {
if let index = self.state.rooms.firstIndex(where: { $0.id == roomSummary.id }) {
self.state.rooms[index] = self.buildOrUpdateRoomFromSummary(roomSummary)
switch callback {
case .updatedLastMessage:
var room = self.state.rooms[index]
room.lastMessage = await self.lastMessageFromEventBrief(roomSummary.lastMessage)
self.state.rooms[index] = room
default:
self.state.rooms[index] = await self.buildOrUpdateRoomForSummary(roomSummary)
}
}
}
}
Expand Down Expand Up @@ -110,14 +136,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
Task { await roomSummary.loadDetails() }
}

private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom {
let lastMessage = lastMessageFromEventBrief(roomSummary.lastMessage)

private func buildOrUpdateRoomForSummary(_ roomSummary: RoomSummaryProtocol) async -> HomeScreenRoom {
guard var room = state.rooms.first(where: { $0.id == roomSummary.id }) else {
return HomeScreenRoom(id: roomSummary.id,
displayName: roomSummary.displayName,
topic: roomSummary.topic,
lastMessage: lastMessage,
lastMessage: await lastMessageFromEventBrief(roomSummary.lastMessage),
avatar: roomSummary.avatar,
isDirect: roomSummary.isDirect,
isEncrypted: roomSummary.isEncrypted,
Expand All @@ -127,20 +151,20 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol

room.avatar = roomSummary.avatar
room.displayName = roomSummary.displayName
room.lastMessage = lastMessage

room.topic = roomSummary.topic
return room
}

private func lastMessageFromEventBrief(_ eventBrief: EventBrief?) -> String? {
private func lastMessageFromEventBrief(_ eventBrief: EventBrief?) async -> String? {
guard let eventBrief = eventBrief else {
return nil
}

let senderDisplayName = senderDisplayNameForBrief(eventBrief)

if let htmlBody = eventBrief.htmlBody,
let lastMessageAttributedString = attributedStringBuilder.fromHTML(htmlBody) {
let lastMessageAttributedString = await attributedStringBuilder.fromHTML(htmlBody) {
return "\(senderDisplayName): \(String(lastMessageAttributedString.characters))"
} else {
return "\(senderDisplayName): \(eventBrief.body)"
Expand Down
4 changes: 1 addition & 3 deletions ElementX/Sources/Services/Room/RoomProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ class RoomProxy: RoomProxyProtocol {
room.setDelegate(delegate: nil)
}

var id: String {
room.id()
}
lazy var id: String = room.id()

var name: String? {
room.name()
Expand Down
12 changes: 6 additions & 6 deletions ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,21 @@ class RoomSummary: RoomSummaryProtocol {
roomProxy.isTombstoned
}

private(set) var displayName: String? {
private(set) var avatar: UIImage? {
didSet {
callbacks.send(.updatedData)
callbacks.send(.updatedAvatar)
}
}

private(set) var lastMessage: EventBrief? {
private(set) var displayName: String? {
didSet {
callbacks.send(.updatedData)
callbacks.send(.updatedDisplayName)
}
}

private(set) var avatar: UIImage? {
private(set) var lastMessage: EventBrief? {
didSet {
callbacks.send(.updatedData)
callbacks.send(.updatedLastMessage)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import Combine
import UIKit

enum RoomSummaryCallback {
case updatedData
case updatedAvatar
case updatedDisplayName
case updatedLastMessage
}

@MainActor
Expand Down
25 changes: 22 additions & 3 deletions ElementX/Sources/Services/Timeline/RoomTimelineController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
private let memberDetailProvider: MemberDetailProviderProtocol

private var cancellables = Set<AnyCancellable>()
private var timelineItemsUpdateTask: Task<Void, Never>? {
willSet {
timelineItemsUpdateTask?.cancel()
}
}

let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()

Expand Down Expand Up @@ -96,10 +101,20 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}

private func updateTimelineItems() {
timelineItemsUpdateTask = Task {
await asyncUpdateTimelineItems()
}
}

private func asyncUpdateTimelineItems() async {
stefanceriu marked this conversation as resolved.
Show resolved Hide resolved
var newTimelineItems = [RoomTimelineItemProtocol]()

var previousMessage: RoomMessageProtocol?
for message in timelineProvider.messages {
if Task.isCancelled {
return
}

let areMessagesFromTheSameDay = haveSameDay(lhs: previousMessage, rhs: message)
let shouldAddSectionHeader = !areMessagesFromTheSameDay

Expand All @@ -111,13 +126,17 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
let areMessagesFromTheSameSender = (previousMessage?.sender == message.sender)
let shouldShowSenderDetails = !areMessagesFromTheSameSender || !areMessagesFromTheSameDay

newTimelineItems.append(timelineItemFactory.buildTimelineItemFor(message: message,
isOutgoing: message.sender == userId,
showSenderDetails: shouldShowSenderDetails))
newTimelineItems.append(await timelineItemFactory.buildTimelineItemFor(message: message,
isOutgoing: message.sender == userId,
showSenderDetails: shouldShowSenderDetails))

previousMessage = message
}

if Task.isCancelled {
return
}

timelineItems = newTimelineItems

callbacks.send(.updatedTimelineItems)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
self.attributedStringBuilder = attributedStringBuilder
}

func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) -> RoomTimelineItemProtocol {
func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) async -> RoomTimelineItemProtocol {
let displayName = memberDetailProvider.displayNameForUserId(message.sender)
let avatarURL = memberDetailProvider.avatarURLStringForUserId(message.sender)
let avatarImage = mediaProvider.imageFromURLString(avatarURL)

switch message {
case let message as TextRoomMessage:
return buildTextTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
return await buildTextTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
case let message as ImageRoomMessage:
return buildImageTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
return await buildImageTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
case let message as NoticeRoomMessage:
return buildNoticeTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
return await buildNoticeTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
case let message as EmoteRoomMessage:
return buildEmoteTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
return await buildEmoteTimelineItemFromMessage(message, isOutgoing, showSenderDetails, displayName, avatarImage)
default:
fatalError("Unknown room message.")
}
Expand All @@ -47,8 +47,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)

return TextRoomTimelineItem(id: message.id,
Expand All @@ -66,7 +66,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
var aspectRatio: CGFloat?
if let width = message.width,
let height = message.height {
Expand All @@ -93,8 +93,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)

return NoticeRoomTimelineItem(id: message.id,
Expand All @@ -112,8 +112,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
_ isOutgoing: Bool,
_ showSenderDetails: Bool,
_ displayName: String?,
_ avatarImage: UIImage?) -> RoomTimelineItemProtocol {
let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
_ avatarImage: UIImage?) async -> RoomTimelineItemProtocol {
let attributedText = await (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body))
let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText)

return EmoteRoomTimelineItem(id: message.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ import Foundation

@MainActor
protocol RoomTimelineItemFactoryProtocol {
func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) -> RoomTimelineItemProtocol
func buildTimelineItemFor(message: RoomMessageProtocol, isOutgoing: Bool, showSenderDetails: Bool) async -> RoomTimelineItemProtocol
}
Loading