mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Added user display names and switched to using an avatar url directly in the homeScreen viewModel for the current user.
This commit is contained in:
@@ -554,8 +554,8 @@
|
|||||||
18F2BAA327D25B4000DD1988 /* HomeScreen */ = {
|
18F2BAA327D25B4000DD1988 /* HomeScreen */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
18F2BAAB27D25B4000DD1988 /* HomeScreenModels.swift */,
|
|
||||||
18F2BAA427D25B4000DD1988 /* HomeScreenCoordinator.swift */,
|
18F2BAA427D25B4000DD1988 /* HomeScreenCoordinator.swift */,
|
||||||
|
18F2BAAB27D25B4000DD1988 /* HomeScreenModels.swift */,
|
||||||
18F2BAAA27D25B4000DD1988 /* HomeScreenViewModelProtocol.swift */,
|
18F2BAAA27D25B4000DD1988 /* HomeScreenViewModelProtocol.swift */,
|
||||||
18F2BAAE27D25B4000DD1988 /* HomeScreenViewModel.swift */,
|
18F2BAAE27D25B4000DD1988 /* HomeScreenViewModel.swift */,
|
||||||
18F2BAAC27D25B4000DD1988 /* View */,
|
18F2BAAC27D25B4000DD1988 /* View */,
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
|||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
let userDisplayName = self.parameters.userSession.userDisplayName ?? self.parameters.userSession.userIdentifier
|
let userDisplayName = self.parameters.userSession.userDisplayName ?? self.parameters.userSession.userIdentifier
|
||||||
viewModel = HomeScreenViewModel(userDisplayName: userDisplayName, mediaProvider: self.parameters.mediaProvider)
|
viewModel = HomeScreenViewModel(userDisplayName: userDisplayName,
|
||||||
|
userAvatarURL: self.parameters.userSession.userAvatarURL,
|
||||||
|
mediaProvider: self.parameters.mediaProvider)
|
||||||
|
|
||||||
let view = HomeScreen(context: viewModel.context)
|
let view = HomeScreen(context: viewModel.context)
|
||||||
hostingController = UIHostingController(rootView: view)
|
hostingController = UIHostingController(rootView: view)
|
||||||
@@ -62,15 +64,6 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
|||||||
switch result {
|
switch result {
|
||||||
case .logout:
|
case .logout:
|
||||||
self.completion?(.logout)
|
self.completion?(.logout)
|
||||||
case .loadUserAvatar:
|
|
||||||
self.parameters.mediaProvider.loadCurrentUserAvatar({ result in
|
|
||||||
switch result {
|
|
||||||
case .success(let avatar):
|
|
||||||
self.viewModel.updateWithUserAvatar(avatar)
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
case .selectRoom(let roomIdentifier):
|
case .selectRoom(let roomIdentifier):
|
||||||
self.completion?(.selectRoom(roomIdentifier: roomIdentifier))
|
self.completion?(.selectRoom(roomIdentifier: roomIdentifier))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,11 @@ import UIKit
|
|||||||
|
|
||||||
enum HomeScreenViewModelResult {
|
enum HomeScreenViewModelResult {
|
||||||
case logout
|
case logout
|
||||||
case loadUserAvatar
|
|
||||||
case selectRoom(roomIdentifier: String)
|
case selectRoom(roomIdentifier: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum HomeScreenViewAction {
|
enum HomeScreenViewAction {
|
||||||
case logout
|
case logout
|
||||||
case loadUserAvatar
|
|
||||||
case loadRoomData(roomIdentifier: String)
|
case loadRoomData(roomIdentifier: String)
|
||||||
case selectRoom(roomIdentifier: String)
|
case selectRoom(roomIdentifier: String)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,17 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
init(userDisplayName: String, mediaProvider: MediaProviderProtocol) {
|
init(userDisplayName: String, userAvatarURL: String?, mediaProvider: MediaProviderProtocol) {
|
||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
super.init(initialViewState: HomeScreenViewState(userDisplayName: userDisplayName, isLoadingRooms: true))
|
super.init(initialViewState: HomeScreenViewState(userDisplayName: userDisplayName, isLoadingRooms: true))
|
||||||
|
|
||||||
|
if let userAvatarURL = userAvatarURL {
|
||||||
|
mediaProvider.loadImageFromURL(userAvatarURL) { [weak self] result in
|
||||||
|
if case let .success(avatar) = result {
|
||||||
|
self?.state.userAvatar = avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
@@ -50,8 +58,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
self.completion?(.logout)
|
self.completion?(.logout)
|
||||||
case .loadRoomData(let roomIdentifier):
|
case .loadRoomData(let roomIdentifier):
|
||||||
self.loadRoomDataForIdentifier(roomIdentifier)
|
self.loadRoomDataForIdentifier(roomIdentifier)
|
||||||
case .loadUserAvatar:
|
|
||||||
self.completion?(.loadUserAvatar)
|
|
||||||
case .selectRoom(let roomIdentifier):
|
case .selectRoom(let roomIdentifier):
|
||||||
self.completion?(.selectRoom(roomIdentifier: roomIdentifier))
|
self.completion?(.selectRoom(roomIdentifier: roomIdentifier))
|
||||||
}
|
}
|
||||||
@@ -118,7 +124,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
room.loadDisplayName { [weak self] result in
|
room.displayName { [weak self] result in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|
||||||
switch result {
|
switch result {
|
||||||
|
|||||||
@@ -23,6 +23,4 @@ protocol HomeScreenViewModelProtocol {
|
|||||||
var context: HomeScreenViewModelType.Context { get }
|
var context: HomeScreenViewModelType.Context { get }
|
||||||
|
|
||||||
func updateWithRoomList(_ roomList: [RoomProxyProtocol])
|
func updateWithRoomList(_ roomList: [RoomProxyProtocol])
|
||||||
|
|
||||||
func updateWithUserAvatar(_ avatar: UIImage?)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,6 @@ struct HomeScreen: View {
|
|||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: 40, height: 40, alignment: .center)
|
.frame(width: 40, height: 40, alignment: .center)
|
||||||
.mask(Circle())
|
.mask(Circle())
|
||||||
} else {
|
|
||||||
let _ = context.send(viewAction: .loadUserAvatar)
|
|
||||||
}
|
}
|
||||||
Text("Hello, \(context.viewState.userDisplayName)!")
|
Text("Hello, \(context.viewState.userDisplayName)!")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
@@ -154,6 +152,7 @@ struct RoomCell: View {
|
|||||||
struct HomeScreen_Previews: PreviewProvider {
|
struct HomeScreen_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed",
|
let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed",
|
||||||
|
userAvatarURL: nil,
|
||||||
mediaProvider: MockMediaProvider())
|
mediaProvider: MockMediaProvider())
|
||||||
|
|
||||||
let rooms = [MockRoomProxy(displayName: "Alfa"),
|
let rooms = [MockRoomProxy(displayName: "Alfa"),
|
||||||
|
|||||||
@@ -67,7 +67,16 @@ class UserSession: ClientDelegate {
|
|||||||
do {
|
do {
|
||||||
return try client.displayName()
|
return try client.displayName()
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed retrieving room info with error: \(error)")
|
MXLog.error("Failed retrieving the user's display name with error: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var userAvatarURL: String? {
|
||||||
|
do {
|
||||||
|
return try client.avatarUrl()
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed retrieving the user's avatar URL with error: \(error)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +102,7 @@ class UserSession: ClientDelegate {
|
|||||||
|
|
||||||
func fetchRoomList(_ completion: @escaping ([RoomProxy]) -> Void) {
|
func fetchRoomList(_ completion: @escaping ([RoomProxy]) -> Void) {
|
||||||
DispatchQueue.global(qos: .background).async {
|
DispatchQueue.global(qos: .background).async {
|
||||||
let rooms = self.client.conversations().map {
|
let rooms = self.client.rooms().map {
|
||||||
return RoomProxy(room: $0, messageFactory: RoomMessageFactory())
|
return RoomProxy(room: $0, messageFactory: RoomMessageFactory())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,22 +21,6 @@ struct MediaProvider: MediaProviderProtocol {
|
|||||||
self.processingQueue = DispatchQueue(label: "MediaProviderProcessingQueue")
|
self.processingQueue = DispatchQueue(label: "MediaProviderProcessingQueue")
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCurrentUserAvatar(_ completion: @escaping (Result<UIImage?, MediaProviderError>) -> Void) {
|
|
||||||
processingQueue.async {
|
|
||||||
do {
|
|
||||||
let imageData = try client.avatar()
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.success(UIImage(data: Data(bytes: imageData, count: imageData.count))))
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed retrieving image with error: \(error)")
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(.failure(.failedRetrievingImage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func imageForURL(_ url: String?) -> UIImage? {
|
func imageForURL(_ url: String?) -> UIImage? {
|
||||||
guard let url = url else {
|
guard let url = url else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ enum MediaProviderError: Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protocol MediaProviderProtocol {
|
protocol MediaProviderProtocol {
|
||||||
func loadCurrentUserAvatar(_ completion: @escaping (Result<UIImage?, MediaProviderError>) -> Void)
|
|
||||||
|
|
||||||
func imageForURL(_ url: String?) -> UIImage?
|
func imageForURL(_ url: String?) -> UIImage?
|
||||||
|
|
||||||
func loadImageFromURL(_ url: String, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void)
|
func loadImageFromURL(_ url: String, _ completion: @escaping (Result<UIImage, MediaProviderError>) -> Void)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class MemberDetailsProvider: MemberDetailsProviderProtocol {
|
|||||||
private let roomProxy: RoomProxyProtocol?
|
private let roomProxy: RoomProxyProtocol?
|
||||||
private let processingQueue = DispatchQueue(label: "MemberDetailsProviderProcessingQueue")
|
private let processingQueue = DispatchQueue(label: "MemberDetailsProviderProcessingQueue")
|
||||||
private var memberAvatars = [String: String]()
|
private var memberAvatars = [String: String]()
|
||||||
|
private var memberDisplayNames = [String: String]()
|
||||||
|
|
||||||
init(roomProxy: RoomProxyProtocol) {
|
init(roomProxy: RoomProxyProtocol) {
|
||||||
self.roomProxy = roomProxy
|
self.roomProxy = roomProxy
|
||||||
@@ -50,4 +51,38 @@ class MemberDetailsProvider: MemberDetailsProviderProtocol {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayNameForUserId(_ userId: String) -> String? {
|
||||||
|
self.memberDisplayNames[userId]
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void) {
|
||||||
|
guard let roomProxy = roomProxy else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let avatarURL = displayNameForUserId(userId) {
|
||||||
|
completion(.success(avatarURL))
|
||||||
|
}
|
||||||
|
|
||||||
|
processingQueue.async {
|
||||||
|
roomProxy.displayNameForUserId(userId, completion: { [weak self] result in
|
||||||
|
guard let self = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let displayName):
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.memberDisplayNames[userId] = displayName
|
||||||
|
completion(.success(displayName))
|
||||||
|
}
|
||||||
|
case .failure:
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.failure(.failedRetrievingUserDisplayName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,13 @@ import Foundation
|
|||||||
enum MemberDetailsProviderError: Error {
|
enum MemberDetailsProviderError: Error {
|
||||||
case invalidRoomProxy
|
case invalidRoomProxy
|
||||||
case failedRetrievingUserAvatarURL
|
case failedRetrievingUserAvatarURL
|
||||||
|
case failedRetrievingUserDisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol MemberDetailsProviderProtocol {
|
protocol MemberDetailsProviderProtocol {
|
||||||
func avatarURLForUserId(_ userId: String) -> String?
|
func avatarURLForUserId(_ userId: String) -> String?
|
||||||
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void)
|
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void)
|
||||||
|
|
||||||
|
func displayNameForUserId(_ userId: String) -> String?
|
||||||
|
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, MemberDetailsProviderError>) -> Void)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ struct MockRoomProxy: RoomProxyProtocol {
|
|||||||
|
|
||||||
var callbacks = PassthroughSubject<RoomProxyCallback, Never>()
|
var callbacks = PassthroughSubject<RoomProxyCallback, Never>()
|
||||||
|
|
||||||
func loadDisplayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void) {
|
func displayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void) {
|
||||||
completion(.success(displayName))
|
completion(.success(displayName))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,4 +41,8 @@ struct MockRoomProxy: RoomProxyProtocol {
|
|||||||
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void) {
|
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,13 +100,29 @@ class RoomProxy: RoomProxyProtocol, Equatable {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completion(.failure(RoomProxyError.failedRetrievingDisplayName))
|
completion(.failure(RoomProxyError.failedRetrievingMemberAvatarURL))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadDisplayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void) {
|
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void) {
|
||||||
|
processingQueue.async {
|
||||||
|
do {
|
||||||
|
let displayName = try self.room.memberDisplayName(userId: userId)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.success(displayName))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion(.failure(RoomProxyError.failedRetrievingMemberDisplayName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void) {
|
||||||
processingQueue.async {
|
processingQueue.async {
|
||||||
do {
|
do {
|
||||||
let displayName = try self.room.displayName()
|
let displayName = try self.room.displayName()
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ enum RoomProxyError: Error {
|
|||||||
case failedRetrievingDisplayName
|
case failedRetrievingDisplayName
|
||||||
case failedRetrievingAvatar
|
case failedRetrievingAvatar
|
||||||
case backwardStreamNotAvailable
|
case backwardStreamNotAvailable
|
||||||
|
case failedRetrievingMemberAvatarURL
|
||||||
|
case failedRetrievingMemberDisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RoomProxyCallback {
|
enum RoomProxyCallback {
|
||||||
@@ -33,9 +35,11 @@ protocol RoomProxyProtocol {
|
|||||||
|
|
||||||
var avatarURL: String? { get }
|
var avatarURL: String? { get }
|
||||||
|
|
||||||
|
func displayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void)
|
||||||
|
|
||||||
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void)
|
func avatarURLForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void)
|
||||||
|
|
||||||
func loadDisplayName(_ completion: @escaping (Result<String, RoomProxyError>) -> Void)
|
func displayNameForUserId(_ userId: String, completion: @escaping (Result<String?, RoomProxyError>) -> Void)
|
||||||
|
|
||||||
func paginateBackwards(count: UInt, callback: ((Result<[RoomMessageProtocol], RoomProxyError>) -> Void)?)
|
func paginateBackwards(count: UInt, callback: ((Result<[RoomMessageProtocol], RoomProxyError>) -> Void)?)
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
|
||||||
|
|
||||||
var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Yesterday"),
|
var timelineItems: [RoomTimelineItemProtocol] = [SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Yesterday"),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString, text: "You rock!", timestamp: "10:10 AM", shouldShowSenderDetails: true, sender: "Alice"),
|
TextRoomTimelineItem(id: UUID().uuidString, text: "You rock!", timestamp: "10:10 AM", shouldShowSenderDetails: true, senderId: "Alice"),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString, text: "You also rule!", timestamp: "10:11 AM", shouldShowSenderDetails: false, sender: "Alice"),
|
TextRoomTimelineItem(id: UUID().uuidString, text: "You also rule!", timestamp: "10:11 AM", shouldShowSenderDetails: false, senderId: "Alice"),
|
||||||
SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Today"),
|
SeparatorRoomTimelineItem(id: UUID().uuidString, text: "Today"),
|
||||||
TextRoomTimelineItem(id: UUID().uuidString, text: "You too!", timestamp: "5 PM", shouldShowSenderDetails: true, sender: "Bob")]
|
TextRoomTimelineItem(id: UUID().uuidString, text: "You too!", timestamp: "5 PM", shouldShowSenderDetails: true, senderId: "Bob")]
|
||||||
|
|
||||||
func paginateBackwards(_ count: UInt, callback: ((Result<Void, RoomTimelineControllerError>) -> Void)) {
|
func paginateBackwards(_ count: UInt, callback: ((Result<Void, RoomTimelineControllerError>) -> Void)) {
|
||||||
callbacks.send(.updatedTimelineItems)
|
callbacks.send(.updatedTimelineItems)
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let item = timelineItem as? EventBasedTimelineItemProtocol {
|
if let item = timelineItem as? EventBasedTimelineItemProtocol {
|
||||||
loadAvatarForTimelineItem(item)
|
loadUserAvatarForTimelineItem(item)
|
||||||
|
loadUserDisplayNameForTimelineItem(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch timelineItem {
|
switch timelineItem {
|
||||||
@@ -110,13 +111,11 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func loadImageForTimelineItem(_ timelineItem: ImageRoomTimelineItem) {
|
private func loadImageForTimelineItem(_ timelineItem: ImageRoomTimelineItem) {
|
||||||
var item = timelineItem
|
if timelineItem.image != nil {
|
||||||
|
|
||||||
if item.image != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let url = item.url else {
|
guard let url = timelineItem.url else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +125,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if case let .success(image) = result {
|
if case let .success(image) = result {
|
||||||
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }) else {
|
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
||||||
|
var item = self.timelineItems[index] as? ImageRoomTimelineItem else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,19 +137,18 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) {
|
private func loadUserAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) {
|
||||||
var item = timelineItem
|
if timelineItem.shouldShowSenderDetails == false {
|
||||||
|
|
||||||
if item.shouldShowSenderDetails == false {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
memberDetailsProvider.avatarURLForUserId(timelineItem.sender) { result in
|
memberDetailsProvider.avatarURLForUserId(timelineItem.senderId) { result in
|
||||||
if case let .success(avatarURL) = result,
|
if case let .success(avatarURL) = result,
|
||||||
let avatarURL = avatarURL {
|
let avatarURL = avatarURL {
|
||||||
self.mediaProvider.loadImageFromURL(avatarURL) { result in
|
self.mediaProvider.loadImageFromURL(avatarURL) { result in
|
||||||
if case let .success(image) = result {
|
if case let .success(image) = result {
|
||||||
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }) else {
|
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
||||||
|
var item = self.timelineItems[index] as? EventBasedTimelineItemProtocol else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,4 +160,24 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func loadUserDisplayNameForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) {
|
||||||
|
if timelineItem.shouldShowSenderDetails == false {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
memberDetailsProvider.displayNameForUserId(timelineItem.senderId) { result in
|
||||||
|
if case let .success(displayName) = result,
|
||||||
|
let displayName = displayName {
|
||||||
|
guard let index = self.timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
|
||||||
|
var item = self.timelineItems[index] as? EventBasedTimelineItemProtocol else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.senderDisplayName = displayName
|
||||||
|
self.timelineItems[index] = item
|
||||||
|
self.callbacks.send(.updatedTimelineItem(timelineItem.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -14,6 +14,7 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol {
|
|||||||
var timestamp: String { get }
|
var timestamp: String { get }
|
||||||
var shouldShowSenderDetails: Bool { get }
|
var shouldShowSenderDetails: Bool { get }
|
||||||
|
|
||||||
var sender: String { get }
|
var senderId: String { get }
|
||||||
|
var senderDisplayName: String? { get set }
|
||||||
var senderAvatar: UIImage? { get set }
|
var senderAvatar: UIImage? { get set }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
|
|||||||
let timestamp: String
|
let timestamp: String
|
||||||
let shouldShowSenderDetails: Bool
|
let shouldShowSenderDetails: Bool
|
||||||
|
|
||||||
let sender: String
|
let senderId: String
|
||||||
|
var senderDisplayName: String?
|
||||||
var senderAvatar: UIImage?
|
var senderAvatar: UIImage?
|
||||||
|
|
||||||
let url: String?
|
let url: String?
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equat
|
|||||||
let timestamp: String
|
let timestamp: String
|
||||||
let shouldShowSenderDetails: Bool
|
let shouldShowSenderDetails: Bool
|
||||||
|
|
||||||
let sender: String
|
let senderId: String
|
||||||
|
var senderDisplayName: String?
|
||||||
var senderAvatar: UIImage?
|
var senderAvatar: UIImage?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ struct RoomTimelineItemFactory {
|
|||||||
|
|
||||||
func buildTimelineItemFor(_ roomMessage: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol {
|
func buildTimelineItemFor(_ roomMessage: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol {
|
||||||
|
|
||||||
|
let displayName = memberDetailsProvider.displayNameForUserId(roomMessage.sender)
|
||||||
let avatarURL = memberDetailsProvider.avatarURLForUserId(roomMessage.sender)
|
let avatarURL = memberDetailsProvider.avatarURLForUserId(roomMessage.sender)
|
||||||
let avatarImage = mediaProvider.imageForURL(avatarURL)
|
let avatarImage = mediaProvider.imageForURL(avatarURL)
|
||||||
|
|
||||||
@@ -30,14 +31,16 @@ struct RoomTimelineItemFactory {
|
|||||||
text: message.content,
|
text: message.content,
|
||||||
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
|
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
|
||||||
shouldShowSenderDetails: showSenderDetails,
|
shouldShowSenderDetails: showSenderDetails,
|
||||||
sender: message.sender,
|
senderId: message.sender,
|
||||||
|
senderDisplayName: displayName,
|
||||||
senderAvatar: avatarImage)
|
senderAvatar: avatarImage)
|
||||||
case let message as ImageRoomMessage:
|
case let message as ImageRoomMessage:
|
||||||
return ImageRoomTimelineItem(id: message.id,
|
return ImageRoomTimelineItem(id: message.id,
|
||||||
text: message.content,
|
text: message.content,
|
||||||
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
|
timestamp: message.originServerTs.formatted(date: .omitted, time: .shortened),
|
||||||
shouldShowSenderDetails: showSenderDetails,
|
shouldShowSenderDetails: showSenderDetails,
|
||||||
sender: message.sender,
|
senderId: message.sender,
|
||||||
|
senderDisplayName: displayName,
|
||||||
senderAvatar: avatarImage,
|
senderAvatar: avatarImage,
|
||||||
url: message.url,
|
url: message.url,
|
||||||
image: mediaProvider.imageForURL(message.url))
|
image: mediaProvider.imageForURL(message.url))
|
||||||
|
|||||||
+10
-2
@@ -16,7 +16,7 @@ struct EventBasedTimelineView: View {
|
|||||||
if timelineItem.shouldShowSenderDetails {
|
if timelineItem.shouldShowSenderDetails {
|
||||||
HStack {
|
HStack {
|
||||||
avatar
|
avatar
|
||||||
Text(timelineItem.sender)
|
Text(timelineItem.senderDisplayName ?? timelineItem.senderId)
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.bold()
|
.bold()
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -35,10 +35,18 @@ struct EventBasedTimelineView: View {
|
|||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.overlay(Circle().stroke(Color(.sRGB, red: 0.05, green: 0.74, blue: 0.55, opacity: 1.0)))
|
.overlay(Circle().stroke(Color(.sRGB, red: 0.05, green: 0.74, blue: 0.55, opacity: 1.0)))
|
||||||
} else {
|
} else {
|
||||||
PlaceholderAvatarImage(firstCharacter: String(timelineItem.sender.prefix(2).suffix(1)).uppercased())
|
PlaceholderAvatarImage(firstCharacter: String(firstLetter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.frame(width: 44.0, height: 44.0)
|
.frame(width: 44.0, height: 44.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var firstLetter: String {
|
||||||
|
if let senderDisplayName = timelineItem.senderDisplayName {
|
||||||
|
return senderDisplayName.prefix(1).uppercased()
|
||||||
|
} else {
|
||||||
|
return timelineItem.senderId.prefix(2).suffix(1).uppercased()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
|
|||||||
text: "Some image",
|
text: "Some image",
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
shouldShowSenderDetails: false,
|
shouldShowSenderDetails: false,
|
||||||
sender: "Bob",
|
senderId: "Bob",
|
||||||
url: nil,
|
url: nil,
|
||||||
image: UIImage(systemName: "photo"))
|
image: UIImage(systemName: "photo"))
|
||||||
ImageRoomTimelineView(timelineItem: timelineItem)
|
ImageRoomTimelineView(timelineItem: timelineItem)
|
||||||
@@ -49,7 +49,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider {
|
|||||||
text: "Some other image",
|
text: "Some other image",
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
shouldShowSenderDetails: false,
|
shouldShowSenderDetails: false,
|
||||||
sender: "Bob",
|
senderId: "Bob",
|
||||||
url: nil,
|
url: nil,
|
||||||
image: nil)
|
image: nil)
|
||||||
ImageRoomTimelineView(timelineItem: timelineItem)
|
ImageRoomTimelineView(timelineItem: timelineItem)
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ struct TextRoomTimelineView_Previews: PreviewProvider {
|
|||||||
text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
|
text: "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic.",
|
||||||
timestamp: "Now",
|
timestamp: "Now",
|
||||||
shouldShowSenderDetails: true,
|
shouldShowSenderDetails: true,
|
||||||
sender: "Bob")
|
senderId: "Bob")
|
||||||
TextRoomTimelineView(timelineItem: timelineItem)
|
TextRoomTimelineView(timelineItem: timelineItem)
|
||||||
|
|
||||||
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString,
|
let timelineItem = TextRoomTimelineItem(id: UUID().uuidString,
|
||||||
text: "Some other text",
|
text: "Some other text",
|
||||||
timestamp: "Later",
|
timestamp: "Later",
|
||||||
shouldShowSenderDetails: true,
|
shouldShowSenderDetails: true,
|
||||||
sender: "Anne")
|
senderId: "Anne")
|
||||||
TextRoomTimelineView(timelineItem: timelineItem)
|
TextRoomTimelineView(timelineItem: timelineItem)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|||||||
Reference in New Issue
Block a user