mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
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:
@@ -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
|
||||
}
|
||||
@@ -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()))
|
||||
|
||||
Reference in New Issue
Block a user