From c04e3452ba0417e1dff2d2f0a3979c38f1e165f6 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 26 May 2022 16:04:48 +0300 Subject: [PATCH] #53 - Refactored various components: - updated the authentication coordinator to use async await - replaced the UserSession with a ClientProxy (similar to the RoomProxy) - renamed `completion` to `callback` on the coordinators and view models - renamed `viewModelResult` to `viewModelAction` - introduced back UserSession as a user specific service container --- ElementX.xcodeproj/project.pbxproj | 38 +++++- ElementX/Sources/AppCoordinator.swift | 29 +++-- .../HomeScreen/HomeScreenCoordinator.swift | 33 +++-- .../Screens/HomeScreen/HomeScreenModels.swift | 2 +- .../HomeScreen/HomeScreenViewModel.swift | 20 +-- .../HomeScreenViewModelProtocol.swift | 4 +- .../Screens/HomeScreen/View/HomeScreen.swift | 8 +- .../LoginScreen/LoginScreenCoordinator.swift | 13 +- .../LoginScreen/LoginScreenModels.swift | 2 +- .../LoginScreen/LoginScreenViewModel.swift | 4 +- .../LoginScreenViewModelProtocol.swift | 2 +- .../Screens/RoomScreen/RoomScreenModels.swift | 2 +- .../AuthenticationCoordinator.swift | 119 ++++++++---------- .../Authentication/KeychainController.swift | 19 ++- .../KeychainControllerProtocol.swift | 8 +- .../ClientProxy.swift} | 61 ++++----- .../Services/Client/ClientProxyProtocol.swift | 36 ++++++ .../Services/Media/MediaProvider.swift | 23 ++-- .../Media/MediaProviderProtocol.swift | 4 +- .../Sources/Services/Media/MediaSource.swift | 4 +- .../Services/Media/MockMediaProvider.swift | 4 +- .../Room/Members/MemberDetailsProvider.swift | 6 +- .../MemberDetailsProviderProtocol.swift | 4 +- .../Room/RoomSummary/MockRoomSummary.swift | 4 +- .../Room/RoomSummary/RoomSummary.swift | 4 +- .../Services/Session/UserSession.swift | 19 +++ .../Session/UserSessionProtocol.swift | 14 +++ .../Timeline/RoomTimelineController.swift | 8 +- .../RoomTimelineItemFactory.swift | 4 +- .../TemplateSimpleScreenCoordinator.swift | 18 ++- .../ElementX/TemplateSimpleScreenModels.swift | 2 +- .../TemplateSimpleScreenViewModel.swift | 6 +- ...emplateSimpleScreenViewModelProtocol.swift | 2 +- 33 files changed, 312 insertions(+), 214 deletions(-) rename ElementX/Sources/Services/{Authentication/UserSession.swift => Client/ClientProxy.swift} (70%) create mode 100644 ElementX/Sources/Services/Client/ClientProxyProtocol.swift create mode 100644 ElementX/Sources/Services/Session/UserSession.swift create mode 100644 ElementX/Sources/Services/Session/UserSessionProtocol.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index d6cfa85ce..92757590b 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -27,6 +27,9 @@ 13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */; }; 15D1F9C415D9C921643BA82E /* UserIndicatorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61B73D5E21F524A9BE44448D /* UserIndicatorRequest.swift */; }; 17CC4FB64F3A670F43ECBE5F /* UITestsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCA431E6EDD71F7067B5F9E7 /* UITestsRootView.swift */; }; + 189F120A283F8BC700AC0100 /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189F1209283F8BC700AC0100 /* ClientProxyProtocol.swift */; }; + 18D841AC284107E5000FD3FB /* UserSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D841AB284107E5000FD3FB /* UserSessionProtocol.swift */; }; + 18D841AE28410817000FD3FB /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D841AD28410817000FD3FB /* UserSession.swift */; }; 1999ECC6777752A2616775CF /* MemberDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A152791A2F56BD193BFE986 /* MemberDetailsProvider.swift */; }; 1A70A2199394B5EC660934A5 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = A678E40E917620059695F067 /* MatrixRustSDK */; }; 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; }; @@ -157,7 +160,7 @@ D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */; }; D94F664677C380A3CAB8D7F6 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68706A66BBA04268F7747A2F /* ActivityIndicatorPresenter.swift */; }; DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC95CD75B688E946438165 /* Coordinator.swift */; }; - DD4ADDB73E0935B74D2D18D6 /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E3FE65EE63CBA65592863C2 /* UserSession.swift */; }; + DD4ADDB73E0935B74D2D18D6 /* ClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E3FE65EE63CBA65592863C2 /* ClientProxy.swift */; }; DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63815AD6A5C306453342F2 /* ImageRoomTimelineItem.swift */; }; DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; }; DFF7D6A6C26DDD40D00AE579 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = F012CB5EE3F2B67359F6CC52 /* target.yml */; }; @@ -222,6 +225,9 @@ 13802897C7AFA360EA74C0B0 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 16DC8C5B2991724903F1FA6A /* AppIcon.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = AppIcon.pdf; sourceTree = ""; }; 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecorationTimelineItemProtocol.swift; sourceTree = ""; }; + 189F1209283F8BC700AC0100 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = ""; }; + 18D841AB284107E5000FD3FB /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = ""; }; + 18D841AD28410817000FD3FB /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; }; 193FB285430D3956B6E61E4D /* UserIndicatorViewPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorViewPresentable.swift; sourceTree = ""; }; 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = ""; }; 1A2082B5226B2A3A4D0798B6 /* LoginScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = ""; }; @@ -359,7 +365,7 @@ 8C0AA893D6F8A2F563E01BB9 /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = in; path = in.lproj/Localizable.stringsdict; sourceTree = ""; }; 8C2ABC1A9B62BDB3D216E7FD /* MemberDetailProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailProviderManager.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; - 8E3FE65EE63CBA65592863C2 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; }; + 8E3FE65EE63CBA65592863C2 /* ClientProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxy.swift; sourceTree = ""; }; 90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = ""; }; 92B61C243325DC76D3086494 /* EventBriefFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactoryProtocol.swift; sourceTree = ""; }; 938BD1FCD9E6FF3FCFA7AB4C /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-CN"; path = "zh-CN.lproj/Localizable.stringsdict"; sourceTree = ""; }; @@ -533,6 +539,8 @@ 0787F81684E503024BD0C051 /* Services */ = { isa = PBXGroup; children = ( + 18D841AA284107DC000FD3FB /* Session */, + 189F1208283F88F300AC0100 /* Client */, AAFDD509929A0CCF8BCE51EB /* Authentication */, 79E560F5113ED25D172E550C /* Media */, 40E6246F03D1FE377BC5D963 /* Room */, @@ -562,6 +570,24 @@ path = ViewModel; sourceTree = ""; }; + 189F1208283F88F300AC0100 /* Client */ = { + isa = PBXGroup; + children = ( + 8E3FE65EE63CBA65592863C2 /* ClientProxy.swift */, + 189F1209283F8BC700AC0100 /* ClientProxyProtocol.swift */, + ); + path = Client; + sourceTree = ""; + }; + 18D841AA284107DC000FD3FB /* Session */ = { + isa = PBXGroup; + children = ( + 18D841AB284107E5000FD3FB /* UserSessionProtocol.swift */, + 18D841AD28410817000FD3FB /* UserSession.swift */, + ); + path = Session; + sourceTree = ""; + }; 24FD174C31912A5FACFEAFB5 /* SupportingFiles */ = { isa = PBXGroup; children = ( @@ -921,7 +947,6 @@ 0AD575D36B9F6D1D543305D1 /* AuthenticationCoordinator.swift */, F3BC93D4555571E8B4BC47F9 /* KeychainController.swift */, 956BDA4AE16429AD015661A8 /* KeychainControllerProtocol.swift */, - 8E3FE65EE63CBA65592863C2 /* UserSession.swift */, ); path = Authentication; sourceTree = ""; @@ -1379,6 +1404,7 @@ C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */, EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */, 7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */, + 18D841AC284107E5000FD3FB /* UserSessionProtocol.swift in Sources */, 224A55EEAEECF5336B14A4A5 /* EmoteRoomMessage.swift in Sources */, 6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */, 68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */, @@ -1400,6 +1426,7 @@ 2D8A687149E46B8C8B989561 /* KeychainController.swift in Sources */, 277D2531C70F207A2F9F5906 /* KeychainControllerProtocol.swift in Sources */, 9C9E48A627C7C166084E3F5B /* LabelledActivityIndicatorView.swift in Sources */, + 18D841AE28410817000FD3FB /* UserSession.swift in Sources */, D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */, A941EAD7F407F2ED6DA54A31 /* LoginScreen.swift in Sources */, 306CC09DF101E7E9CDE79AA5 /* LoginScreenCoordinator.swift in Sources */, @@ -1481,12 +1508,13 @@ 8775F46AE3234A5A5688C19D /* UserIndicator.swift in Sources */, 7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */, 0602FA07557F580086782A9E /* UserIndicatorPresentationContext.swift in Sources */, + 189F120A283F8BC700AC0100 /* ClientProxyProtocol.swift in Sources */, 7A54700193DC1F264368746A /* UserIndicatorPresenter.swift in Sources */, 10866439ABA58CCDB5D1459D /* UserIndicatorQueue.swift in Sources */, 15D1F9C415D9C921643BA82E /* UserIndicatorRequest.swift in Sources */, C052A8CDC7A8E7A2D906674F /* UserIndicatorStore.swift in Sources */, 80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */, - DD4ADDB73E0935B74D2D18D6 /* UserSession.swift in Sources */, + DD4ADDB73E0935B74D2D18D6 /* ClientProxy.swift in Sources */, 01F4A40C1EDCEC8DC4EC9CFA /* WeakDictionary.swift in Sources */, 77E192BA943B90F9F310CA23 /* WeakDictionaryKeyReference.swift in Sources */, 50391038BC50C8ED9A4D88A0 /* WeakDictionaryReference.swift in Sources */, diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/AppCoordinator.swift index 7fc0ee640..823353dc8 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/AppCoordinator.swift @@ -6,6 +6,7 @@ // import UIKit +import Kingfisher class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { private let window: UIWindow @@ -18,8 +19,10 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { private let keychainController: KeychainControllerProtocol private let authenticationCoordinator: AuthenticationCoordinator! + private var userSession: UserSession! + private let memberDetailProviderManager: MemberDetailProviderManager - + private var indicatorPresenter: UserIndicatorTypePresenterProtocol private var loadingIndicator: UserIndicator? private var errorIndicator: UserIndicator? @@ -70,15 +73,22 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { showLoginErrorToast() } - func authenticationCoordinatorDidSetupUserSession(_ authenticationCoordinator: AuthenticationCoordinator) { + func authenticationCoordinatorDidSetupClientProxy(_ authenticationCoordinator: AuthenticationCoordinator) { + guard let clientProxy = authenticationCoordinator.clientProxy else { + fatalError("User session should be setup at this point") + } + + userSession = .init(clientProxy: clientProxy, + mediaProvider: MediaProvider(clientProxy: clientProxy, imageCache: ImageCache.default)) + presentHomeScreen() } - func authenticationCoordinatorDidTearDownUserSession(_ authenticationCoordinator: AuthenticationCoordinator) { + func authenticationCoordinatorDidTearDownClientProxy(_ authenticationCoordinator: AuthenticationCoordinator) { if let presentedCoordinator = childCoordinators.first { remove(childCoordinator: presentedCoordinator) } - + mainNavigationController.setViewControllers([splashViewController], animated: false) authenticationCoordinator.start() } @@ -89,18 +99,17 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { hideLoadingIndicator() - guard let userSession = authenticationCoordinator.userSession else { + guard let userSession = userSession else { fatalError("User session should be already setup at this point") } let parameters = HomeScreenCoordinatorParameters(userSession: userSession, - mediaProvider: userSession.mediaProvider, attributedStringBuilder: AttributedStringBuilder(), memberDetailProviderManager: memberDetailProviderManager) let coordinator = HomeScreenCoordinator(parameters: parameters) - coordinator.completion = { [weak self] result in - switch result { + coordinator.callback = { [weak self] action in + switch action { case .logout: self?.authenticationCoordinator.logout() case .selectRoom(let roomIdentifier): @@ -113,11 +122,11 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { } private func presentRoomWithIdentifier(_ roomIdentifier: String) { - guard let userSession = authenticationCoordinator.userSession else { + guard let userSession = userSession else { fatalError("User session should be already setup at this point") } - guard let roomProxy = userSession.rooms.first(where: { $0.id == roomIdentifier }) else { + guard let roomProxy = userSession.clientProxy.rooms.first(where: { $0.id == roomIdentifier }) else { MXLog.error("Invalid room identifier: \(roomIdentifier)") return } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index 6d6c49036..e92880cd7 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -18,13 +18,12 @@ import SwiftUI import Combine struct HomeScreenCoordinatorParameters { - let userSession: UserSession - let mediaProvider: MediaProviderProtocol + let userSession: UserSessionProtocol let attributedStringBuilder: AttributedStringBuilderProtocol let memberDetailProviderManager: MemberDetailProviderManager } -enum HomeScreenCoordinatorResult { +enum HomeScreenCoordinatorAction { case logout case selectRoom(roomIdentifier: String) } @@ -47,7 +46,7 @@ final class HomeScreenCoordinator: Coordinator, Presentable { // Must be used only internally var childCoordinators: [Coordinator] = [] - var completion: ((HomeScreenCoordinatorResult) -> Void)? + var callback: ((HomeScreenCoordinatorAction) -> Void)? // MARK: - Setup @@ -59,22 +58,22 @@ final class HomeScreenCoordinator: Coordinator, Presentable { let view = HomeScreen(context: viewModel.context) hostingController = UIHostingController(rootView: view) - viewModel.completion = { [weak self] result in + viewModel.callback = { [weak self] action in guard let self = self else { return } - switch result { + switch action { case .logout: - self.completion?(.logout) + self.callback?(.logout) case .selectRoom(let roomIdentifier): - self.completion?(.selectRoom(roomIdentifier: roomIdentifier)) + self.callback?(.selectRoom(roomIdentifier: roomIdentifier)) } } - parameters.userSession + parameters.userSession.clientProxy .callbacks .receive(on: DispatchQueue.main) - .sink { [weak self] result in - switch result { + .sink { [weak self] action in + switch action { case .updatedRoomsList: self?.updateRoomsList() } @@ -83,13 +82,13 @@ final class HomeScreenCoordinator: Coordinator, Presentable { updateRoomsList() Task { - if case let .success(userAvatarURL) = await parameters.userSession.loadUserAvatarURL() { - if case let .success(avatar) = await parameters.mediaProvider.loadImageFromURL(userAvatarURL) { + if case let .success(userAvatarURLString) = await parameters.userSession.clientProxy.loadUserAvatarURLString() { + if case let .success(avatar) = await parameters.userSession.mediaProvider.loadImageFromURLString(userAvatarURLString) { self.viewModel.updateWithUserAvatar(avatar) } } - if case let .success(userDisplayName) = await parameters.userSession.loadUserDisplayName() { + if case let .success(userDisplayName) = await parameters.userSession.clientProxy.loadUserDisplayName() { self.viewModel.updateWithUserDisplayName(userDisplayName) } } @@ -107,7 +106,7 @@ final class HomeScreenCoordinator: Coordinator, Presentable { // MARK: - Private func updateRoomsList() { - self.roomSummaries = parameters.userSession.rooms.compactMap { roomProxy in + roomSummaries = parameters.userSession.clientProxy.rooms.compactMap { roomProxy in guard !roomProxy.isSpace, !roomProxy.isTombstoned else { return nil } @@ -119,10 +118,10 @@ final class HomeScreenCoordinator: Coordinator, Presentable { let memberDetailProvider = parameters.memberDetailProviderManager.memberDetailProviderForRoomProxy(roomProxy) return RoomSummary(roomProxy: roomProxy, - mediaProvider: parameters.mediaProvider, + mediaProvider: parameters.userSession.mediaProvider, eventBriefFactory: EventBriefFactory(memberDetailProvider: memberDetailProvider)) } - self.viewModel.updateWithRoomList(roomSummaries) + viewModel.updateWithRoomSummaries(roomSummaries) } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 3db9154cf..ba7864b81 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -17,7 +17,7 @@ import Foundation import UIKit -enum HomeScreenViewModelResult { +enum HomeScreenViewModelAction { case logout case selectRoom(roomIdentifier: String) } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 3d5e90dae..0c894754f 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -25,13 +25,13 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol private var roomUpdateListeners = Set() - private var roomList: [RoomSummaryProtocol]? { + private var roomSummaries: [RoomSummaryProtocol]? { didSet { - self.state.isLoadingRooms = (roomList?.count ?? 0 == 0) + self.state.isLoadingRooms = (roomSummaries?.count ?? 0 == 0) } } - var completion: ((HomeScreenViewModelResult) -> Void)? + var callback: ((HomeScreenViewModelAction) -> Void)? // MARK: - Setup @@ -46,24 +46,24 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol override func process(viewAction: HomeScreenViewAction) async { switch viewAction { case .logout: - completion?(.logout) + callback?(.logout) case .loadRoomData(let roomIdentifier): loadRoomDataForIdentifier(roomIdentifier) case .selectRoom(let roomIdentifier): - completion?(.selectRoom(roomIdentifier: roomIdentifier)) + callback?(.selectRoom(roomIdentifier: roomIdentifier)) } } - func updateWithRoomList(_ roomList: [RoomSummaryProtocol]) { - self.roomList = roomList + func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol]) { + self.roomSummaries = roomSummaries - state.rooms = roomList.map { roomSummary in + state.rooms = roomSummaries.map { roomSummary in buildOrUpdateRoomFromSummary(roomSummary) } roomUpdateListeners.removeAll() - roomList.forEach({ roomSummary in + roomSummaries.forEach({ roomSummary in roomSummary.callbacks .receive(on: DispatchQueue.main) .sink { [weak self] callback in @@ -94,7 +94,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol // MARK: - Private private func loadRoomDataForIdentifier(_ roomIdentifier: String) { - guard let roomSummary = roomList?.first(where: { $0.id == roomIdentifier }) else { + guard let roomSummary = roomSummaries?.first(where: { $0.id == roomIdentifier }) else { MXLog.error("Invalid room identifier") return } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift index a7526f3b0..0d5bc342e 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift @@ -19,11 +19,11 @@ import UIKit @MainActor protocol HomeScreenViewModelProtocol { - var completion: ((HomeScreenViewModelResult) -> Void)? { get set } + var callback: ((HomeScreenViewModelAction) -> Void)? { get set } var context: HomeScreenViewModelType.Context { get } func updateWithUserAvatar(_ avatar: UIImage) func updateWithUserDisplayName(_ displayName: String) - func updateWithRoomList(_ roomList: [RoomSummaryProtocol]) + func updateWithRoomSummaries(_ roomSummaries: [RoomSummaryProtocol]) } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 106bb6aab..2f5527e81 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -176,11 +176,11 @@ struct HomeScreen_Previews: PreviewProvider { htmlBody: nil, date: .now) - let rooms = [MockRoomSummary(topic: "Topic", displayName: "Alpha"), - MockRoomSummary(displayName: "Beta"), - MockRoomSummary(displayName: "Omega", lastMessage: eventBrief)] + let roomSummaries = [MockRoomSummary(displayName: "Alpha", topic: "Topic"), + MockRoomSummary(displayName: "Beta"), + MockRoomSummary(displayName: "Omega", lastMessage: eventBrief)] - viewModel.updateWithRoomList(rooms) + viewModel.updateWithRoomSummaries(roomSummaries) if let avatarImage = UIImage(systemName: "person.fill.questionmark") { viewModel.updateWithUserAvatar(avatarImage) diff --git a/ElementX/Sources/Screens/LoginScreen/LoginScreenCoordinator.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenCoordinator.swift index 2e1e48747..082d695ef 100644 --- a/ElementX/Sources/Screens/LoginScreen/LoginScreenCoordinator.swift +++ b/ElementX/Sources/Screens/LoginScreen/LoginScreenCoordinator.swift @@ -20,6 +20,10 @@ struct LoginScreenCoordinatorParameters { } +enum LoginScreenCoordinatorAction { + case login((username: String, password: String)) +} + final class LoginScreenCoordinator: Coordinator, Presentable { // MARK: - Properties @@ -34,7 +38,7 @@ final class LoginScreenCoordinator: Coordinator, Presentable { // Must be used only internally var childCoordinators: [Coordinator] = [] - var completion: ((LoginScreenViewModelResult) -> Void)? + var callback: ((LoginScreenCoordinatorAction) -> Void)? // MARK: - Setup @@ -47,10 +51,13 @@ final class LoginScreenCoordinator: Coordinator, Presentable { loginScreenHostingController = UIHostingController(rootView: view) loginScreenHostingController.isModalInPresentation = true - loginScreenViewModel.completion = { [weak self] result in + loginScreenViewModel.callback = { [weak self] action in MXLog.debug("[LoginScreenCoordinator] LoginScreenViewModel did complete.") guard let self = self else { return } - self.completion?(result) + switch action { + case .login(let credentials): + self.callback?(.login(credentials)) + } } } diff --git a/ElementX/Sources/Screens/LoginScreen/LoginScreenModels.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenModels.swift index 46ef0e2c3..33a1cf45d 100644 --- a/ElementX/Sources/Screens/LoginScreen/LoginScreenModels.swift +++ b/ElementX/Sources/Screens/LoginScreen/LoginScreenModels.swift @@ -16,7 +16,7 @@ import Foundation -enum LoginScreenViewModelResult { +enum LoginScreenViewModelAction { case login((username: String, password: String)) } diff --git a/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift index dbd5569b6..90378bd06 100644 --- a/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift +++ b/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift @@ -26,7 +26,7 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc // MARK: Public - var completion: ((LoginScreenViewModelResult) -> Void)? + var callback: ((LoginScreenViewModelAction) -> Void)? // MARK: - Setup @@ -40,7 +40,7 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc override func process(viewAction: LoginScreenViewAction) async { switch viewAction { case .login: - completion?(.login((username: context.username, password: context.password))) + callback?(.login((username: context.username, password: context.password))) } } } diff --git a/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModelProtocol.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModelProtocol.swift index dab024124..4b2158437 100644 --- a/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModelProtocol.swift @@ -18,6 +18,6 @@ import Foundation @MainActor protocol LoginScreenViewModelProtocol { - var completion: ((LoginScreenViewModelResult) -> Void)? { get set } + var callback: ((LoginScreenViewModelAction) -> Void)? { get set } var context: LoginScreenViewModelType.Context { get } } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index 179f91429..a252fe8e4 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -16,7 +16,7 @@ import Foundation -enum RoomScreenViewModelResult { +enum RoomScreenViewModelAction { } diff --git a/ElementX/Sources/Services/Authentication/AuthenticationCoordinator.swift b/ElementX/Sources/Services/Authentication/AuthenticationCoordinator.swift index d06d4aa9d..baf6542b4 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationCoordinator.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationCoordinator.swift @@ -19,9 +19,9 @@ protocol AuthenticationCoordinatorDelegate: AnyObject { func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator) - func authenticationCoordinatorDidSetupUserSession(_ authenticationCoordinator: AuthenticationCoordinator) + func authenticationCoordinatorDidSetupClientProxy(_ authenticationCoordinator: AuthenticationCoordinator) - func authenticationCoordinatorDidTearDownUserSession(_ authenticationCoordinator: AuthenticationCoordinator) + func authenticationCoordinatorDidTearDownClientProxy(_ authenticationCoordinator: AuthenticationCoordinator) func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didFailWithError error: AuthenticationCoordinatorError) @@ -32,7 +32,7 @@ class AuthenticationCoordinator: Coordinator { private let keychainController: KeychainControllerProtocol private let navigationRouter: NavigationRouter - private(set) var userSession: UserSession? + private(set) var clientProxy: ClientProxyProtocol? var childCoordinators: [Coordinator] = [] weak var delegate: AuthenticationCoordinatorDelegate? @@ -45,13 +45,13 @@ class AuthenticationCoordinator: Coordinator { func start() { - let availableRestoreTokens = keychainController.restoreTokens() + let availableAccessTokens = keychainController.accessTokens() - guard let usernameTokenTuple = availableRestoreTokens.first else { + guard let usernameTokenTuple = availableAccessTokens.first else { startNewLoginFlow { result in switch result { case .success: - self.delegate?.authenticationCoordinatorDidSetupUserSession(self) + self.delegate?.authenticationCoordinatorDidSetupClientProxy(self) case .failure(let error): self.delegate?.authenticationCoordinator(self, didFailWithError: error) MXLog.error("Failed logging in user with error: \(error)") @@ -60,33 +60,31 @@ class AuthenticationCoordinator: Coordinator { return } - restorePreviousLogin(usernameTokenTuple) { [weak self] result in - guard let self = self else { return } - - switch result { + Task { + switch await restorePreviousLogin(usernameTokenTuple) { case .success: - self.delegate?.authenticationCoordinatorDidSetupUserSession(self) + self.delegate?.authenticationCoordinatorDidSetupClientProxy(self) case .failure(let error): self.delegate?.authenticationCoordinator(self, didFailWithError: error) MXLog.error("Failed restoring login with error: \(error)") // On any restoration failure reset the token and restart - self.keychainController.removeAllTokens() + self.keychainController.removeAllAccessTokens() self.start() } } } - - func logout() { - keychainController.removeAllTokens() - if let userIdentifier = userSession?.userIdentifier { + func logout() { + keychainController.removeAllAccessTokens() + + if let userIdentifier = clientProxy?.userIdentifier { deleteBaseDirectoryForUsername(userIdentifier) } - userSession = nil + clientProxy = nil - delegate?.authenticationCoordinatorDidTearDownUserSession(self) + delegate?.authenticationCoordinatorDidTearDownClientProxy(self) } // MARK: - Private @@ -95,17 +93,15 @@ class AuthenticationCoordinator: Coordinator { let parameters = LoginScreenCoordinatorParameters() let coordinator = LoginScreenCoordinator(parameters: parameters) - coordinator.completion = { [weak self, weak coordinator] result in + coordinator.callback = { [weak self, weak coordinator] result in guard let self = self, let coordinator = coordinator else { return } switch result { case .login(let result): - self.login(username: result.username, password: result.password) { [weak self] result in - guard let self = self else { return } - - switch result { + Task { + switch await self.login(username: result.username, password: result.password) { case .success: completion(.success(())) self.remove(childCoordinator: coordinator) @@ -113,6 +109,7 @@ class AuthenticationCoordinator: Coordinator { case .failure(let error): completion(.failure(error)) } + } } } @@ -123,71 +120,63 @@ class AuthenticationCoordinator: Coordinator { coordinator.start() } - private func login(username: String, password: String, completion: @escaping (Result) -> Void) { + private func login(username: String, password: String) async -> Result { Benchmark.startTrackingForIdentifier("Login", message: "Started new login") delegate?.authenticationCoordinatorDidStartLoading(self) - DispatchQueue.global(qos: .background).async { [weak self] in - guard let self = self else { return } - - do { - self.setupUserSessionForClient(try loginNewClient(basePath: self.baseDirectoryPathForUsername(username), - username: username, - password: password)) - - DispatchQueue.main.async { - completion(.success(())) - } - } catch { - MXLog.error("Failed logging in with error: \(error)") - - DispatchQueue.main.async { - completion(.failure(.failedLoggingIn)) - } - } + let basePath = baseDirectoryPathForUsername(username) + let loginTask = Task.detached { + try loginNewClient(basePath: basePath, + username: username, + password: password) + } + + switch await loginTask.result { + case .success(let client): + return await setupProxyForClient(client) + case .failure(let error): + MXLog.error("Failed logging in with error: \(error)") + return .failure(.failedLoggingIn) } } - private func restorePreviousLogin(_ usernameTokenTuple: (username: String, token: String), completion: @escaping (Result) -> Void) { + private func restorePreviousLogin(_ usernameTokenTuple: (username: String, accessToken: String)) async -> Result { Benchmark.startTrackingForIdentifier("Login", message: "Started restoring previous login") delegate?.authenticationCoordinatorDidStartLoading(self) - DispatchQueue.global(qos: .background).async { [weak self] in - guard let self = self else { return } - - do { - self.setupUserSessionForClient(try loginWithToken(basePath: self.baseDirectoryPathForUsername(usernameTokenTuple.username), - restoreToken: usernameTokenTuple.token)) - - DispatchQueue.main.async { - completion(.success(())) - } - } catch { - MXLog.error("Failed restoring login with error: \(error)") - - DispatchQueue.main.async { - completion(.failure(.failedRestoringLogin)) - } - } + let basePath = baseDirectoryPathForUsername(usernameTokenTuple.username) + let loginTask = Task.detached { + try loginWithToken(basePath: basePath, + restoreToken: usernameTokenTuple.accessToken) + } + + switch await loginTask.result { + case .success(let client): + return await setupProxyForClient(client) + case .failure(let error): + MXLog.error("Failed restoring login with error: \(error)") + return .failure(.failedRestoringLogin) } } - private func setupUserSessionForClient(_ client: Client) { + private func setupProxyForClient(_ client: Client) async -> Result { Benchmark.endTrackingForIdentifier("Login", message: "Finished login") do { - let restoreToken = try client.restoreToken() + let accessToken = try client.restoreToken() let userId = try client.userId() - keychainController.setRestoreToken(restoreToken, forUsername: userId) + keychainController.setAccessToken(accessToken, forUsername: userId) } catch { - delegate?.authenticationCoordinator(self, didFailWithError: .failedSettingUpSession) MXLog.error("Failed setting up user session with error: \(error)") + return .failure(.failedSettingUpSession) } - userSession = UserSession(client: client) + clientProxy = ClientProxy(client: client) + + return .success(()) } private func baseDirectoryPathForUsername(_ username: String) -> String { diff --git a/ElementX/Sources/Services/Authentication/KeychainController.swift b/ElementX/Sources/Services/Authentication/KeychainController.swift index 66c9301ef..efb3b9b9d 100644 --- a/ElementX/Sources/Services/Authentication/KeychainController.swift +++ b/ElementX/Sources/Services/Authentication/KeychainController.swift @@ -9,26 +9,21 @@ import Foundation import KeychainAccess class KeychainController: KeychainControllerProtocol { - - struct Constants { - static let restoreTokenGroupKey = "restoreTokens" - } - private let keychain: Keychain init(identifier: String) { keychain = Keychain(service: identifier) } - func setRestoreToken(_ token: String, forUsername username: String) { + func setAccessToken(_ accessToken: String, forUsername username: String) { do { - try keychain.set(token, key: username) + try keychain.set(accessToken, key: username) } catch { MXLog.error("Failed storing user restore token with error: \(error)") } } - func restoreTokenForUsername(_ username: String) -> String? { + func accessTokenForUsername(_ username: String) -> String? { do { return try keychain.get(username) } catch { @@ -37,17 +32,17 @@ class KeychainController: KeychainControllerProtocol { } } - func restoreTokens() -> [(username: String, token: String)] { + func accessTokens() -> [(username: String, accessToken: String)] { keychain.allKeys().compactMap { username in - guard let token = restoreTokenForUsername(username) else { + guard let accessToken = accessTokenForUsername(username) else { return nil } - return (username, token) + return (username, accessToken) } } - func removeAllTokens() { + func removeAllAccessTokens() { do { try keychain.removeAll() } catch { diff --git a/ElementX/Sources/Services/Authentication/KeychainControllerProtocol.swift b/ElementX/Sources/Services/Authentication/KeychainControllerProtocol.swift index 21693394a..52cb8b3be 100644 --- a/ElementX/Sources/Services/Authentication/KeychainControllerProtocol.swift +++ b/ElementX/Sources/Services/Authentication/KeychainControllerProtocol.swift @@ -8,8 +8,8 @@ import Foundation protocol KeychainControllerProtocol { - func setRestoreToken(_ token: String, forUsername username: String) - func restoreTokenForUsername(_ username: String) -> String? - func restoreTokens() -> [(username: String, token: String)] - func removeAllTokens() + func setAccessToken(_ accessToken: String, forUsername username: String) + func accessTokenForUsername(_ username: String) -> String? + func accessTokens() -> [(username: String, accessToken: String)] + func removeAllAccessTokens() } diff --git a/ElementX/Sources/Services/Authentication/UserSession.swift b/ElementX/Sources/Services/Client/ClientProxy.swift similarity index 70% rename from ElementX/Sources/Services/Authentication/UserSession.swift rename to ElementX/Sources/Services/Client/ClientProxy.swift index 2cfa78b1d..c116537e7 100644 --- a/ElementX/Sources/Services/Authentication/UserSession.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -1,5 +1,5 @@ // -// UserSession.swift +// ClientProxy.swift // ElementX // // Created by Stefan Ceriu on 14.02.2022. @@ -9,53 +9,39 @@ import Foundation import MatrixRustSDK import Combine import UIKit -import Kingfisher -enum UserSessionCallback { - case updatedRoomsList -} - -enum UserSessionError: Error { - case failedRetrievingAvatarURL - case failedRetrievingDisplayName -} - -private class WeakUserSessionWrapper: ClientDelegate { - private weak var userSession: UserSession? +private class WeakClientProxyWrapper: ClientDelegate { + private weak var clientProxy: ClientProxy? - init(userSession: UserSession) { - self.userSession = userSession + init(clientProxy: ClientProxy) { + self.clientProxy = clientProxy } - @MainActor func didReceiveSyncUpdate() { - userSession?.didReceiveSyncUpdate() + func didReceiveSyncUpdate() { + clientProxy?.didReceiveSyncUpdate() } } -@MainActor -class UserSession { +class ClientProxy: ClientProxyProtocol { private let client: Client private(set) var rooms: [RoomProxy] = [] { didSet { - self.callbacks.send(.updatedRoomsList) + callbacks.send(.updatedRoomsList) } } - let mediaProvider: MediaProviderProtocol - deinit { client.setDelegate(delegate: nil) } - let callbacks = PassthroughSubject() + let callbacks = PassthroughSubject() init(client: Client) { self.client = client - self.mediaProvider = MediaProvider(client: client, imageCache: ImageCache.default) - client.setDelegate(delegate: WeakUserSessionWrapper(userSession: self)) + client.setDelegate(delegate: WeakClientProxyWrapper(clientProxy: self)) Benchmark.startTrackingForIdentifier("ClientSync", message: "Started sync.") client.startSync() @@ -72,8 +58,8 @@ class UserSession { } } - func loadUserDisplayName() async -> Result { - await Task.detached { () -> Result in + func loadUserDisplayName() async -> Result { + await Task.detached { () -> Result in do { let displayName = try self.client.displayName() return .success(displayName) @@ -85,8 +71,8 @@ class UserSession { .value } - func loadUserAvatarURL() async -> Result { - await Task.detached { () -> Result in + func loadUserAvatarURLString() async -> Result { + await Task.detached { () -> Result in do { let avatarURL = try self.client.avatarUrl() return .success(avatarURL) @@ -97,9 +83,18 @@ class UserSession { .value } - // MARK: ClientDelegate + func mediaSourceForURLString(_ urlString: String) -> MatrixRustSDK.MediaSource { + MatrixRustSDK.mediaSourceFromUrl(url: urlString) + } - func didReceiveSyncUpdate() { + func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) throws -> Data { + let bytes = try client.getMediaContent(source: source) + return Data(bytes: bytes, count: bytes.count) + } + + // MARK: Private + + fileprivate func didReceiveSyncUpdate() { Benchmark.logElapsedDurationForIdentifier("ClientSync", message: "Received sync update") Task.detached { @@ -107,10 +102,8 @@ class UserSession { } } - // MARK: Private - private func updateRooms() async { - var currentRooms = self.rooms + var currentRooms = rooms Benchmark.startTrackingForIdentifier("ClientRooms", message: "Fetching available rooms") let sdkRooms = client.rooms() Benchmark.endTrackingForIdentifier("ClientRooms", message: "Retrieved \(sdkRooms.count) rooms") diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift new file mode 100644 index 000000000..38b13caea --- /dev/null +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -0,0 +1,36 @@ +// +// ClientProxyProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 26/05/2022. +// Copyright © 2022 element.io. All rights reserved. +// + +import Foundation +import MatrixRustSDK +import Combine + +enum ClientProxyCallback { + case updatedRoomsList +} + +enum ClientProxyError: Error { + case failedRetrievingAvatarURL + case failedRetrievingDisplayName +} + +protocol ClientProxyProtocol { + var callbacks: PassthroughSubject { get } + + var userIdentifier: String { get } + + var rooms: [RoomProxy] { get } + + func loadUserDisplayName() async -> Result + + func loadUserAvatarURLString() async -> Result + + func mediaSourceForURLString(_ urlString: String) -> MatrixRustSDK.MediaSource + + func loadMediaContentForSource(_ source: MatrixRustSDK.MediaSource) throws -> Data +} diff --git a/ElementX/Sources/Services/Media/MediaProvider.swift b/ElementX/Sources/Services/Media/MediaProvider.swift index 451b2ec76..570643d66 100644 --- a/ElementX/Sources/Services/Media/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/MediaProvider.swift @@ -7,16 +7,15 @@ // import UIKit -import MatrixRustSDK import Kingfisher struct MediaProvider: MediaProviderProtocol { - private let client: Client + private let clientProxy: ClientProxyProtocol private let imageCache: Kingfisher.ImageCache private let processingQueue: DispatchQueue - init(client: Client, imageCache: Kingfisher.ImageCache) { - self.client = client + init(clientProxy: ClientProxyProtocol, imageCache: Kingfisher.ImageCache) { + self.clientProxy = clientProxy self.imageCache = imageCache self.processingQueue = DispatchQueue(label: "MediaProviderProcessingQueue", attributes: .concurrent) } @@ -29,18 +28,18 @@ struct MediaProvider: MediaProviderProtocol { return imageCache.retrieveImageInMemoryCache(forKey: source.underlyingSource.url(), options: nil) } - func imageFromURL(_ url: String?) -> UIImage? { - guard let url = url else { + func imageFromURLString(_ urlString: String?) -> UIImage? { + guard let urlString = urlString else { return nil } - return imageFromSource(MediaSource(source: mediaSourceFromUrl(url: url))) + return imageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString))) } - func loadImageFromURL(_ url: String) async -> Result { - await loadImageFromSource(MediaSource(source: mediaSourceFromUrl(url: url))) + func loadImageFromURLString(_ urlString: String) async -> Result { + await loadImageFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString))) } - + func loadImageFromSource(_ source: MediaSource) async -> Result { if let image = imageFromSource(source) { return .success(image) @@ -59,9 +58,9 @@ struct MediaProvider: MediaProviderProtocol { return await Task.detached { () -> Result in do { - let imageData = try client.getMediaContent(source: source.underlyingSource) + let imageData = try clientProxy.loadMediaContentForSource(source.underlyingSource) - guard let image = UIImage(data: Data(bytes: imageData, count: imageData.count)) else { + guard let image = UIImage(data: imageData) else { MXLog.error("Invalid image data") return .failure(.invalidImageData) } diff --git a/ElementX/Sources/Services/Media/MediaProviderProtocol.swift b/ElementX/Sources/Services/Media/MediaProviderProtocol.swift index ed9b66d99..2914d6094 100644 --- a/ElementX/Sources/Services/Media/MediaProviderProtocol.swift +++ b/ElementX/Sources/Services/Media/MediaProviderProtocol.swift @@ -20,7 +20,7 @@ protocol MediaProviderProtocol { func loadImageFromSource(_ source: MediaSource) async -> Result - func imageFromURL(_ url: String?) -> UIImage? + func imageFromURLString(_ urlString: String?) -> UIImage? - func loadImageFromURL(_ url: String) async -> Result + func loadImageFromURLString(_ urlString: String) async -> Result } diff --git a/ElementX/Sources/Services/Media/MediaSource.swift b/ElementX/Sources/Services/Media/MediaSource.swift index c6f37c161..44e0cfd32 100644 --- a/ElementX/Sources/Services/Media/MediaSource.swift +++ b/ElementX/Sources/Services/Media/MediaSource.swift @@ -13,11 +13,11 @@ struct MediaSource: Equatable { let underlyingSource: MatrixRustSDK.MediaSource init(source: MatrixRustSDK.MediaSource) { - self.underlyingSource = source + underlyingSource = source } init(urlString: String) { - self.underlyingSource = mediaSourceFromUrl(url: urlString) + underlyingSource = MatrixRustSDK.mediaSourceFromUrl(url: urlString) } // MARK: - Equatable diff --git a/ElementX/Sources/Services/Media/MockMediaProvider.swift b/ElementX/Sources/Services/Media/MockMediaProvider.swift index c3e6230e5..ae85cbe49 100644 --- a/ElementX/Sources/Services/Media/MockMediaProvider.swift +++ b/ElementX/Sources/Services/Media/MockMediaProvider.swift @@ -19,11 +19,11 @@ struct MockMediaProvider: MediaProviderProtocol { return .failure(.failedRetrievingImage) } - func imageFromURL(_ url: String?) -> UIImage? { + func imageFromURLString(_ urlString: String?) -> UIImage? { return nil } - func loadImageFromURL(_ url: String) async -> Result { + func loadImageFromURLString(_ urlString: String) async -> Result { return .failure(.failedRetrievingImage) } } diff --git a/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift b/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift index 201a99cc8..7c0e531ae 100644 --- a/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift +++ b/ElementX/Sources/Services/Room/Members/MemberDetailsProvider.swift @@ -17,12 +17,12 @@ class MemberDetailProvider: MemberDetailProviderProtocol { self.roomProxy = roomProxy } - func avatarURLForUserId(_ userId: String) -> String? { + func avatarURLStringForUserId(_ userId: String) -> String? { memberAvatars[userId] } - func loadAvatarURLForUserId(_ userId: String) async -> Result { - if let avatarURL = avatarURLForUserId(userId) { + func loadAvatarURLStringForUserId(_ userId: String) async -> Result { + if let avatarURL = avatarURLStringForUserId(userId) { return .success(avatarURL) } diff --git a/ElementX/Sources/Services/Room/Members/MemberDetailsProviderProtocol.swift b/ElementX/Sources/Services/Room/Members/MemberDetailsProviderProtocol.swift index 911a4f051..ceccd675c 100644 --- a/ElementX/Sources/Services/Room/Members/MemberDetailsProviderProtocol.swift +++ b/ElementX/Sources/Services/Room/Members/MemberDetailsProviderProtocol.swift @@ -16,8 +16,8 @@ enum MemberDetailProviderError: Error { @MainActor protocol MemberDetailProviderProtocol { - func avatarURLForUserId(_ userId: String) -> String? - func loadAvatarURLForUserId(_ userId: String) async -> Result + func avatarURLStringForUserId(_ userId: String) -> String? + func loadAvatarURLStringForUserId(_ userId: String) async -> Result func displayNameForUserId(_ userId: String) -> String? func loadDisplayNameForUserId(_ userId: String) async -> Result diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift index f9c6b4063..8c6a67d60 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift @@ -14,6 +14,8 @@ struct MockRoomSummary: RoomSummaryProtocol { var name: String? + var displayName: String? + var topic: String? var isDirect: Bool = false @@ -24,8 +26,6 @@ struct MockRoomSummary: RoomSummaryProtocol { var isTombstoned: Bool = false - var displayName: String? - var lastMessage: EventBrief? var avatar: UIImage? diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift index 046a1f4ba..f23717807 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift @@ -124,11 +124,11 @@ class RoomSummary: RoomSummaryProtocol { } private func loadAvatar() async { - guard let avatarURL = roomProxy.avatarURL else { + guard let avatarURLString = roomProxy.avatarURL else { return } - switch await mediaProvider.loadImageFromURL(avatarURL) { + switch await mediaProvider.loadImageFromURLString(avatarURLString) { case .success(let avatar): self.avatar = avatar case .failure(let error): diff --git a/ElementX/Sources/Services/Session/UserSession.swift b/ElementX/Sources/Services/Session/UserSession.swift new file mode 100644 index 000000000..3cab1f63c --- /dev/null +++ b/ElementX/Sources/Services/Session/UserSession.swift @@ -0,0 +1,19 @@ +// +// UserSession.swift +// ElementX +// +// Created by Stefan Ceriu on 27/05/2022. +// Copyright © 2022 element.io. All rights reserved. +// + +import Foundation + +class UserSession: UserSessionProtocol { + let clientProxy: ClientProxyProtocol + let mediaProvider: MediaProviderProtocol + + init(clientProxy: ClientProxyProtocol, mediaProvider: MediaProviderProtocol) { + self.clientProxy = clientProxy + self.mediaProvider = mediaProvider + } +} diff --git a/ElementX/Sources/Services/Session/UserSessionProtocol.swift b/ElementX/Sources/Services/Session/UserSessionProtocol.swift new file mode 100644 index 000000000..ebbe6f183 --- /dev/null +++ b/ElementX/Sources/Services/Session/UserSessionProtocol.swift @@ -0,0 +1,14 @@ +// +// UserSessionProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 27/05/2022. +// Copyright © 2022 element.io. All rights reserved. +// + +import Foundation + +protocol UserSessionProtocol { + var clientProxy: ClientProxyProtocol { get } + var mediaProvider: MediaProviderProtocol { get } +} diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index 5f5d71f13..94ce2aaa1 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -157,13 +157,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return } - switch await memberDetailProvider.loadAvatarURLForUserId(timelineItem.senderId) { - case .success(let avatarURL): - guard let avatarURL = avatarURL else { + switch await memberDetailProvider.loadAvatarURLStringForUserId(timelineItem.senderId) { + case .success(let avatarURLString): + guard let avatarURLString = avatarURLString else { return } - switch await mediaProvider.loadImageFromURL(avatarURL) { + switch await mediaProvider.loadImageFromURLString(avatarURLString) { case .success(let avatar): guard let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }), var item = timelineItems[index] as? EventBasedTimelineItemProtocol else { diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 65ec4dbd2..5ce473070 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -24,8 +24,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { func buildTimelineItemFor(message: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol { let displayName = memberDetailProvider.displayNameForUserId(message.sender) - let avatarURL = memberDetailProvider.avatarURLForUserId(message.sender) - let avatarImage = mediaProvider.imageFromURL(avatarURL) + let avatarURL = memberDetailProvider.avatarURLStringForUserId(message.sender) + let avatarImage = mediaProvider.imageFromURLString(avatarURL) switch message { case let message as TextRoomMessage: diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenCoordinator.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenCoordinator.swift index e68de349c..10fcf0c85 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenCoordinator.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenCoordinator.swift @@ -20,6 +20,11 @@ struct TemplateSimpleScreenCoordinatorParameters { let promptType: TemplateSimpleScreenPromptType } +enum TemplateSimpleScreenCoordinatorAction { + case accept + case cancel +} + final class TemplateSimpleScreenCoordinator: Coordinator, Presentable { // MARK: - Properties @@ -37,7 +42,7 @@ final class TemplateSimpleScreenCoordinator: Coordinator, Presentable { // Must be used only internally var childCoordinators: [Coordinator] = [] - var completion: ((TemplateSimpleScreenViewModelResult) -> Void)? + var callback: ((TemplateSimpleScreenCoordinatorAction) -> Void)? // MARK: - Setup @@ -56,10 +61,15 @@ final class TemplateSimpleScreenCoordinator: Coordinator, Presentable { func start() { MXLog.debug("[TemplateSimpleScreenCoordinator] did start.") - templateSimpleScreenViewModel.completion = { [weak self] result in + templateSimpleScreenViewModel.callback = { [weak self] action in guard let self = self else { return } - MXLog.debug("[TemplateSimpleScreenCoordinator] TemplateSimpleScreenViewModel did complete with result: \(result).") - self.completion?(result) + MXLog.debug("[TemplateSimpleScreenCoordinator] TemplateSimpleScreenViewModel did complete with result: \(action).") + switch action { + case .accept: + self.callback?(.accept) + case .cancel: + self.callback?(.cancel) + } } } diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenModels.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenModels.swift index dd641ad5b..a272b6363 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenModels.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenModels.swift @@ -47,7 +47,7 @@ extension TemplateSimpleScreenPromptType: Identifiable, CaseIterable { // MARK: View model -enum TemplateSimpleScreenViewModelResult { +enum TemplateSimpleScreenViewModelAction { case accept case cancel } diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModel.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModel.swift index 005fe63c7..a35db040a 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModel.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModel.swift @@ -26,7 +26,7 @@ class TemplateSimpleScreenViewModel: TemplateSimpleScreenViewModelType, Template // MARK: Public - var completion: ((TemplateSimpleScreenViewModelResult) -> Void)? + var callback: ((TemplateSimpleScreenViewModelAction) -> Void)? // MARK: - Setup @@ -39,9 +39,9 @@ class TemplateSimpleScreenViewModel: TemplateSimpleScreenViewModelType, Template override func process(viewAction: TemplateSimpleScreenViewAction) async { switch viewAction { case .accept: - completion?(.accept) + callback?(.accept) case .cancel: - completion?(.cancel) + callback?(.cancel) case .incrementCount: state.count += 1 case .decrementCount: diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModelProtocol.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModelProtocol.swift index 02b3c2ae1..e16081823 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModelProtocol.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModelProtocol.swift @@ -18,6 +18,6 @@ import Foundation @MainActor protocol TemplateSimpleScreenViewModelProtocol { - var completion: ((TemplateSimpleScreenViewModelResult) -> Void)? { get set } + var callback: ((TemplateSimpleScreenViewModelAction) -> Void)? { get set } var context: TemplateSimpleScreenViewModelType.Context { get } }