diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index 21fc8039d..38bbe20e7 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -54,11 +54,7 @@ final class HomeScreenCoordinator: Coordinator, Presentable { init(parameters: HomeScreenCoordinatorParameters) { self.parameters = parameters - let userDisplayName = parameters.userSession.userDisplayName ?? parameters.userSession.userIdentifier - viewModel = HomeScreenViewModel(userDisplayName: userDisplayName, - userAvatarURL: parameters.userSession.userAvatarURL, - mediaProvider: parameters.mediaProvider, - attributedStringBuilder: parameters.attributedStringBuilder) + viewModel = HomeScreenViewModel(attributedStringBuilder: parameters.attributedStringBuilder) let view = HomeScreen(context: viewModel.context) hostingController = UIHostingController(rootView: view) @@ -82,6 +78,22 @@ final class HomeScreenCoordinator: Coordinator, Presentable { }.store(in: &cancellables) updateRoomsList() + + parameters.userSession.userAvatarURL { [weak self] result in + if case let .success(avatarURL) = result { + self?.parameters.mediaProvider.loadImageFromURL(avatarURL) { result in + if case let .success(avatar) = result { + self?.viewModel.updateWithUserAvatar(avatar) + } + } + } + } + + parameters.userSession.userDisplayName { [weak self] result in + if case let .success(displayName) = result { + self?.viewModel.updateWithUserDisplayName(displayName) + } + } } // MARK: - Public diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 9b81dacf2..3db9154cf 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -29,7 +29,7 @@ enum HomeScreenViewAction { } struct HomeScreenViewState: BindableState { - let userDisplayName: String + var userDisplayName: String? var userAvatar: UIImage? var rooms: [HomeScreenRoom] = [] diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index deea95f63..a8e72c352 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -24,7 +24,6 @@ typealias HomeScreenViewModelType = StateStoreViewModel() @@ -34,27 +33,15 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol self.state.isLoadingRooms = (roomList?.count ?? 0 == 0) } } - + var completion: ((HomeScreenViewModelResult) -> Void)? // MARK: - Setup - init(userDisplayName: String, - userAvatarURL: String?, - mediaProvider: MediaProviderProtocol, - attributedStringBuilder: AttributedStringBuilderProtocol) { - self.mediaProvider = mediaProvider + init(attributedStringBuilder: AttributedStringBuilderProtocol) { self.attributedStringBuilder = attributedStringBuilder - 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 - } - } - } + super.init(initialViewState: HomeScreenViewState(isLoadingRooms: true)) } // MARK: - Public @@ -97,10 +84,14 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } - func updateWithUserAvatar(_ avatar: UIImage?) { + func updateWithUserAvatar(_ avatar: UIImage) { self.state.userAvatar = avatar } + func updateWithUserDisplayName(_ displayName: String) { + self.state.userDisplayName = displayName + } + // MARK: - Private private func loadRoomDataForIdentifier(_ roomIdentifier: String) { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift index 1fdeaaafb..3e3e95d9a 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift @@ -22,5 +22,7 @@ protocol HomeScreenViewModelProtocol { var context: HomeScreenViewModelType.Context { get } + func updateWithUserAvatar(_ avatar: UIImage) + func updateWithUserDisplayName(_ displayName: String) func updateWithRoomList(_ roomList: [RoomSummaryProtocol]) } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 1a7409e8c..ca87bece0 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -73,18 +73,32 @@ struct HomeScreen: View { .toolbar { ToolbarItem(placement: .navigationBarLeading) { HStack { - if let avatar = context.viewState.userAvatar { - Image(uiImage: avatar) - .resizable() - .scaledToFill() - .frame(width: 40, height: 40, alignment: .center) - .mask(Circle()) + ZStack { + if let avatar = context.viewState.userAvatar { + Image(uiImage: avatar) + .resizable() + .scaledToFill() + .frame(width: 40, height: 40, alignment: .center) + .mask(Circle()) + } else { + EmptyView() + } } - Text("Hello, \(context.viewState.userDisplayName)!") - .font(.subheadline) - .fontWeight(.bold) + .animation(.easeInOut, value: context.viewState.userAvatar) + .transition(.opacity) + + ZStack { + if let displayName = context.viewState.userDisplayName { + Text("Hello, \(displayName)!") + .font(.subheadline) + .fontWeight(.bold) + } else { + EmptyView() + } + } + .animation(.easeInOut, value: context.viewState.userDisplayName) + .transition(.opacity) } - .animation(.easeInOut, value: context.viewState.userAvatar) } ToolbarItem(placement: .navigationBarTrailing) { Button("Logout") { @@ -153,10 +167,7 @@ struct RoomCell: View { struct HomeScreen_Previews: PreviewProvider { static var previews: some View { - let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed", - userAvatarURL: nil, - mediaProvider: MockMediaProvider(), - attributedStringBuilder: AttributedStringBuilder()) + let viewModel = HomeScreenViewModel(attributedStringBuilder: AttributedStringBuilder()) let eventBrief = EventBrief(eventId: "id", senderId: "senderId", @@ -171,7 +182,9 @@ struct HomeScreen_Previews: PreviewProvider { viewModel.updateWithRoomList(rooms) - viewModel.updateWithUserAvatar(UIImage(systemName: "person.fill.questionmark")) + if let avatarImage = UIImage(systemName: "person.fill.questionmark") { + viewModel.updateWithUserAvatar(avatarImage) + } return HomeScreen(context: viewModel.context) } diff --git a/ElementX/Sources/Services/Authentication/UserSession.swift b/ElementX/Sources/Services/Authentication/UserSession.swift index 0e85cefae..a29c2fe91 100644 --- a/ElementX/Sources/Services/Authentication/UserSession.swift +++ b/ElementX/Sources/Services/Authentication/UserSession.swift @@ -15,6 +15,11 @@ enum UserSessionCallback { case updatedRoomsList } +enum UserSessionError: Error { + case failedRetrievingAvatarURL + case failedRetrievingDisplayName +} + private class WeakUserSessionWrapper: ClientDelegate { private weak var userSession: UserSession? @@ -70,21 +75,35 @@ class UserSession: ClientDelegate { } } - var userDisplayName: String? { - do { - return try client.displayName() - } catch { - MXLog.error("Failed retrieving the user's display name with error: \(error)") - return nil + func userDisplayName(_ completion: @escaping (Result) -> Void) { + processingQueue.async { + do { + let displayName = try self.client.displayName() + + DispatchQueue.main.async { + completion(.success(displayName)) + } + } catch { + DispatchQueue.main.async { + completion(.failure(UserSessionError.failedRetrievingDisplayName)) + } + } } } - var userAvatarURL: String? { - do { - return try client.avatarUrl() - } catch { - MXLog.error("Failed retrieving the user's avatar URL with error: \(error)") - return nil + func userAvatarURL(_ completion: @escaping (Result) -> Void) { + processingQueue.async { + do { + let displayName = try self.client.avatarUrl() + + DispatchQueue.main.async { + completion(.success(displayName)) + } + } catch { + DispatchQueue.main.async { + completion(.failure(UserSessionError.failedRetrievingDisplayName)) + } + } } }