Timeline item sender profiles (#463)

* Adopt timeline item sender profiles for displaying user avatars and display names
* Prevent images from automatically being evicted from the in-memory cache when entering background
* Get rid of all the `urlString: String`s and start using proper URLs
* Add back manual display name loading as the sender profiles aren't working properly yet
* Implement a sender profile.
* Bump the RustSDK to v1.0.32-alpha

Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
Stefan Ceriu
2023-01-18 16:29:44 +02:00
committed by GitHub
parent 8f906c7be7
commit 8f3f83167b
70 changed files with 390 additions and 495 deletions
@@ -1,24 +0,0 @@
//
// 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
protocol RoomMessageProtocol {
var id: String { get }
var body: String { get }
var sender: String { get }
var timestamp: Date { get }
}
@@ -23,7 +23,7 @@ struct MockRoomProxy: RoomProxyProtocol {
let name: String? = nil
let displayName: String?
var topic: String?
var avatarURL: String?
var avatarURL: URL?
var isDirect = Bool.random()
var isSpace = Bool.random()
var isPublic = Bool.random()
@@ -37,11 +37,11 @@ struct MockRoomProxy: RoomProxyProtocol {
.failure(.failedRetrievingMemberDisplayName)
}
func avatarURLStringForUserId(_ userId: String) -> String? {
func avatarURLForUserId(_ userId: String) -> URL? {
nil
}
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError> {
.failure(.failedRetrievingMemberAvatarURL)
}
@@ -32,8 +32,8 @@ struct RoomMemberProxy {
member.displayName
}
var avatarUrl: String? {
member.avatarUrl
var avatarURL: URL? {
member.avatarUrl.flatMap(URL.init(string:))
}
var membership: MembershipState {
@@ -1,34 +0,0 @@
//
// 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 MatrixRustSDK
struct SomeRoomMessage: RoomMessageProtocol {
let id: String
let body: String
let sender: String
let timestamp: Date
}
struct RoomMessageFactory: RoomMessageFactoryProtocol {
func buildRoomMessageFrom(_ eventItemProxy: EventTimelineItemProxy) -> RoomMessageProtocol {
SomeRoomMessage(id: eventItemProxy.id,
body: eventItemProxy.body ?? "",
sender: eventItemProxy.sender,
timestamp: eventItemProxy.timestamp)
}
}
@@ -1,22 +0,0 @@
//
// 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 MatrixRustSDK
protocol RoomMessageFactoryProtocol {
func buildRoomMessageFrom(_ eventItemProxy: EventTimelineItemProxy) -> RoomMessageProtocol
}
+17 -9
View File
@@ -29,7 +29,7 @@ class RoomProxy: RoomProxyProtocol {
private var sendMessageBackgroundTask: BackgroundTaskProtocol?
private var memberAvatars = [String: String]()
private var memberAvatars = [String: URL]()
private var memberDisplayNames = [String: String]()
private(set) var displayName: String?
@@ -82,8 +82,8 @@ class RoomProxy: RoomProxyProtocol {
room.isTombstoned()
}
var avatarURL: String? {
room.avatarUrl()
var avatarURL: URL? {
room.avatarUrl().flatMap(URL.init(string:))
}
var encryptionBadgeImage: UIImage? {
@@ -95,16 +95,24 @@ class RoomProxy: RoomProxyProtocol {
return Asset.Images.encryptionTrusted.image
}
func avatarURLStringForUserId(_ userId: String) -> String? {
func avatarURLForUserId(_ userId: String) -> URL? {
memberAvatars[userId]
}
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError> {
do {
let avatarURL = try await Task.dispatch(on: serialDispatchQueue) {
guard let urlString = try await Task.dispatch(on: serialDispatchQueue, {
try self.room.memberAvatarUrl(userId: userId)
}) else {
return .success(nil)
}
update(avatarURL: avatarURL, forUserId: userId)
guard let avatarURL = URL(string: urlString) else {
MXLog.error("Invalid avatar URL string: \(String(describing: urlString))")
return .failure(.failedRetrievingMemberAvatarURL)
}
update(url: avatarURL, forUserId: userId)
return .success(avatarURL)
} catch {
return .failure(.failedRetrievingMemberAvatarURL)
@@ -238,8 +246,8 @@ class RoomProxy: RoomProxyProtocol {
// MARK: - Private
private func update(avatarURL: String?, forUserId userId: String) {
memberAvatars[userId] = avatarURL
private func update(url: URL?, forUserId userId: String) {
memberAvatars[userId] = url
}
private func update(displayName: String?, forUserId userId: String) {
@@ -15,6 +15,7 @@
//
import Combine
import Foundation
import MatrixRustSDK
enum RoomProxyError: Error {
@@ -44,11 +45,11 @@ protocol RoomProxyProtocol {
var topic: String? { get }
var avatarURL: String? { get }
var avatarURL: URL? { get }
func avatarURLStringForUserId(_ userId: String) -> String?
func avatarURLForUserId(_ userId: String) -> URL?
func loadAvatarURLForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError>
func displayNameForUserId(_ userId: String) -> String?
@@ -53,21 +53,21 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
static let rooms: [RoomSummary] = [
.filled(details: RoomSummaryDetails(id: "1", name: "First room",
isDirect: true,
avatarURLString: nil,
avatarURL: nil,
lastMessage: AttributedString("Prosciutto beef ribs pancetta filet mignon kevin hamburger, chuck ham venison picanha. Beef ribs chislic turkey biltong tenderloin."),
lastMessageTimestamp: .distantPast,
unreadNotificationCount: 4)),
.filled(details: RoomSummaryDetails(id: "2",
name: "Second room",
isDirect: true,
avatarURLString: "mockImageURLString",
avatarURL: URL.picturesDirectory,
lastMessage: nil,
lastMessageTimestamp: nil,
unreadNotificationCount: 1)),
.filled(details: RoomSummaryDetails(id: "3",
name: "Third room",
isDirect: true,
avatarURLString: nil,
avatarURL: nil,
lastMessage: try? AttributedString(markdown: "**@mock:client.com**: T-bone beef ribs bacon"),
lastMessageTimestamp: .distantPast,
unreadNotificationCount: 0)),
@@ -20,7 +20,7 @@ struct RoomSummaryDetails {
let id: String
let name: String
let isDirect: Bool
let avatarURLString: String?
let avatarURL: URL?
let lastMessage: AttributedString?
let lastMessageTimestamp: Date?
let unreadNotificationCount: UInt
@@ -20,7 +20,6 @@ import MatrixRustSDK
class RoomSummaryProvider: RoomSummaryProviderProtocol {
private let slidingSyncViewProxy: SlidingSyncViewProxy
private let roomMessageFactory: RoomMessageFactoryProtocol
private let serialDispatchQueue: DispatchQueue
private var cancellables = Set<AnyCancellable>()
@@ -35,9 +34,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
}
}
init(slidingSyncViewProxy: SlidingSyncViewProxy, roomMessageFactory: RoomMessageFactoryProtocol) {
init(slidingSyncViewProxy: SlidingSyncViewProxy) {
self.slidingSyncViewProxy = slidingSyncViewProxy
self.roomMessageFactory = roomMessageFactory
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider")
rooms = slidingSyncViewProxy.currentRoomsList().map { roomListEntry in
@@ -147,22 +145,31 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
var attributedLastMessage: AttributedString?
var lastMessageTimestamp: Date?
if let latestRoomMessage = room.latestRoomMessage() {
let lastMessage = roomMessageFactory.buildRoomMessageFrom(EventTimelineItemProxy(item: latestRoomMessage))
#warning("Intentionally remove the sender mxid from the room list for now")
// if let lastMessageSender = try? AttributedString(markdown: "**\(lastMessage.sender)**") {
// // Don't include the message body in the markdown otherwise it makes tappable links.
// attributedLastMessage = lastMessageSender + ": " + AttributedString(lastMessage.body)
// }
attributedLastMessage = AttributedString(lastMessage.body)
lastMessageTimestamp = lastMessage.timestamp
// Dispatch onto another queue otherwise the rust method latestRoomMessage crashes.
// This will be fixed when we get async uniffi support.
DispatchQueue.global(qos: .default).sync {
if let latestRoomMessage = room.latestRoomMessage() {
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage)
lastMessageTimestamp = lastMessage.timestamp
if let senderDisplayName = lastMessage.sender.displayName,
let attributedSenderDisplayName = try? AttributedString(markdown: "**\(senderDisplayName)**") {
// Don't include the message body in the markdown otherwise it makes tappable links.
attributedLastMessage = attributedSenderDisplayName + ": " + AttributedString(lastMessage.body ?? "")
} else if let body = lastMessage.body {
attributedLastMessage = AttributedString(body)
}
}
}
let avatarURL = room.fullRoom()?.avatarUrl().flatMap(URL.init(string:))
let details = RoomSummaryDetails(id: room.roomId(),
name: room.name() ?? room.roomId(),
isDirect: room.isDm() ?? false,
avatarURLString: room.fullRoom()?.avatarUrl(),
avatarURL: avatarURL,
lastMessage: attributedLastMessage,
lastMessageTimestamp: lastMessageTimestamp,
unreadNotificationCount: UInt(room.unreadNotifications().notificationCount()))