mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +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:
@@ -101,7 +101,7 @@ struct HomeScreenRoom: Identifiable, Equatable {
|
||||
|
||||
var lastMessage: AttributedString?
|
||||
|
||||
var avatarURLString: String?
|
||||
var avatarURL: URL?
|
||||
|
||||
var avatar: UIImage?
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
.store(in: &cancellables)
|
||||
|
||||
Task {
|
||||
if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() {
|
||||
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .home)) {
|
||||
if case let .success(url) = await userSession.clientProxy.loadUserAvatarURL() {
|
||||
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURL(url, avatarSize: .user(on: .home)) {
|
||||
state.userAvatar = avatar
|
||||
}
|
||||
}
|
||||
@@ -169,12 +169,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
private func loadDataForRoomIdentifier(_ identifier: String) {
|
||||
guard let room = state.rooms.first(where: { $0.roomId == identifier }),
|
||||
room.avatar == nil,
|
||||
let avatarURLString = room.avatarURLString else {
|
||||
let avatarURL = room.avatarURL else {
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
if case let .success(image) = await userSession.mediaProvider.loadImageFromURLString(avatarURLString, avatarSize: .room(on: .home)) {
|
||||
if case let .success(image) = await userSession.mediaProvider.loadImageFromURL(avatarURL, avatarSize: .room(on: .home)) {
|
||||
guard let roomIndex = state.rooms.firstIndex(where: { $0.roomId == identifier }) else {
|
||||
return
|
||||
}
|
||||
@@ -222,7 +222,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
}
|
||||
|
||||
private func buildRoom(with details: RoomSummaryDetails, invalidated: Bool) -> HomeScreenRoom {
|
||||
let avatarImage = userSession.mediaProvider.imageFromURLString(details.avatarURLString, avatarSize: .room(on: .home))
|
||||
let avatarImage = details.avatarURL.flatMap { userSession.mediaProvider.imageFromURL($0, avatarSize: .room(on: .home)) }
|
||||
|
||||
var timestamp: String?
|
||||
if let lastMessageTimestamp = details.lastMessageTimestamp {
|
||||
@@ -236,7 +236,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
hasUnreads: details.unreadNotificationCount > 0,
|
||||
timestamp: timestamp,
|
||||
lastMessage: details.lastMessage,
|
||||
avatarURLString: details.avatarURLString,
|
||||
avatarURL: details.avatarURL,
|
||||
avatar: avatarImage)
|
||||
}
|
||||
|
||||
|
||||
@@ -61,13 +61,13 @@ enum RoomDetailsViewAction {
|
||||
struct RoomDetailsMember: Identifiable, Equatable {
|
||||
let id: String
|
||||
let name: String?
|
||||
let avatarUrl: String?
|
||||
let avatarURL: URL?
|
||||
// cached
|
||||
var avatar: UIImage?
|
||||
|
||||
init(withProxy proxy: RoomMemberProxy) {
|
||||
id = proxy.userId
|
||||
name = proxy.displayName
|
||||
avatarUrl = proxy.avatarUrl
|
||||
avatarURL = proxy.avatarURL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,7 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
|
||||
if let avatarURL = roomProxy.avatarURL {
|
||||
Task {
|
||||
if case let .success(avatar) = await mediaProvider.loadImageFromURLString(avatarURL,
|
||||
avatarSize: .room(on: .details)) {
|
||||
if case let .success(avatar) = await mediaProvider.loadImageFromURL(avatarURL, avatarSize: .room(on: .details)) {
|
||||
state.roomAvatar = avatar
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,15 +46,14 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
return
|
||||
}
|
||||
if member.avatar != nil {
|
||||
// already loaded
|
||||
// Avatar already loaded.
|
||||
return
|
||||
}
|
||||
guard let avatarUrl = member.avatarUrl else {
|
||||
// user has no avatar
|
||||
guard let avatarURL = member.avatarURL else {
|
||||
return
|
||||
}
|
||||
|
||||
switch await mediaProvider.loadImageFromURLString(avatarUrl, avatarSize: .user(on: .roomDetails)) {
|
||||
switch await mediaProvider.loadImageFromURL(avatarURL, avatarSize: .user(on: .roomDetails)) {
|
||||
case .success(let image):
|
||||
if let index = state.members.firstIndex(where: { $0.id == memberId }) {
|
||||
state.members[index].avatar = image
|
||||
|
||||
@@ -36,7 +36,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
timelineViewFactory: RoomTimelineViewFactoryProtocol,
|
||||
mediaProvider: MediaProviderProtocol,
|
||||
roomName: String?,
|
||||
roomAvatarUrl: String? = nil) {
|
||||
roomAvatarUrl: URL? = nil) {
|
||||
self.timelineController = timelineController
|
||||
self.timelineViewFactory = timelineViewFactory
|
||||
self.mediaProvider = mediaProvider
|
||||
@@ -76,11 +76,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
state.contextMenuBuilder = buildContexMenuForItemId(_:)
|
||||
|
||||
buildTimelineViews()
|
||||
|
||||
|
||||
if let roomAvatarUrl {
|
||||
Task {
|
||||
if case let .success(avatar) = await mediaProvider.loadImageFromURLString(roomAvatarUrl,
|
||||
avatarSize: .room(on: .timeline)) {
|
||||
if case let .success(avatar) = await mediaProvider.loadImageFromURL(roomAvatarUrl,
|
||||
avatarSize: .room(on: .timeline)) {
|
||||
state.roomAvatar = avatar
|
||||
}
|
||||
}
|
||||
@@ -261,7 +261,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
case .reply:
|
||||
state.bindings.composerFocused = true
|
||||
state.composerMode = .reply(id: item.id, displayName: item.senderDisplayName ?? item.senderId)
|
||||
state.composerMode = .reply(id: item.id, displayName: item.sender.displayName ?? item.sender.id)
|
||||
case .viewSource:
|
||||
let debugDescription = timelineController.debugDescriptionFor(item.id)
|
||||
MXLog.info(debugDescription)
|
||||
|
||||
@@ -71,7 +71,7 @@ struct RoomHeaderView_Previews: PreviewProvider {
|
||||
timelineViewFactory: RoomTimelineViewFactory(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomName: "Some Room name",
|
||||
roomAvatarUrl: "mock_url")
|
||||
roomAvatarUrl: URL.picturesDirectory)
|
||||
|
||||
RoomHeaderView(context: viewModel.context)
|
||||
.previewLayout(.sizeThatFits)
|
||||
|
||||
@@ -60,7 +60,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
||||
HStack(alignment: .top, spacing: 4) {
|
||||
TimelineSenderAvatarView(timelineItem: timelineItem)
|
||||
.accessibilityHidden(true)
|
||||
Text(timelineItem.senderDisplayName ?? timelineItem.senderId)
|
||||
Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
|
||||
.font(.element.footnoteBold)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.lineLimit(1)
|
||||
|
||||
@@ -47,7 +47,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
|
||||
if timelineItem.shouldShowSenderDetails {
|
||||
HStack {
|
||||
TimelineSenderAvatarView(timelineItem: timelineItem)
|
||||
Text(timelineItem.senderDisplayName ?? timelineItem.senderId)
|
||||
Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
|
||||
.font(.body)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.fontWeight(.semibold)
|
||||
|
||||
@@ -22,13 +22,10 @@ struct EmoteRoomTimelineView: View {
|
||||
|
||||
var body: some View {
|
||||
TimelineStyler(timelineItem: timelineItem) {
|
||||
HStack(alignment: .top) {
|
||||
Image(systemName: "face.dashed").padding(.top, 1.0)
|
||||
if let attributedComponents = timelineItem.attributedComponents {
|
||||
FormattedBodyText(attributedComponents: attributedComponents)
|
||||
} else {
|
||||
FormattedBodyText(text: timelineItem.text)
|
||||
}
|
||||
if let attributedComponents = timelineItem.attributedComponents {
|
||||
FormattedBodyText(attributedComponents: attributedComponents)
|
||||
} else {
|
||||
FormattedBodyText(text: timelineItem.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +56,6 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: senderId)
|
||||
sender: .init(id: senderId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,6 @@ struct EncryptedRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: false,
|
||||
senderId: senderId)
|
||||
sender: .init(id: senderId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ struct FileRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
source: nil,
|
||||
thumbnailSource: nil))
|
||||
|
||||
@@ -57,7 +57,7 @@ struct FileRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
source: nil,
|
||||
thumbnailSource: nil))
|
||||
|
||||
@@ -67,7 +67,7 @@ struct FileRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
source: nil,
|
||||
thumbnailSource: nil))
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
source: nil,
|
||||
image: UIImage(systemName: "photo")))
|
||||
|
||||
@@ -72,7 +72,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
source: nil,
|
||||
image: nil))
|
||||
|
||||
@@ -82,7 +82,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
source: nil,
|
||||
image: nil,
|
||||
aspectRatio: 0.7,
|
||||
|
||||
@@ -59,6 +59,6 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: senderId)
|
||||
sender: .init(id: senderId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,6 @@ struct RedactedRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: senderId)
|
||||
sender: .init(id: senderId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
urlString: nil,
|
||||
sender: .init(id: "Bob"),
|
||||
imageURL: nil,
|
||||
image: UIImage(systemName: "photo")))
|
||||
|
||||
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString,
|
||||
@@ -73,8 +73,8 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
urlString: nil,
|
||||
sender: .init(id: "Bob"),
|
||||
imageURL: nil,
|
||||
image: nil))
|
||||
|
||||
StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString,
|
||||
@@ -83,8 +83,8 @@ struct StickerRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
urlString: nil,
|
||||
sender: .init(id: "Bob"),
|
||||
imageURL: nil,
|
||||
image: nil,
|
||||
aspectRatio: 0.7,
|
||||
blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW"))
|
||||
|
||||
@@ -68,6 +68,6 @@ struct TextRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
senderId: senderId)
|
||||
sender: .init(id: senderId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,6 @@ struct UnsupportedRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: false,
|
||||
senderId: senderId)
|
||||
sender: .init(id: senderId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ struct VideoRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
duration: 21,
|
||||
source: nil,
|
||||
thumbnailSource: nil,
|
||||
@@ -84,7 +84,7 @@ struct VideoRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
duration: 22,
|
||||
source: nil,
|
||||
thumbnailSource: nil,
|
||||
@@ -96,7 +96,7 @@ struct VideoRoomTimelineView_Previews: PreviewProvider {
|
||||
groupState: .single,
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
senderId: "Bob",
|
||||
sender: .init(id: "Bob"),
|
||||
duration: 23,
|
||||
source: nil,
|
||||
thumbnailSource: nil,
|
||||
|
||||
@@ -24,14 +24,14 @@ struct TimelineSenderAvatarView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .center) {
|
||||
if let avatar = timelineItem.senderAvatar {
|
||||
if let avatar = timelineItem.sender.avatar {
|
||||
Image(uiImage: avatar)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.overlay(Circle().stroke(Color.element.accent))
|
||||
} else {
|
||||
PlaceholderAvatarImage(text: timelineItem.senderDisplayName ?? timelineItem.senderId,
|
||||
contentId: timelineItem.senderId)
|
||||
PlaceholderAvatarImage(text: timelineItem.sender.displayName ?? timelineItem.sender.id,
|
||||
contentId: timelineItem.sender.id)
|
||||
}
|
||||
}
|
||||
.clipShape(Circle())
|
||||
@@ -41,6 +41,6 @@ struct TimelineSenderAvatarView: View {
|
||||
.stroke(Color.element.background, lineWidth: 3)
|
||||
)
|
||||
|
||||
.animation(.elementDefault, value: timelineItem.senderAvatar)
|
||||
.animation(.elementDefault, value: timelineItem.sender.avatar)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,22 +27,22 @@ class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol {
|
||||
self.userSession = userSession
|
||||
let bindings = SettingsViewStateBindings()
|
||||
super.init(initialViewState: .init(bindings: bindings, deviceID: userSession.deviceId, userID: userSession.userID))
|
||||
|
||||
|
||||
Task {
|
||||
if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() {
|
||||
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .settings)) {
|
||||
if case let .success(userAvatarURL) = await userSession.clientProxy.loadUserAvatarURL() {
|
||||
if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURL(userAvatarURL, avatarSize: .user(on: .settings)) {
|
||||
state.userAvatar = avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Task {
|
||||
if case let .success(userDisplayName) = await self.userSession.clientProxy.loadUserDisplayName() {
|
||||
state.userDisplayName = userDisplayName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func process(viewAction: SettingsViewAction) async {
|
||||
switch viewAction {
|
||||
case .close:
|
||||
|
||||
Reference in New Issue
Block a user