From 0c3e2f4a6ccb0bd1a51a356f90a404fdda3d0485 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 25 May 2022 16:51:48 +0300 Subject: [PATCH] vector-im/element-x-ios/issues/53 - Various tweaks following code review --- ElementX.xcodeproj/project.pbxproj | 12 +++ .../ViewModel/StateStoreViewModel.swift | 5 +- .../HomeScreen/HomeScreenViewModel.swift | 9 +-- .../LoginScreen/LoginScreenViewModel.swift | 5 +- .../RoomScreen/RoomScreenViewModel.swift | 81 +++++++++---------- .../Services/Authentication/UserSession.swift | 14 ++-- .../Services/Media/MediaProvider.swift | 3 +- .../Services/Media/MockMediaProvider.swift | 8 -- .../Services/Room/RoomMessageFactory.swift | 2 +- .../Room/RoomMessageFactoryProtocol.swift | 14 ++++ .../Sources/Services/Room/RoomProxy.swift | 25 +++--- .../Room/RoomSummary/EventBriefFactory.swift | 4 +- .../EventBriefFactoryProtocol.swift | 2 +- .../Room/RoomSummary/MockRoomSummary.swift | 2 +- .../Room/RoomSummary/RoomSummary.swift | 18 ++--- .../Timeline/RoomTimelineController.swift | 30 +++---- .../RoomTimelineItemFactory.swift | 13 ++- .../RoomTimelineItemFactoryProtocol.swift | 14 ++++ .../RoomTimelineViewFactory.swift | 7 +- .../RoomTimelineViewFactoryProtocol.swift | 14 ++++ ElementX/Sources/UITestsAppCoordinator.swift | 2 +- .../TemplateSimpleScreenViewModel.swift | 9 +-- .../TemplateSimpleScreenViewModelTests.swift | 5 +- 23 files changed, 172 insertions(+), 126 deletions(-) create mode 100644 ElementX/Sources/Services/Room/RoomMessageFactoryProtocol.swift create mode 100644 ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift create mode 100644 ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactoryProtocol.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index ac448a671..d6cfa85ce 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 1151DCC5EC2C6585826545EC /* UserIndicatorPresenterSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B695D0D12086158BAD1D9859 /* UserIndicatorPresenterSpy.swift */; }; 1281625B25371BE53D36CB3A /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */; }; 12F70C493FB69F4D7E9A37EA /* NavigationRouterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */; }; + 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 */; }; 1999ECC6777752A2616775CF /* MemberDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A152791A2F56BD193BFE986 /* MemberDetailsProvider.swift */; }; @@ -35,6 +36,7 @@ 24906A1E82D0046655958536 /* MessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18CF12478983A5EB390FB26 /* MessageComposer.swift */; }; 277D2531C70F207A2F9F5906 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 956BDA4AE16429AD015661A8 /* KeychainControllerProtocol.swift */; }; 2797C9D9BA642370F1C85D78 /* Untranslated.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = F75DF9500D69A3AAF8339E69 /* Untranslated.stringsdict */; }; + 297CD0A27C87B0C50FF192EE /* RoomTimelineViewFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */; }; 29AEE68A604940180AB9EBFF /* MockRoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BDAC8895AB2B77B47703AE /* MockRoomSummary.swift */; }; 2BA59D0AEFB4B82A2EC2A326 /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = A981A4CA233FB5C13B9CA690 /* SwiftyBeaver */; }; 2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; }; @@ -110,6 +112,7 @@ 8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */; }; 8BBD3AA589DEE02A1B0923B2 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F49CDE349C490D617332770 /* NoticeRoomTimelineItem.swift */; }; 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; + 8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */; }; 90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; }; 90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; }; 93BA4A81B6D893271101F9F0 /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = FD43A50D9B75C9D6D30F006B /* SwiftyBeaver */; }; @@ -332,6 +335,7 @@ 7B04BD3874D736127A8156B8 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 7BDF6A69C2BB99535193E554 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = ""; }; 7D0CBC76C80E04345E11F2DB /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; + 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactoryProtocol.swift; sourceTree = ""; }; 7DA80FADE73CDF01E96F5B8E /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = sq.lproj/Localizable.strings; sourceTree = ""; }; 7DDBF99755A9008CF8C8499E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 7E154FEA1E6FE964D3DF7859 /* fy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fy; path = fy.lproj/Localizable.strings; sourceTree = ""; }; @@ -364,6 +368,7 @@ 956BDA4AE16429AD015661A8 /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; 95CC95CD75B688E946438165 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsAppCoordinator.swift; sourceTree = ""; }; + 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactoryProtocol.swift; sourceTree = ""; }; 9772C1D2223108EB3131AEE4 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = ""; }; 97F893DBB5F88D746C6DCDE5 /* ku */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ku; path = ku.lproj/Localizable.strings; sourceTree = ""; }; 997783054A2E95F9E624217E /* kaa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kaa; path = kaa.lproj/Localizable.strings; sourceTree = ""; }; @@ -461,6 +466,7 @@ EBE5502760CF6CA2D7201883 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = ""; }; ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; EE8BCD14EFED23459A43FDFF /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; + EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineViewFactoryProtocol.swift; sourceTree = ""; }; EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; F012CB5EE3F2B67359F6CC52 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = ""; }; F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -620,6 +626,7 @@ children = ( 3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */, FA154570F693D93513E584C1 /* RoomMessageFactory.swift */, + 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */, A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */, 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */, 33996F58948B54839D653EC1 /* Members */, @@ -849,8 +856,10 @@ 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */, 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */, 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */, + 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */, ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */, 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */, + EEE384418EB1FEDFA62C9CD0 /* RoomTimelineViewFactoryProtocol.swift */, ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */, 75D1D02F7F3AC1122FCFB4F3 /* Items */, ); @@ -1423,6 +1432,7 @@ BF35062D06888FA80BD139FF /* Presentable.swift in Sources */, 53B9C2240C2F5533246EE230 /* RectangleToastView.swift in Sources */, FE79E2BCCF69E8BF4D21E15A /* RoomMessageFactory.swift in Sources */, + 8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */, D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */, 4FC1EFE4968A259CBBACFAFB /* RoomProxy.swift in Sources */, FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */, @@ -1436,10 +1446,12 @@ 78B71D53C1FC55FB7A9B75F0 /* RoomTimelineController.swift in Sources */, 9B8DE1D424E37581C7D99CCC /* RoomTimelineControllerProtocol.swift in Sources */, 4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */, + 13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */, 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */, 9BD3A773186291560DF92B62 /* RoomTimelineProvider.swift in Sources */, 77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */, 5D430CDE11EAC3E8E6B80A66 /* RoomTimelineViewFactory.swift in Sources */, + 297CD0A27C87B0C50FF192EE /* RoomTimelineViewFactoryProtocol.swift in Sources */, CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */, 7F19E97E7985F518C9018B83 /* RootRouter.swift in Sources */, 2C0CE61E5DC177938618E0B1 /* RootRouterType.swift in Sources */, diff --git a/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift b/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift index 24bed91aa..0b38a4404 100644 --- a/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift +++ b/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift @@ -99,14 +99,15 @@ class StateStoreViewModel { self.context = Context(initialViewState: initialViewState) self.context.viewActions.sink { [weak self] action in guard let self = self else { return } - self.process(viewAction: action) + + Task { await self.process(viewAction: action) } } .store(in: &cancellables) } /// Override to handles incoming `ViewAction`s from the `ViewModel`. /// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation. - func process(viewAction: ViewAction) { + func process(viewAction: ViewAction) async { // Default implementation, -no-op } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index c28c59926..3d5e90dae 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -17,8 +17,7 @@ import SwiftUI import Combine -typealias HomeScreenViewModelType = StateStoreViewModel +typealias HomeScreenViewModelType = StateStoreViewModel class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol { @@ -44,7 +43,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol // MARK: - Public - override func process(viewAction: HomeScreenViewAction) { + override func process(viewAction: HomeScreenViewAction) async { switch viewAction { case .logout: completion?(.logout) @@ -100,9 +99,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol return } - Task { - await roomSummary.loadDetails() - } + Task { await roomSummary.loadDetails() } } private func buildOrUpdateRoomFromSummary(_ roomSummary: RoomSummaryProtocol) -> HomeScreenRoom { diff --git a/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift index 118fb9a9f..dbd5569b6 100644 --- a/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift +++ b/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift @@ -16,8 +16,7 @@ import SwiftUI -typealias LoginScreenViewModelType = StateStoreViewModel +typealias LoginScreenViewModelType = StateStoreViewModel class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtocol { @@ -38,7 +37,7 @@ class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtoc // MARK: - Public - override func process(viewAction: LoginScreenViewAction) { + override func process(viewAction: LoginScreenViewAction) async { switch viewAction { case .login: completion?(.login((username: context.username, password: context.password))) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 05592c3f0..93bdb1edd 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -16,8 +16,7 @@ import SwiftUI -typealias RoomScreenViewModelType = StateStoreViewModel +typealias RoomScreenViewModelType = StateStoreViewModel class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol { @@ -26,12 +25,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol } private let timelineController: RoomTimelineControllerProtocol - private let timelineViewFactory: RoomTimelineViewFactory + private let timelineViewFactory: RoomTimelineViewFactoryProtocol // MARK: - Setup init(timelineController: RoomTimelineControllerProtocol, - timelineViewFactory: RoomTimelineViewFactory, + timelineViewFactory: RoomTimelineViewFactoryProtocol, roomName: String?) { self.timelineController = timelineController self.timelineViewFactory = timelineViewFactory @@ -41,20 +40,20 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol timelineController.callbacks .receive(on: DispatchQueue.main) .sink { [weak self] callback in - guard let self = self else { return } - - switch callback { - case .updatedTimelineItems: - self.buildTimelineViews() - case .updatedTimelineItem(let itemId): - guard let timelineItem = self.timelineController.timelineItems.first(where: { $0.id == itemId }), - let viewIndex = self.state.items.firstIndex(where: { $0.id == itemId }) else { - return - } + guard let self = self else { return } - self.state.items[viewIndex] = timelineViewFactory.buildTimelineViewFor(timelineItem) - } - }.store(in: &cancellables) + switch callback { + case .updatedTimelineItems: + self.buildTimelineViews() + case .updatedTimelineItem(let itemId): + guard let timelineItem = self.timelineController.timelineItems.first(where: { $0.id == itemId }), + let viewIndex = self.state.items.firstIndex(where: { $0.id == itemId }) else { + return + } + + self.state.items[viewIndex] = timelineViewFactory.buildTimelineViewFor(timelineItem: timelineItem) + } + }.store(in: &cancellables) state.contextMenuBuilder = buildContexMenuForItemId(_:) @@ -63,31 +62,29 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol // MARK: - Public - override func process(viewAction: RoomScreenViewAction) { - Task { - switch viewAction { - case .loadPreviousPage: - state.isBackPaginating = true - - switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) { - default: - state.isBackPaginating = false - } - - case .itemAppeared(let id): - await timelineController.processItemAppearance(id) - case .itemDisappeared(let id): - await timelineController.processItemDisappearance(id) - case .linkClicked(let url): - MXLog.warning("Link clicked: \(url)") - case .sendMessage: - guard state.bindings.composerText.count > 0 else { - return - } - - await timelineController.sendMessage(state.bindings.composerText) - state.bindings.composerText = "" + override func process(viewAction: RoomScreenViewAction) async { + switch viewAction { + case .loadPreviousPage: + state.isBackPaginating = true + + switch await timelineController.paginateBackwards(Constants.backPaginationPageSize) { + default: + state.isBackPaginating = false } + + case .itemAppeared(let id): + await timelineController.processItemAppearance(id) + case .itemDisappeared(let id): + await timelineController.processItemDisappearance(id) + case .linkClicked(let url): + MXLog.warning("Link clicked: \(url)") + case .sendMessage: + guard state.bindings.composerText.count > 0 else { + return + } + + await timelineController.sendMessage(state.bindings.composerText) + state.bindings.composerText = "" } } @@ -95,7 +92,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol private func buildTimelineViews() { let stateItems = timelineController.timelineItems.map { item in - timelineViewFactory.buildTimelineViewFor(item) + timelineViewFactory.buildTimelineViewFor(timelineItem: item) } state.items = stateItems diff --git a/ElementX/Sources/Services/Authentication/UserSession.swift b/ElementX/Sources/Services/Authentication/UserSession.swift index ff606f206..2cfa78b1d 100644 --- a/ElementX/Sources/Services/Authentication/UserSession.swift +++ b/ElementX/Sources/Services/Authentication/UserSession.swift @@ -60,9 +60,7 @@ class UserSession { Benchmark.startTrackingForIdentifier("ClientSync", message: "Started sync.") client.startSync() - Task { - await updateRooms() - } + Task { await updateRooms() } } var userIdentifier: String { @@ -83,7 +81,8 @@ class UserSession { return .failure(.failedRetrievingDisplayName) } - }.value + } + .value } func loadUserAvatarURL() async -> Result { @@ -94,7 +93,8 @@ class UserSession { } catch { return .failure(.failedRetrievingDisplayName) } - }.value + } + .value } // MARK: ClientDelegate @@ -116,7 +116,7 @@ class UserSession { Benchmark.endTrackingForIdentifier("ClientRooms", message: "Retrieved \(sdkRooms.count) rooms") Benchmark.startTrackingForIdentifier("ProcessingRooms", message: "Started processing \(sdkRooms.count) rooms") - let diff = sdkRooms.map({ $0.id()}).difference(from: currentRooms.map({ $0.id })) + let diff = sdkRooms.map({ $0.id() }).difference(from: currentRooms.map(\.id)) for change in diff { switch change { @@ -125,7 +125,7 @@ class UserSession { MXLog.error("Failed retrieving sdk room with id: \(id)") break } - currentRooms.append(RoomProxy(room: sdkRoom, messageFactory: RoomMessageFactory())) + currentRooms.append(RoomProxy(room: sdkRoom, roomMessageFactory: RoomMessageFactory())) case .remove(_, let id, _): currentRooms.removeAll { $0.id == id } } diff --git a/ElementX/Sources/Services/Media/MediaProvider.swift b/ElementX/Sources/Services/Media/MediaProvider.swift index 747fa9154..451b2ec76 100644 --- a/ElementX/Sources/Services/Media/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/MediaProvider.swift @@ -73,6 +73,7 @@ struct MediaProvider: MediaProviderProtocol { MXLog.error("Failed retrieving image with error: \(error)") return .failure(.failedRetrievingImage) } - }.value + } + .value } } diff --git a/ElementX/Sources/Services/Media/MockMediaProvider.swift b/ElementX/Sources/Services/Media/MockMediaProvider.swift index d6355f9a8..c3e6230e5 100644 --- a/ElementX/Sources/Services/Media/MockMediaProvider.swift +++ b/ElementX/Sources/Services/Media/MockMediaProvider.swift @@ -11,10 +11,6 @@ import UIKit struct MockMediaProvider: MediaProviderProtocol { - func loadCurrentUserAvatar(_ completion: @escaping (Result) -> Void) { - - } - func imageFromSource(_ source: MediaSource?) -> UIImage? { return nil } @@ -26,11 +22,7 @@ struct MockMediaProvider: MediaProviderProtocol { func imageFromURL(_ url: String?) -> UIImage? { return nil } - - func loadImageFromURL(_ url: String, _ completion: @escaping (Result) -> Void) { - } - func loadImageFromURL(_ url: String) async -> Result { return .failure(.failedRetrievingImage) } diff --git a/ElementX/Sources/Services/Room/RoomMessageFactory.swift b/ElementX/Sources/Services/Room/RoomMessageFactory.swift index f29304708..bce3dc443 100644 --- a/ElementX/Sources/Services/Room/RoomMessageFactory.swift +++ b/ElementX/Sources/Services/Room/RoomMessageFactory.swift @@ -9,7 +9,7 @@ import Foundation import MatrixRustSDK -struct RoomMessageFactory { +struct RoomMessageFactory: RoomMessageFactoryProtocol { func buildRoomMessageFrom(_ message: AnyMessage) -> RoomMessageProtocol { if let textMessage = message.textMessage() { return TextRoomMessage(message: textMessage) diff --git a/ElementX/Sources/Services/Room/RoomMessageFactoryProtocol.swift b/ElementX/Sources/Services/Room/RoomMessageFactoryProtocol.swift new file mode 100644 index 000000000..a2ab3524d --- /dev/null +++ b/ElementX/Sources/Services/Room/RoomMessageFactoryProtocol.swift @@ -0,0 +1,14 @@ +// +// RoomMessageFactoryProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 26/05/2022. +// Copyright © 2022 element.io. All rights reserved. +// + +import Foundation +import MatrixRustSDK + +protocol RoomMessageFactoryProtocol { + func buildRoomMessageFrom(_ message: AnyMessage) -> RoomMessageProtocol +} diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 61ca7878b..d91d647dc 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -27,7 +27,7 @@ private class WeakRoomProxyWrapper: RoomDelegate { class RoomProxy: RoomProxyProtocol { private let room: Room - private let messageFactory: RoomMessageFactory + private let roomMessageFactory: RoomMessageFactoryProtocol private var backwardStream: BackwardsStreamProtocol? @@ -37,9 +37,9 @@ class RoomProxy: RoomProxyProtocol { private(set) var messages: [RoomMessageProtocol] - init(room: Room, messageFactory: RoomMessageFactory) { + init(room: Room, roomMessageFactory: RoomMessageFactoryProtocol) { self.room = room - self.messageFactory = messageFactory + self.roomMessageFactory = roomMessageFactory messages = [] room.setDelegate(delegate: WeakRoomProxyWrapper(roomProxy: self)) @@ -91,7 +91,8 @@ class RoomProxy: RoomProxyProtocol { } catch { return .failure(.failedRetrievingMemberAvatarURL) } - }.value + } + .value } func loadDisplayNameForUserId(_ userId: String) async -> Result { @@ -102,7 +103,8 @@ class RoomProxy: RoomProxyProtocol { } catch { return .failure(.failedRetrievingMemberDisplayName) } - }.value + } + .value } func loadDisplayName() async -> Result { @@ -119,7 +121,8 @@ class RoomProxy: RoomProxyProtocol { } catch { return .failure(.failedRetrievingDisplayName) } - }.value + } + .value } func paginateBackwards(count: UInt) async -> Result { @@ -133,13 +136,14 @@ class RoomProxy: RoomProxyProtocol { Benchmark.endTrackingForIdentifier("BackPagination \(self.id)", message: "Finished backpaginating \(count) message(s) in room \(self.id)") let messages = sdkMessages.map { message in - self.messageFactory.buildRoomMessageFrom(message) + self.roomMessageFactory.buildRoomMessageFrom(message) }.reversed() self.messages.insert(contentsOf: messages, at: 0) return .success(()) - }.value + } + .value } func sendMessage(_ message: String) async -> Result { @@ -153,13 +157,14 @@ class RoomProxy: RoomProxyProtocol { } catch { return .failure(.failedSendingMessage) } - }.value + } + .value } // MARK: - Private fileprivate func appendMessage(_ message: AnyMessage) { - let message = self.messageFactory.buildRoomMessageFrom(message) + let message = roomMessageFactory.buildRoomMessageFrom(message) messages.append(message) callbacks.send(.updatedMessages) } diff --git a/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactory.swift b/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactory.swift index 31fa4191f..6f4ed208f 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactory.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactory.swift @@ -16,7 +16,7 @@ struct EventBriefFactory: EventBriefFactoryProtocol { self.memberDetailProvider = memberDetailProvider } - func eventBriefForMessage(_ message: RoomMessageProtocol?) async -> EventBrief? { + func buildEventBriefFor(message: RoomMessageProtocol?) async -> EventBrief? { guard let message = message else { return nil } @@ -37,7 +37,7 @@ struct EventBriefFactory: EventBriefFactoryProtocol { // MARK: - Private - private func buildEventBrief(message: RoomMessageProtocol, htmlBody: String?) async -> EventBrief? { + private func buildEventBrief(message: RoomMessageProtocol, htmlBody: String?) async -> EventBrief? { switch await memberDetailProvider.loadDisplayNameForUserId(message.sender) { case .success(let displayName): return EventBrief(eventId: message.id, diff --git a/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactoryProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactoryProtocol.swift index c22358b56..7743db3db 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactoryProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/EventBriefFactoryProtocol.swift @@ -10,5 +10,5 @@ import Foundation @MainActor protocol EventBriefFactoryProtocol { - func eventBriefForMessage(_ message: RoomMessageProtocol?) async -> EventBrief? + func buildEventBriefFor(message: RoomMessageProtocol?) async -> EventBrief? } diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift index 2ae1550a3..f9c6b4063 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummary.swift @@ -30,7 +30,7 @@ struct MockRoomSummary: RoomSummaryProtocol { var avatar: UIImage? - func loadDetails() { + func loadDetails() async { } diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift index df5f15e06..046a1f4ba 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummary.swift @@ -48,19 +48,19 @@ class RoomSummary: RoomSummaryProtocol { private(set) var displayName: String? { didSet { - self.callbacks.send(.updatedData) + callbacks.send(.updatedData) } } private(set) var lastMessage: EventBrief? { didSet { - self.callbacks.send(.updatedData) + callbacks.send(.updatedData) } } private(set) var avatar: UIImage? { didSet { - self.callbacks.send(.updatedData) + callbacks.send(.updatedData) } } @@ -72,7 +72,7 @@ class RoomSummary: RoomSummaryProtocol { self.eventBriefFactory = eventBriefFactory Task { - lastMessage = await eventBriefFactory.eventBriefForMessage(roomProxy.messages.last) + lastMessage = await eventBriefFactory.buildEventBriefFor(message: roomProxy.messages.last) } roomProxy.callbacks @@ -85,7 +85,7 @@ class RoomSummary: RoomSummaryProtocol { switch callback { case .updatedMessages: Task { - self.lastMessage = await eventBriefFactory.eventBriefForMessage(roomProxy.messages.last) + self.lastMessage = await eventBriefFactory.buildEventBriefFor(message: roomProxy.messages.last) } } } @@ -98,13 +98,13 @@ class RoomSummary: RoomSummaryProtocol { } await withTaskGroup(of: Void.self) { group in - group.addTask(priority: .medium) { + group.addTask { await self.loadDisplayName() } - group.addTask(priority: .medium) { + group.addTask { await self.loadAvatar() } - group.addTask(priority: .medium) { + group.addTask { await self.loadLastMessage() } } @@ -143,7 +143,7 @@ class RoomSummary: RoomSummaryProtocol { switch await roomProxy.paginateBackwards(count: 1) { case .success: - lastMessage = await eventBriefFactory.eventBriefForMessage(roomProxy.messages.last) + lastMessage = await eventBriefFactory.buildEventBriefFor(message: roomProxy.messages.last) case .failure(let error): MXLog.error("Failed back paginating with error: \(error)") } diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift index 82a3ecc70..5f5d71f13 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -12,7 +12,7 @@ import UIKit class RoomTimelineController: RoomTimelineControllerProtocol { private let timelineProvider: RoomTimelineProviderProtocol - private let timelineItemFactory: RoomTimelineItemFactory + private let timelineItemFactory: RoomTimelineItemFactoryProtocol private let mediaProvider: MediaProviderProtocol private let memberDetailProvider: MemberDetailProviderProtocol @@ -23,7 +23,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { private(set) var timelineItems = [RoomTimelineItemProtocol]() init(timelineProvider: RoomTimelineProviderProtocol, - timelineItemFactory: RoomTimelineItemFactory, + timelineItemFactory: RoomTimelineItemFactoryProtocol, mediaProvider: MediaProviderProtocol, memberDetailProvider: MemberDetailProviderProtocol) { self.timelineProvider = timelineProvider @@ -35,13 +35,13 @@ class RoomTimelineController: RoomTimelineControllerProtocol { .callbacks .receive(on: DispatchQueue.main) .sink { [weak self] callback in - guard let self = self else { return } - - switch callback { - case .updatedMessages: - self.updateTimelineItems() - } - }.store(in: &cancellables) + guard let self = self else { return } + + switch callback { + case .updatedMessages: + self.updateTimelineItems() + } + }.store(in: &cancellables) updateTimelineItems() @@ -59,7 +59,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } func processItemAppearance(_ itemId: String) async { - guard let timelineItem = self.timelineItems.filter({ $0.id == itemId}).first else { + guard let timelineItem = timelineItems.first(where: { $0.id == itemId }) else { return } @@ -98,8 +98,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol { var newTimelineItems = [RoomTimelineItemProtocol]() var previousMessage: RoomMessageProtocol? - for message in self.timelineProvider.messages { - let areMessagesFromTheSameDay = self.haveSameDay(lhs: previousMessage, rhs: message) + for message in timelineProvider.messages { + let areMessagesFromTheSameDay = haveSameDay(lhs: previousMessage, rhs: message) let shouldAddSectionHeader = !areMessagesFromTheSameDay if shouldAddSectionHeader { @@ -110,14 +110,14 @@ class RoomTimelineController: RoomTimelineControllerProtocol { let areMessagesFromTheSameSender = (previousMessage?.sender == message.sender) let shouldShowSenderDetails = !areMessagesFromTheSameSender || !areMessagesFromTheSameDay - newTimelineItems.append(timelineItemFactory.buildTimelineItemFor(message, showSenderDetails: shouldShowSenderDetails)) + newTimelineItems.append(timelineItemFactory.buildTimelineItemFor(message: message, showSenderDetails: shouldShowSenderDetails)) previousMessage = message } - self.timelineItems = newTimelineItems + timelineItems = newTimelineItems - self.callbacks.send(.updatedTimelineItems) + callbacks.send(.updatedTimelineItems) } private func haveSameDay(lhs: RoomMessageProtocol?, rhs: RoomMessageProtocol?) -> Bool { diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index c08d95d20..65ec4dbd2 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -1,5 +1,5 @@ // -// TimelineItemFactory.swift +// RoomTimelineItemFactory.swift // ElementX // // Created by Stefan Ceriu on 16/03/2022. @@ -9,8 +9,7 @@ import Foundation import UIKit -@MainActor -struct RoomTimelineItemFactory { +struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { private let mediaProvider: MediaProviderProtocol private let memberDetailProvider: MemberDetailProviderProtocol private let attributedStringBuilder: AttributedStringBuilderProtocol @@ -23,12 +22,12 @@ struct RoomTimelineItemFactory { self.attributedStringBuilder = attributedStringBuilder } - func buildTimelineItemFor(_ roomMessage: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol { - let displayName = memberDetailProvider.displayNameForUserId(roomMessage.sender) - let avatarURL = memberDetailProvider.avatarURLForUserId(roomMessage.sender) + func buildTimelineItemFor(message: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol { + let displayName = memberDetailProvider.displayNameForUserId(message.sender) + let avatarURL = memberDetailProvider.avatarURLForUserId(message.sender) let avatarImage = mediaProvider.imageFromURL(avatarURL) - switch roomMessage { + switch message { case let message as TextRoomMessage: return buildTextTimelineItemFromMessage(message, showSenderDetails, displayName, avatarImage) case let message as ImageRoomMessage: diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift new file mode 100644 index 000000000..372539b7e --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactoryProtocol.swift @@ -0,0 +1,14 @@ +// +// RoomTimelineItemFactoryProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 26/05/2022. +// Copyright © 2022 element.io. All rights reserved. +// + +import Foundation + +@MainActor +protocol RoomTimelineItemFactoryProtocol { + func buildTimelineItemFor(message: RoomMessageProtocol, showSenderDetails: Bool) -> RoomTimelineItemProtocol +} diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactory.swift index c1caaf6b6..5da308666 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactory.swift @@ -1,5 +1,5 @@ // -// TimelineViewFactory.swift +// RoomTimelineViewFactory.swift // ElementX // // Created by Stefan Ceriu on 16/03/2022. @@ -8,9 +8,8 @@ import Foundation -@MainActor -struct RoomTimelineViewFactory { - func buildTimelineViewFor(_ timelineItem: RoomTimelineItemProtocol) -> RoomTimelineViewProvider { +struct RoomTimelineViewFactory: RoomTimelineViewFactoryProtocol { + func buildTimelineViewFor(timelineItem: RoomTimelineItemProtocol) -> RoomTimelineViewProvider { switch timelineItem { case let item as TextRoomTimelineItem: return .text(item) diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactoryProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactoryProtocol.swift new file mode 100644 index 000000000..13fa47a5b --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineViewFactoryProtocol.swift @@ -0,0 +1,14 @@ +// +// RoomTimelineViewFactoryProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 26/05/2022. +// Copyright © 2022 element.io. All rights reserved. +// + +import Foundation + +@MainActor +protocol RoomTimelineViewFactoryProtocol { + func buildTimelineViewFor(timelineItem: RoomTimelineItemProtocol) -> RoomTimelineViewProvider +} diff --git a/ElementX/Sources/UITestsAppCoordinator.swift b/ElementX/Sources/UITestsAppCoordinator.swift index 0d75a5159..bd90356c4 100644 --- a/ElementX/Sources/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITestsAppCoordinator.swift @@ -22,7 +22,7 @@ class UITestsAppCoordinator: Coordinator { let screens = mockScreens() let rootView = UITestsRootView(mockScreens: screens) { id in - guard let screen = screens.filter({ $0.id == id }).first else { + guard let screen = screens.first(where: { $0.id == id }) else { fatalError() } diff --git a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModel.swift b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModel.swift index 6adbf3741..005fe63c7 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModel.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/ElementX/TemplateSimpleScreenViewModel.swift @@ -16,8 +16,7 @@ import SwiftUI -typealias TemplateSimpleScreenViewModelType = StateStoreViewModel +typealias TemplateSimpleScreenViewModelType = StateStoreViewModel class TemplateSimpleScreenViewModel: TemplateSimpleScreenViewModelType, TemplateSimpleScreenViewModelProtocol { @@ -34,10 +33,10 @@ class TemplateSimpleScreenViewModel: TemplateSimpleScreenViewModelType, Template init(promptType: TemplateSimpleScreenPromptType, initialCount: Int = 0) { super.init(initialViewState: TemplateSimpleScreenViewState(promptType: promptType, count: 0)) } - + // MARK: - Public - - override func process(viewAction: TemplateSimpleScreenViewAction) { + + override func process(viewAction: TemplateSimpleScreenViewAction) async { switch viewAction { case .accept: completion?(.accept) diff --git a/Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit/TemplateSimpleScreenViewModelTests.swift b/Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit/TemplateSimpleScreenViewModelTests.swift index a71372279..d130865e5 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit/TemplateSimpleScreenViewModelTests.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit/TemplateSimpleScreenViewModelTests.swift @@ -36,14 +36,17 @@ class TemplateSimpleScreenViewModelTests: XCTestCase { XCTAssertEqual(context.viewState.count, Constants.counterInitialValue) } - func testCounter() throws { + func testCounter() async throws { context.send(viewAction: .incrementCount) + await Task.yield() XCTAssertEqual(context.viewState.count, 1) context.send(viewAction: .incrementCount) + await Task.yield() XCTAssertEqual(context.viewState.count, 2) context.send(viewAction: .decrementCount) + await Task.yield() XCTAssertEqual(context.viewState.count, 1) } }