From 9eec77e228326186694b421c160dc8e5782cbb6e Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 8 Mar 2022 14:24:33 +0200 Subject: [PATCH] Added room timeline screen with live events listening and back pagination. Rearranged project files. --- .swiftlint.yml | 1 + ElementX.xcodeproj/project.pbxproj | 704 ++++++++++-------- .../xcshareddata/swiftpm/Package.resolved | 2 +- ElementX/Sources/AppCoordinator.swift | 32 +- .../Sources/Modules/Models/RoomModel.swift | 109 --- .../Other/Activity/Activity.swift | 0 .../Other/Activity/ActivityCenter.swift | 0 .../Other/Activity/ActivityDismissal.swift | 0 .../Other/Activity/ActivityPresentable.swift | 0 .../FullscreenLoadingActivityPresenter.swift | 0 .../ToastActivityPresenter.swift | 0 .../Other/Activity/ActivityRequest.swift | 0 .../LabelledActivityIndicatorView.swift | 0 .../Activity/Toasts/RectangleToastView.swift | 0 .../Activity/Toasts/RoundedToastView.swift | 0 .../{Modules => }/Other/Coordinator.swift | 0 .../Sources/{Modules => }/Other/MXLog.swift | 0 .../Routers/NavigationModule.swift | 0 .../Routers/NavigationRouter.swift | 0 .../Routers/NavigationRouterStore.swift | 0 .../NavigationRouterStoreProtocol.swift | 0 .../Routers/NavigationRouterType.swift | 0 .../Routers/Presentable.swift | 0 .../Routers/RootRouter.swift | 0 .../Routers/RootRouterType.swift | 0 .../SwiftUI/ViewModel/BindableState.swift | 0 .../ViewModel/StateStoreViewModel.swift | 0 .../Other/WeakDictionary/WeakDictionary.swift | 0 .../WeakDictionaryKeyReference.swift | 0 .../WeakDictionaryReference.swift | 0 .../WeakDictionary/WeakKeyDictionary.swift | 0 .../HomeScreen/HomeScreenCoordinator.swift | 5 +- .../HomeScreen/HomeScreenModels.swift | 12 +- .../HomeScreen/HomeScreenViewModel.swift | 71 +- .../HomeScreenViewModelProtocol.swift | 2 +- .../Test/UI/HomeScreenUITests.swift | 0 .../Test/Unit/HomeScreenViewModelTests.swift | 0 .../HomeScreen/View/HomeScreen.swift | 68 +- .../LoginScreen/LoginScreenCoordinator.swift | 0 .../LoginScreen/LoginScreenModels.swift | 0 .../LoginScreen/LoginScreenViewModel.swift | 0 .../LoginScreenViewModelProtocol.swift | 0 .../Test/UI/LoginScreenUITests.swift | 0 .../Test/Unit/LoginScreenViewModelTests.swift | 0 .../LoginScreen/View/LoginScreen.swift | 0 .../RoomScreen/RoomScreenCoordinator.swift | 67 ++ .../Screens/RoomScreen/RoomScreenModels.swift | 50 ++ .../RoomScreen/RoomScreenViewModel.swift | 74 ++ .../RoomScreenViewModelProtocol.swift | 24 + .../Test/UI/RoomScreenUITests.swift | 17 + .../Test/Unit/RoomScreenViewModelTests.swift | 21 + .../Screens/RoomScreen/View/RoomScreen.swift | 85 +++ .../Splash/SplashViewController.swift | 0 .../Splash/SplashViewController.xib | 0 .../TemplateSimpleScreenCoordinator.swift | 0 .../MockTemplateSimpleScreenScreenState.swift | 0 .../TemplateSimpleScreenModels.swift | 0 .../TemplateSimpleScreenViewModel.swift | 0 ...emplateSimpleScreenViewModelProtocol.swift | 0 .../Test/UI/TemplateSimpleScreenUITests.swift | 0 .../TemplateSimpleScreenViewModelTests.swift | 0 .../View/TemplateSimpleScreen.swift | 0 .../AuthenticationCoordinator.swift | 0 .../Authentication/KeychainController.swift | 0 .../KeychainControllerProtocol.swift | 0 .../Authentication/UserSession.swift | 46 +- .../MockRoomProxy.swift} | 17 +- ElementX/Sources/Services/RoomProxy.swift | 152 ++++ .../RoomProxyProtocol.swift} | 19 +- .../Timeline/MockRoomTimelineController.swift | 20 + .../Timeline/RoomTimelineController.swift | 48 ++ .../RoomTimelineControllerProtocol.swift | 17 + .../Timeline/RoomTimelineItemProtocol.swift | 16 + .../Timeline/RoomTimelineProvider.swift | 61 ++ .../Timeline/TextRoomTimelineItem.swift | 16 + Scripts/createSwiftUISimpleScreen.sh | 2 +- fastlane/Fastfile | 2 + 77 files changed, 1269 insertions(+), 491 deletions(-) delete mode 100644 ElementX/Sources/Modules/Models/RoomModel.swift rename ElementX/Sources/{Modules => }/Other/Activity/Activity.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/ActivityCenter.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/ActivityDismissal.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/ActivityPresentable.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/ActivityPresenters/FullscreenLoadingActivityPresenter.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/ActivityPresenters/ToastActivityPresenter.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/ActivityRequest.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/LabelledActivityIndicatorView.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/Toasts/RectangleToastView.swift (100%) rename ElementX/Sources/{Modules => }/Other/Activity/Toasts/RoundedToastView.swift (100%) rename ElementX/Sources/{Modules => }/Other/Coordinator.swift (100%) rename ElementX/Sources/{Modules => }/Other/MXLog.swift (100%) rename ElementX/Sources/{Modules => Other}/Routers/NavigationModule.swift (100%) rename ElementX/Sources/{Modules => Other}/Routers/NavigationRouter.swift (100%) rename ElementX/Sources/{Modules => Other}/Routers/NavigationRouterStore.swift (100%) rename ElementX/Sources/{Modules => Other}/Routers/NavigationRouterStoreProtocol.swift (100%) rename ElementX/Sources/{Modules => Other}/Routers/NavigationRouterType.swift (100%) rename ElementX/Sources/{Modules => Other}/Routers/Presentable.swift (100%) rename ElementX/Sources/{Modules => Other}/Routers/RootRouter.swift (100%) rename ElementX/Sources/{Modules => Other}/Routers/RootRouterType.swift (100%) rename ElementX/Sources/{Modules => }/Other/SwiftUI/ViewModel/BindableState.swift (100%) rename ElementX/Sources/{Modules => }/Other/SwiftUI/ViewModel/StateStoreViewModel.swift (100%) rename ElementX/Sources/{Modules => }/Other/WeakDictionary/WeakDictionary.swift (100%) rename ElementX/Sources/{Modules => }/Other/WeakDictionary/WeakDictionaryKeyReference.swift (100%) rename ElementX/Sources/{Modules => }/Other/WeakDictionary/WeakDictionaryReference.swift (100%) rename ElementX/Sources/{Modules => }/Other/WeakDictionary/WeakKeyDictionary.swift (100%) rename ElementX/Sources/{Modules => Screens}/HomeScreen/HomeScreenCoordinator.swift (93%) rename ElementX/Sources/{Modules => Screens}/HomeScreen/HomeScreenModels.swift (82%) rename ElementX/Sources/{Modules => Screens}/HomeScreen/HomeScreenViewModel.swift (69%) rename ElementX/Sources/{Modules => Screens}/HomeScreen/HomeScreenViewModelProtocol.swift (93%) rename ElementX/Sources/{Modules => Screens}/HomeScreen/Test/UI/HomeScreenUITests.swift (100%) rename ElementX/Sources/{Modules => Screens}/HomeScreen/Test/Unit/HomeScreenViewModelTests.swift (100%) rename ElementX/Sources/{Modules => Screens}/HomeScreen/View/HomeScreen.swift (74%) rename ElementX/Sources/{Modules/Authentication => Screens}/LoginScreen/LoginScreenCoordinator.swift (100%) rename ElementX/Sources/{Modules/Authentication => Screens}/LoginScreen/LoginScreenModels.swift (100%) rename ElementX/Sources/{Modules/Authentication => Screens}/LoginScreen/LoginScreenViewModel.swift (100%) rename ElementX/Sources/{Modules/Authentication => Screens}/LoginScreen/LoginScreenViewModelProtocol.swift (100%) rename ElementX/Sources/{Modules/Authentication => Screens}/LoginScreen/Test/UI/LoginScreenUITests.swift (100%) rename ElementX/Sources/{Modules/Authentication => Screens}/LoginScreen/Test/Unit/LoginScreenViewModelTests.swift (100%) rename ElementX/Sources/{Modules/Authentication => Screens}/LoginScreen/View/LoginScreen.swift (100%) create mode 100644 ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/RoomScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/Test/UI/RoomScreenUITests.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/Test/Unit/RoomScreenViewModelTests.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift rename ElementX/Sources/{Modules => Screens}/Splash/SplashViewController.swift (100%) rename ElementX/Sources/{Modules => Screens}/Splash/SplashViewController.xib (100%) rename ElementX/Sources/{Modules => Screens}/Templates/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift (100%) rename ElementX/Sources/{Modules => Screens}/Templates/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift (100%) rename ElementX/Sources/{Modules => Screens}/Templates/SimpleScreenExample/TemplateSimpleScreenModels.swift (100%) rename ElementX/Sources/{Modules => Screens}/Templates/SimpleScreenExample/TemplateSimpleScreenViewModel.swift (100%) rename ElementX/Sources/{Modules => Screens}/Templates/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift (100%) rename ElementX/Sources/{Modules => Screens}/Templates/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift (100%) rename ElementX/Sources/{Modules => Screens}/Templates/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift (100%) rename ElementX/Sources/{Modules => Screens}/Templates/SimpleScreenExample/View/TemplateSimpleScreen.swift (100%) rename ElementX/Sources/{Modules => Services}/Authentication/AuthenticationCoordinator.swift (100%) rename ElementX/Sources/{Modules => Services}/Authentication/KeychainController.swift (100%) rename ElementX/Sources/{Modules => Services}/Authentication/KeychainControllerProtocol.swift (100%) rename ElementX/Sources/{Modules => Services}/Authentication/UserSession.swift (74%) rename ElementX/Sources/{Modules/Models/MockRoomModel.swift => Services/MockRoomProxy.swift} (68%) create mode 100644 ElementX/Sources/Services/RoomProxy.swift rename ElementX/Sources/{Modules/Models/RoomModelProtocol.swift => Services/RoomProxyProtocol.swift} (55%) create mode 100644 ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift create mode 100644 ElementX/Sources/Services/Timeline/RoomTimelineController.swift create mode 100644 ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift create mode 100644 ElementX/Sources/Services/Timeline/RoomTimelineItemProtocol.swift create mode 100644 ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift create mode 100644 ElementX/Sources/Services/Timeline/TextRoomTimelineItem.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 4386e6bba..66730e43a 100755 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -2,6 +2,7 @@ disabled_rules: - trailing_whitespace - unused_setter_value + - redundant_discardable_let # some rules are only opt-in opt_in_rules: diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 9fe33e8db..e65ebed6d 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -7,18 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 182BC42027BE667200A30C33 /* RoomModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC41F27BE667200A30C33 /* RoomModelProtocol.swift */; }; - 182BC42227BE6C6900A30C33 /* MockRoomModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC42127BE6C6900A30C33 /* MockRoomModel.swift */; }; - 182BC46F27C4CD6D00A30C33 /* RoundedToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC46527C4CD6D00A30C33 /* RoundedToastView.swift */; }; - 182BC47027C4CD6D00A30C33 /* RectangleToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC46627C4CD6D00A30C33 /* RectangleToastView.swift */; }; - 182BC47127C4CD6D00A30C33 /* ActivityPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC46727C4CD6D00A30C33 /* ActivityPresentable.swift */; }; - 182BC47227C4CD6D00A30C33 /* ActivityCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC46827C4CD6D00A30C33 /* ActivityCenter.swift */; }; - 182BC47327C4CD6D00A30C33 /* ToastActivityPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC46A27C4CD6D00A30C33 /* ToastActivityPresenter.swift */; }; - 182BC47527C4CD6D00A30C33 /* Activity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC46C27C4CD6D00A30C33 /* Activity.swift */; }; - 182BC47627C4CD6D00A30C33 /* ActivityRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC46D27C4CD6D00A30C33 /* ActivityRequest.swift */; }; - 182BC47727C4CD6D00A30C33 /* ActivityDismissal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC46E27C4CD6D00A30C33 /* ActivityDismissal.swift */; }; - 182BC47927C4CE2200A30C33 /* LabelledActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC47827C4CE2200A30C33 /* LabelledActivityIndicatorView.swift */; }; - 182BC47B27C4D05200A30C33 /* FullscreenLoadingActivityPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 182BC47A27C4D05200A30C33 /* FullscreenLoadingActivityPresenter.swift */; }; 182BC48127C4EBBB00A30C33 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 182BC48027C4EBBB00A30C33 /* Kingfisher */; }; 1850253F27B6918D002E6B18 /* ElementXTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850253E27B6918D002E6B18 /* ElementXTests.swift */; }; 1850254927B6918D002E6B18 /* ElementXUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850254827B6918D002E6B18 /* ElementXUITests.swift */; }; @@ -30,43 +18,68 @@ 1850257127B6A135002E6B18 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1850256927B6A135002E6B18 /* LaunchScreen.storyboard */; }; 1863A3FC27BA5A9100B52E4D /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A3FB27BA5A9100B52E4D /* KeychainAccess */; }; 1863A40627BA6DFC00B52E4D /* SwiftyBeaver in Frameworks */ = {isa = PBXBuildFile; productRef = 1863A40527BA6DFC00B52E4D /* SwiftyBeaver */; }; - 1863A41427BA716A00B52E4D /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A40E27BA716A00B52E4D /* AuthenticationCoordinator.swift */; }; - 1863A41527BA716A00B52E4D /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A40F27BA716A00B52E4D /* UserSession.swift */; }; - 1863A41627BA716A00B52E4D /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A41027BA716A00B52E4D /* KeychainController.swift */; }; - 1863A41727BA716A00B52E4D /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A41127BA716A00B52E4D /* KeychainControllerProtocol.swift */; }; - 1863A41C27BA76B900B52E4D /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A41B27BA76B900B52E4D /* MXLog.swift */; }; - 1863A42C27BA784300B52E4D /* LoginScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A41F27BA784300B52E4D /* LoginScreenViewModel.swift */; }; - 1863A43027BA784300B52E4D /* LoginScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A42727BA784300B52E4D /* LoginScreenViewModelProtocol.swift */; }; - 1863A43127BA784300B52E4D /* LoginScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A42827BA784300B52E4D /* LoginScreenModels.swift */; }; - 1863A43227BA784300B52E4D /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A42A27BA784300B52E4D /* LoginScreen.swift */; }; - 1863A43427BA786400B52E4D /* LoginScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A42627BA784300B52E4D /* LoginScreenViewModelTests.swift */; }; - 1863A43527BA788500B52E4D /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A42427BA784300B52E4D /* LoginScreenUITests.swift */; }; - 1863A43A27BA789800B52E4D /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A43827BA789800B52E4D /* StateStoreViewModel.swift */; }; - 1863A43B27BA789800B52E4D /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A43927BA789800B52E4D /* BindableState.swift */; }; - 1863A43F27BA790000B52E4D /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A43D27BA790000B52E4D /* Coordinator.swift */; }; - 1863A44927BA79FF00B52E4D /* NavigationRouterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A44127BA79FF00B52E4D /* NavigationRouterStore.swift */; }; - 1863A44A27BA79FF00B52E4D /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A44227BA79FF00B52E4D /* NavigationRouter.swift */; }; - 1863A44B27BA79FF00B52E4D /* RootRouterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A44327BA79FF00B52E4D /* RootRouterType.swift */; }; - 1863A44C27BA79FF00B52E4D /* NavigationRouterStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A44427BA79FF00B52E4D /* NavigationRouterStoreProtocol.swift */; }; - 1863A44D27BA79FF00B52E4D /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A44527BA79FF00B52E4D /* RootRouter.swift */; }; - 1863A44E27BA79FF00B52E4D /* Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A44627BA79FF00B52E4D /* Presentable.swift */; }; - 1863A44F27BA79FF00B52E4D /* NavigationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A44727BA79FF00B52E4D /* NavigationModule.swift */; }; - 1863A45027BA79FF00B52E4D /* NavigationRouterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A44827BA79FF00B52E4D /* NavigationRouterType.swift */; }; - 1863A45627BA7A7800B52E4D /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A45227BA7A7800B52E4D /* WeakDictionaryKeyReference.swift */; }; - 1863A45727BA7A7800B52E4D /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A45327BA7A7800B52E4D /* WeakDictionaryReference.swift */; }; - 1863A45827BA7A7800B52E4D /* WeakDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A45427BA7A7800B52E4D /* WeakDictionary.swift */; }; - 1863A45927BA7A7800B52E4D /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A45527BA7A7800B52E4D /* WeakKeyDictionary.swift */; }; - 1863A45B27BA7B4700B52E4D /* LoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A45A27BA7B4700B52E4D /* LoginScreenCoordinator.swift */; }; - 1863A45F27BAA60300B52E4D /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A45D27BAA60300B52E4D /* SplashViewController.swift */; }; - 1863A46027BAA60300B52E4D /* SplashViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1863A45E27BAA60300B52E4D /* SplashViewController.xib */; }; - 1863A48527BAA8A900B52E4D /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A47927BAA8A900B52E4D /* HomeScreenCoordinator.swift */; }; - 1863A48827BAA8A900B52E4D /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A47F27BAA8A900B52E4D /* HomeScreenViewModelProtocol.swift */; }; - 1863A48927BAA8A900B52E4D /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A48027BAA8A900B52E4D /* HomeScreenModels.swift */; }; - 1863A48A27BAA8A900B52E4D /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A48227BAA8A900B52E4D /* HomeScreen.swift */; }; - 1863A48C27BAA8A900B52E4D /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A48427BAA8A900B52E4D /* HomeScreenViewModel.swift */; }; - 1863A48E27BAA8C800B52E4D /* HomeScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A47C27BAA8A900B52E4D /* HomeScreenUITests.swift */; }; - 1863A48F27BAA8CC00B52E4D /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A47E27BAA8A900B52E4D /* HomeScreenViewModelTests.swift */; }; - 1863A49427BAAA6700B52E4D /* RoomModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1863A49327BAAA6700B52E4D /* RoomModel.swift */; }; + 18F2BAD727D25B4000DD1988 /* RoomProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7327D25B4000DD1988 /* RoomProxyProtocol.swift */; }; + 18F2BAD827D25B4000DD1988 /* RoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7427D25B4000DD1988 /* RoomProxy.swift */; }; + 18F2BAD927D25B4000DD1988 /* MockRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7527D25B4000DD1988 /* MockRoomProxy.swift */; }; + 18F2BADA27D25B4000DD1988 /* RoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7727D25B4000DD1988 /* RoomTimelineProvider.swift */; }; + 18F2BADB27D25B4000DD1988 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7927D25B4000DD1988 /* AuthenticationCoordinator.swift */; }; + 18F2BADC27D25B4000DD1988 /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7A27D25B4000DD1988 /* UserSession.swift */; }; + 18F2BADD27D25B4000DD1988 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7B27D25B4000DD1988 /* KeychainController.swift */; }; + 18F2BADE27D25B4000DD1988 /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7C27D25B4000DD1988 /* KeychainControllerProtocol.swift */; }; + 18F2BADF27D25B4000DD1988 /* NavigationRouterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA7F27D25B4000DD1988 /* NavigationRouterStore.swift */; }; + 18F2BAE027D25B4000DD1988 /* NavigationRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8027D25B4000DD1988 /* NavigationRouter.swift */; }; + 18F2BAE127D25B4000DD1988 /* RootRouterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8127D25B4000DD1988 /* RootRouterType.swift */; }; + 18F2BAE227D25B4000DD1988 /* NavigationRouterStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8227D25B4000DD1988 /* NavigationRouterStoreProtocol.swift */; }; + 18F2BAE327D25B4000DD1988 /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8327D25B4000DD1988 /* RootRouter.swift */; }; + 18F2BAE427D25B4000DD1988 /* Presentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8427D25B4000DD1988 /* Presentable.swift */; }; + 18F2BAE527D25B4000DD1988 /* NavigationModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8527D25B4000DD1988 /* NavigationModule.swift */; }; + 18F2BAE627D25B4000DD1988 /* NavigationRouterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8627D25B4000DD1988 /* NavigationRouterType.swift */; }; + 18F2BAE727D25B4000DD1988 /* RoundedToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8927D25B4000DD1988 /* RoundedToastView.swift */; }; + 18F2BAE827D25B4000DD1988 /* RectangleToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8A27D25B4000DD1988 /* RectangleToastView.swift */; }; + 18F2BAE927D25B4000DD1988 /* ActivityPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8B27D25B4000DD1988 /* ActivityPresentable.swift */; }; + 18F2BAEA27D25B4000DD1988 /* ActivityCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8C27D25B4000DD1988 /* ActivityCenter.swift */; }; + 18F2BAEB27D25B4000DD1988 /* LabelledActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8D27D25B4000DD1988 /* LabelledActivityIndicatorView.swift */; }; + 18F2BAEC27D25B4000DD1988 /* ToastActivityPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA8F27D25B4000DD1988 /* ToastActivityPresenter.swift */; }; + 18F2BAED27D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9027D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift */; }; + 18F2BAEE27D25B4000DD1988 /* Activity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9127D25B4000DD1988 /* Activity.swift */; }; + 18F2BAEF27D25B4000DD1988 /* ActivityRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9227D25B4000DD1988 /* ActivityRequest.swift */; }; + 18F2BAF027D25B4000DD1988 /* ActivityDismissal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9327D25B4000DD1988 /* ActivityDismissal.swift */; }; + 18F2BAF127D25B4000DD1988 /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9427D25B4000DD1988 /* MXLog.swift */; }; + 18F2BAF227D25B4000DD1988 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9627D25B4000DD1988 /* WeakDictionaryKeyReference.swift */; }; + 18F2BAF327D25B4000DD1988 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9727D25B4000DD1988 /* WeakDictionaryReference.swift */; }; + 18F2BAF427D25B4000DD1988 /* WeakDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9827D25B4000DD1988 /* WeakDictionary.swift */; }; + 18F2BAF527D25B4000DD1988 /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9927D25B4000DD1988 /* WeakKeyDictionary.swift */; }; + 18F2BAF627D25B4000DD1988 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9A27D25B4000DD1988 /* Coordinator.swift */; }; + 18F2BAF727D25B4000DD1988 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9D27D25B4000DD1988 /* StateStoreViewModel.swift */; }; + 18F2BAF827D25B4000DD1988 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BA9E27D25B4000DD1988 /* BindableState.swift */; }; + 18F2BAF927D25B4000DD1988 /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAA127D25B4000DD1988 /* SplashViewController.swift */; }; + 18F2BAFA27D25B4000DD1988 /* SplashViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 18F2BAA227D25B4000DD1988 /* SplashViewController.xib */; }; + 18F2BAFB27D25B4000DD1988 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAA427D25B4000DD1988 /* HomeScreenCoordinator.swift */; }; + 18F2BAFE27D25B4000DD1988 /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAAA27D25B4000DD1988 /* HomeScreenViewModelProtocol.swift */; }; + 18F2BAFF27D25B4000DD1988 /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAAB27D25B4000DD1988 /* HomeScreenModels.swift */; }; + 18F2BB0027D25B4000DD1988 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAAD27D25B4000DD1988 /* HomeScreen.swift */; }; + 18F2BB0127D25B4000DD1988 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAAE27D25B4000DD1988 /* HomeScreenViewModel.swift */; }; + 18F2BB0C27D25B4000DD1988 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAC427D25B4000DD1988 /* RoomScreenCoordinator.swift */; }; + 18F2BB0D27D25B4000DD1988 /* RoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAC527D25B4000DD1988 /* RoomScreenViewModel.swift */; }; + 18F2BB0E27D25B4000DD1988 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAC627D25B4000DD1988 /* RoomScreenViewModelProtocol.swift */; }; + 18F2BB0F27D25B4000DD1988 /* RoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAC827D25B4000DD1988 /* RoomScreen.swift */; }; + 18F2BB1127D25B4000DD1988 /* RoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BACA27D25B4000DD1988 /* RoomScreenModels.swift */; }; + 18F2BB1227D25B4000DD1988 /* LoginScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BACC27D25B4000DD1988 /* LoginScreenViewModel.swift */; }; + 18F2BB1527D25B4000DD1988 /* LoginScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAD227D25B4000DD1988 /* LoginScreenViewModelProtocol.swift */; }; + 18F2BB1627D25B4000DD1988 /* LoginScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAD327D25B4000DD1988 /* LoginScreenModels.swift */; }; + 18F2BB1727D25B4000DD1988 /* LoginScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAD427D25B4000DD1988 /* LoginScreenCoordinator.swift */; }; + 18F2BB1827D25B4000DD1988 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAD627D25B4000DD1988 /* LoginScreen.swift */; }; + 18F2BB1A27D25BE800DD1988 /* RoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BB1927D25BE800DD1988 /* RoomTimelineController.swift */; }; + 18F2BB1D27D25D1C00DD1988 /* LoginScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAD127D25B4000DD1988 /* LoginScreenViewModelTests.swift */; }; + 18F2BB1E27D25D2200DD1988 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAA927D25B4000DD1988 /* HomeScreenViewModelTests.swift */; }; + 18F2BB1F27D25D3100DD1988 /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAC327D25B4000DD1988 /* RoomScreenViewModelTests.swift */; }; + 18F2BB2027D25D3400DD1988 /* RoomScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAC127D25B4000DD1988 /* RoomScreenUITests.swift */; }; + 18F2BB2127D25D3C00DD1988 /* HomeScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BAA727D25B4000DD1988 /* HomeScreenUITests.swift */; }; + 18F2BB2227D25D4600DD1988 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BACF27D25B4000DD1988 /* LoginScreenUITests.swift */; }; + 18F2BB2427D262A900DD1988 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BB2327D262A900DD1988 /* RoomTimelineItemProtocol.swift */; }; + 18F2BB2627D262FA00DD1988 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BB2527D262FA00DD1988 /* TextRoomTimelineItem.swift */; }; + 18F2BB2827D2647A00DD1988 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BB2727D2647A00DD1988 /* MockRoomTimelineController.swift */; }; + 18F2BB2A27D2648900DD1988 /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2BB2927D2648900DD1988 /* RoomTimelineControllerProtocol.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -87,18 +100,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 182BC41F27BE667200A30C33 /* RoomModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomModelProtocol.swift; sourceTree = ""; }; - 182BC42127BE6C6900A30C33 /* MockRoomModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomModel.swift; sourceTree = ""; }; - 182BC46527C4CD6D00A30C33 /* RoundedToastView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedToastView.swift; sourceTree = ""; }; - 182BC46627C4CD6D00A30C33 /* RectangleToastView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RectangleToastView.swift; sourceTree = ""; }; - 182BC46727C4CD6D00A30C33 /* ActivityPresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityPresentable.swift; sourceTree = ""; }; - 182BC46827C4CD6D00A30C33 /* ActivityCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityCenter.swift; sourceTree = ""; }; - 182BC46A27C4CD6D00A30C33 /* ToastActivityPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToastActivityPresenter.swift; sourceTree = ""; }; - 182BC46C27C4CD6D00A30C33 /* Activity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Activity.swift; sourceTree = ""; }; - 182BC46D27C4CD6D00A30C33 /* ActivityRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityRequest.swift; sourceTree = ""; }; - 182BC46E27C4CD6D00A30C33 /* ActivityDismissal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityDismissal.swift; sourceTree = ""; }; - 182BC47827C4CE2200A30C33 /* LabelledActivityIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelledActivityIndicatorView.swift; sourceTree = ""; }; - 182BC47A27C4D05200A30C33 /* FullscreenLoadingActivityPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullscreenLoadingActivityPresenter.swift; sourceTree = ""; }; 1850252427B6918C002E6B18 /* ElementX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ElementX.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1850253A27B6918D002E6B18 /* ElementXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ElementXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1850253E27B6918D002E6B18 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = ""; }; @@ -110,43 +111,68 @@ 1850256727B6A135002E6B18 /* ElementX.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = ""; }; 1850256827B6A135002E6B18 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1850256A27B6A135002E6B18 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 1863A40E27BA716A00B52E4D /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = ""; }; - 1863A40F27BA716A00B52E4D /* UserSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; }; - 1863A41027BA716A00B52E4D /* KeychainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = ""; }; - 1863A41127BA716A00B52E4D /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; - 1863A41B27BA76B900B52E4D /* MXLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = ""; }; - 1863A41F27BA784300B52E4D /* LoginScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModel.swift; sourceTree = ""; }; - 1863A42427BA784300B52E4D /* LoginScreenUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = ""; }; - 1863A42627BA784300B52E4D /* LoginScreenViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelTests.swift; sourceTree = ""; }; - 1863A42727BA784300B52E4D /* LoginScreenViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelProtocol.swift; sourceTree = ""; }; - 1863A42827BA784300B52E4D /* LoginScreenModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = ""; }; - 1863A42A27BA784300B52E4D /* LoginScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; - 1863A43827BA789800B52E4D /* StateStoreViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = ""; }; - 1863A43927BA789800B52E4D /* BindableState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = ""; }; - 1863A43D27BA790000B52E4D /* Coordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; - 1863A44127BA79FF00B52E4D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = ""; }; - 1863A44227BA79FF00B52E4D /* NavigationRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = ""; }; - 1863A44327BA79FF00B52E4D /* RootRouterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = ""; }; - 1863A44427BA79FF00B52E4D /* NavigationRouterStoreProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterStoreProtocol.swift; sourceTree = ""; }; - 1863A44527BA79FF00B52E4D /* RootRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = ""; }; - 1863A44627BA79FF00B52E4D /* Presentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = ""; }; - 1863A44727BA79FF00B52E4D /* NavigationModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = ""; }; - 1863A44827BA79FF00B52E4D /* NavigationRouterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterType.swift; sourceTree = ""; }; - 1863A45227BA7A7800B52E4D /* WeakDictionaryKeyReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionaryKeyReference.swift; sourceTree = ""; }; - 1863A45327BA7A7800B52E4D /* WeakDictionaryReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionaryReference.swift; sourceTree = ""; }; - 1863A45427BA7A7800B52E4D /* WeakDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionary.swift; sourceTree = ""; }; - 1863A45527BA7A7800B52E4D /* WeakKeyDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakKeyDictionary.swift; sourceTree = ""; }; - 1863A45A27BA7B4700B52E4D /* LoginScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenCoordinator.swift; sourceTree = ""; }; - 1863A45D27BAA60300B52E4D /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; - 1863A45E27BAA60300B52E4D /* SplashViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SplashViewController.xib; sourceTree = ""; }; - 1863A47927BAA8A900B52E4D /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = ""; }; - 1863A47C27BAA8A900B52E4D /* HomeScreenUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenUITests.swift; sourceTree = ""; }; - 1863A47E27BAA8A900B52E4D /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = ""; }; - 1863A47F27BAA8A900B52E4D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = ""; }; - 1863A48027BAA8A900B52E4D /* HomeScreenModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenModels.swift; sourceTree = ""; }; - 1863A48227BAA8A900B52E4D /* HomeScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; - 1863A48427BAA8A900B52E4D /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = ""; }; - 1863A49327BAAA6700B52E4D /* RoomModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomModel.swift; sourceTree = ""; }; + 18F2BA7327D25B4000DD1988 /* RoomProxyProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxyProtocol.swift; sourceTree = ""; }; + 18F2BA7427D25B4000DD1988 /* RoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = ""; }; + 18F2BA7527D25B4000DD1988 /* MockRoomProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = ""; }; + 18F2BA7727D25B4000DD1988 /* RoomTimelineProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomTimelineProvider.swift; sourceTree = ""; }; + 18F2BA7927D25B4000DD1988 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = ""; }; + 18F2BA7A27D25B4000DD1988 /* UserSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; }; + 18F2BA7B27D25B4000DD1988 /* KeychainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = ""; }; + 18F2BA7C27D25B4000DD1988 /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; + 18F2BA7F27D25B4000DD1988 /* NavigationRouterStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = ""; }; + 18F2BA8027D25B4000DD1988 /* NavigationRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = ""; }; + 18F2BA8127D25B4000DD1988 /* RootRouterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = ""; }; + 18F2BA8227D25B4000DD1988 /* NavigationRouterStoreProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterStoreProtocol.swift; sourceTree = ""; }; + 18F2BA8327D25B4000DD1988 /* RootRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = ""; }; + 18F2BA8427D25B4000DD1988 /* Presentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = ""; }; + 18F2BA8527D25B4000DD1988 /* NavigationModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = ""; }; + 18F2BA8627D25B4000DD1988 /* NavigationRouterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterType.swift; sourceTree = ""; }; + 18F2BA8927D25B4000DD1988 /* RoundedToastView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedToastView.swift; sourceTree = ""; }; + 18F2BA8A27D25B4000DD1988 /* RectangleToastView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RectangleToastView.swift; sourceTree = ""; }; + 18F2BA8B27D25B4000DD1988 /* ActivityPresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityPresentable.swift; sourceTree = ""; }; + 18F2BA8C27D25B4000DD1988 /* ActivityCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityCenter.swift; sourceTree = ""; }; + 18F2BA8D27D25B4000DD1988 /* LabelledActivityIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelledActivityIndicatorView.swift; sourceTree = ""; }; + 18F2BA8F27D25B4000DD1988 /* ToastActivityPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToastActivityPresenter.swift; sourceTree = ""; }; + 18F2BA9027D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullscreenLoadingActivityPresenter.swift; sourceTree = ""; }; + 18F2BA9127D25B4000DD1988 /* Activity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Activity.swift; sourceTree = ""; }; + 18F2BA9227D25B4000DD1988 /* ActivityRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityRequest.swift; sourceTree = ""; }; + 18F2BA9327D25B4000DD1988 /* ActivityDismissal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityDismissal.swift; sourceTree = ""; }; + 18F2BA9427D25B4000DD1988 /* MXLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = ""; }; + 18F2BA9627D25B4000DD1988 /* WeakDictionaryKeyReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionaryKeyReference.swift; sourceTree = ""; }; + 18F2BA9727D25B4000DD1988 /* WeakDictionaryReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionaryReference.swift; sourceTree = ""; }; + 18F2BA9827D25B4000DD1988 /* WeakDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionary.swift; sourceTree = ""; }; + 18F2BA9927D25B4000DD1988 /* WeakKeyDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakKeyDictionary.swift; sourceTree = ""; }; + 18F2BA9A27D25B4000DD1988 /* Coordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; + 18F2BA9D27D25B4000DD1988 /* StateStoreViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = ""; }; + 18F2BA9E27D25B4000DD1988 /* BindableState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = ""; }; + 18F2BAA127D25B4000DD1988 /* SplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; + 18F2BAA227D25B4000DD1988 /* SplashViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SplashViewController.xib; sourceTree = ""; }; + 18F2BAA427D25B4000DD1988 /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = ""; }; + 18F2BAA727D25B4000DD1988 /* HomeScreenUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenUITests.swift; sourceTree = ""; }; + 18F2BAA927D25B4000DD1988 /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = ""; }; + 18F2BAAA27D25B4000DD1988 /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = ""; }; + 18F2BAAB27D25B4000DD1988 /* HomeScreenModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenModels.swift; sourceTree = ""; }; + 18F2BAAD27D25B4000DD1988 /* HomeScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; + 18F2BAAE27D25B4000DD1988 /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = ""; }; + 18F2BAC127D25B4000DD1988 /* RoomScreenUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = ""; }; + 18F2BAC327D25B4000DD1988 /* RoomScreenViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelTests.swift; sourceTree = ""; }; + 18F2BAC427D25B4000DD1988 /* RoomScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomScreenCoordinator.swift; sourceTree = ""; }; + 18F2BAC527D25B4000DD1988 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = ""; }; + 18F2BAC627D25B4000DD1988 /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = ""; }; + 18F2BAC827D25B4000DD1988 /* RoomScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomScreen.swift; sourceTree = ""; }; + 18F2BACA27D25B4000DD1988 /* RoomScreenModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = ""; }; + 18F2BACC27D25B4000DD1988 /* LoginScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModel.swift; sourceTree = ""; }; + 18F2BACF27D25B4000DD1988 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = ""; }; + 18F2BAD127D25B4000DD1988 /* LoginScreenViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelTests.swift; sourceTree = ""; }; + 18F2BAD227D25B4000DD1988 /* LoginScreenViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelProtocol.swift; sourceTree = ""; }; + 18F2BAD327D25B4000DD1988 /* LoginScreenModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = ""; }; + 18F2BAD427D25B4000DD1988 /* LoginScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenCoordinator.swift; sourceTree = ""; }; + 18F2BAD627D25B4000DD1988 /* LoginScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; + 18F2BB1927D25BE800DD1988 /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = ""; }; + 18F2BB2327D262A900DD1988 /* RoomTimelineItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemProtocol.swift; sourceTree = ""; }; + 18F2BB2527D262FA00DD1988 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = ""; }; + 18F2BB2727D2647A00DD1988 /* MockRoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineController.swift; sourceTree = ""; }; + 18F2BB2927D2648900DD1988 /* RoomTimelineControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerProtocol.swift; sourceTree = ""; }; 18FE279627C7B85300016375 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ @@ -179,39 +205,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 182BC46327C4CD6D00A30C33 /* Activity */ = { - isa = PBXGroup; - children = ( - 182BC47827C4CE2200A30C33 /* LabelledActivityIndicatorView.swift */, - 182BC46427C4CD6D00A30C33 /* Toasts */, - 182BC46727C4CD6D00A30C33 /* ActivityPresentable.swift */, - 182BC46827C4CD6D00A30C33 /* ActivityCenter.swift */, - 182BC46927C4CD6D00A30C33 /* ActivityPresenters */, - 182BC46C27C4CD6D00A30C33 /* Activity.swift */, - 182BC46D27C4CD6D00A30C33 /* ActivityRequest.swift */, - 182BC46E27C4CD6D00A30C33 /* ActivityDismissal.swift */, - ); - path = Activity; - sourceTree = ""; - }; - 182BC46427C4CD6D00A30C33 /* Toasts */ = { - isa = PBXGroup; - children = ( - 182BC46527C4CD6D00A30C33 /* RoundedToastView.swift */, - 182BC46627C4CD6D00A30C33 /* RectangleToastView.swift */, - ); - path = Toasts; - sourceTree = ""; - }; - 182BC46927C4CD6D00A30C33 /* ActivityPresenters */ = { - isa = PBXGroup; - children = ( - 182BC47A27C4D05200A30C33 /* FullscreenLoadingActivityPresenter.swift */, - 182BC46A27C4CD6D00A30C33 /* ToastActivityPresenter.swift */, - ); - path = ActivityPresenters; - sourceTree = ""; - }; 1850251B27B6918C002E6B18 = { isa = PBXGroup; children = ( @@ -263,7 +256,9 @@ children = ( 1850256527B6A135002E6B18 /* AppDelegate.swift */, 1850256227B6A135002E6B18 /* AppCoordinator.swift */, - 1863A40927BA716A00B52E4D /* Modules */, + 18F2BA7227D25B4000DD1988 /* Services */, + 18F2BA9F27D25B4000DD1988 /* Screens */, + 18F2BA7D27D25B4000DD1988 /* Other */, ); path = Sources; sourceTree = ""; @@ -279,195 +274,287 @@ path = "Supporting Files"; sourceTree = ""; }; - 1863A40927BA716A00B52E4D /* Modules */ = { + 18F2BA7227D25B4000DD1988 /* Services */ = { isa = PBXGroup; children = ( - 1863A45C27BAA5F100B52E4D /* Splash */, - 1863A40D27BA716A00B52E4D /* Authentication */, - 1863A47827BAA8A900B52E4D /* HomeScreen */, - 1863A49227BAAA6700B52E4D /* Models */, - 1863A44027BA79FF00B52E4D /* Routers */, - 1863A41A27BA76B900B52E4D /* Other */, + 18F2BA7827D25B4000DD1988 /* Authentication */, + 18F2BA7627D25B4000DD1988 /* Timeline */, + 18F2BA7327D25B4000DD1988 /* RoomProxyProtocol.swift */, + 18F2BA7427D25B4000DD1988 /* RoomProxy.swift */, + 18F2BA7527D25B4000DD1988 /* MockRoomProxy.swift */, ); - path = Modules; + path = Services; sourceTree = ""; }; - 1863A40D27BA716A00B52E4D /* Authentication */ = { + 18F2BA7627D25B4000DD1988 /* Timeline */ = { isa = PBXGroup; children = ( - 1863A40E27BA716A00B52E4D /* AuthenticationCoordinator.swift */, - 1863A40F27BA716A00B52E4D /* UserSession.swift */, - 1863A41027BA716A00B52E4D /* KeychainController.swift */, - 1863A41127BA716A00B52E4D /* KeychainControllerProtocol.swift */, - 1863A41D27BA784300B52E4D /* LoginScreen */, + 18F2BB2927D2648900DD1988 /* RoomTimelineControllerProtocol.swift */, + 18F2BB1927D25BE800DD1988 /* RoomTimelineController.swift */, + 18F2BB2727D2647A00DD1988 /* MockRoomTimelineController.swift */, + 18F2BA7727D25B4000DD1988 /* RoomTimelineProvider.swift */, + 18F2BB2327D262A900DD1988 /* RoomTimelineItemProtocol.swift */, + 18F2BB2527D262FA00DD1988 /* TextRoomTimelineItem.swift */, + ); + path = Timeline; + sourceTree = ""; + }; + 18F2BA7827D25B4000DD1988 /* Authentication */ = { + isa = PBXGroup; + children = ( + 18F2BA7927D25B4000DD1988 /* AuthenticationCoordinator.swift */, + 18F2BA7A27D25B4000DD1988 /* UserSession.swift */, + 18F2BA7B27D25B4000DD1988 /* KeychainController.swift */, + 18F2BA7C27D25B4000DD1988 /* KeychainControllerProtocol.swift */, ); path = Authentication; sourceTree = ""; }; - 1863A41A27BA76B900B52E4D /* Other */ = { + 18F2BA7D27D25B4000DD1988 /* Other */ = { isa = PBXGroup; children = ( - 182BC46327C4CD6D00A30C33 /* Activity */, - 1863A43D27BA790000B52E4D /* Coordinator.swift */, - 1863A41B27BA76B900B52E4D /* MXLog.swift */, - 1863A43627BA789800B52E4D /* SwiftUI */, - 1863A45127BA7A7800B52E4D /* WeakDictionary */, + 18F2BA7E27D25B4000DD1988 /* Routers */, + 18F2BA8727D25B4000DD1988 /* Activity */, + 18F2BA9427D25B4000DD1988 /* MXLog.swift */, + 18F2BA9527D25B4000DD1988 /* WeakDictionary */, + 18F2BA9A27D25B4000DD1988 /* Coordinator.swift */, + 18F2BA9B27D25B4000DD1988 /* SwiftUI */, ); path = Other; sourceTree = ""; }; - 1863A41D27BA784300B52E4D /* LoginScreen */ = { + 18F2BA7E27D25B4000DD1988 /* Routers */ = { isa = PBXGroup; children = ( - 1863A45A27BA7B4700B52E4D /* LoginScreenCoordinator.swift */, - 1863A42827BA784300B52E4D /* LoginScreenModels.swift */, - 1863A41F27BA784300B52E4D /* LoginScreenViewModel.swift */, - 1863A42727BA784300B52E4D /* LoginScreenViewModelProtocol.swift */, - 1863A42227BA784300B52E4D /* Test */, - 1863A42927BA784300B52E4D /* View */, - ); - path = LoginScreen; - sourceTree = ""; - }; - 1863A42227BA784300B52E4D /* Test */ = { - isa = PBXGroup; - children = ( - 1863A42327BA784300B52E4D /* UI */, - 1863A42527BA784300B52E4D /* Unit */, - ); - path = Test; - sourceTree = ""; - }; - 1863A42327BA784300B52E4D /* UI */ = { - isa = PBXGroup; - children = ( - 1863A42427BA784300B52E4D /* LoginScreenUITests.swift */, - ); - path = UI; - sourceTree = ""; - }; - 1863A42527BA784300B52E4D /* Unit */ = { - isa = PBXGroup; - children = ( - 1863A42627BA784300B52E4D /* LoginScreenViewModelTests.swift */, - ); - path = Unit; - sourceTree = ""; - }; - 1863A42927BA784300B52E4D /* View */ = { - isa = PBXGroup; - children = ( - 1863A42A27BA784300B52E4D /* LoginScreen.swift */, - ); - path = View; - sourceTree = ""; - }; - 1863A43627BA789800B52E4D /* SwiftUI */ = { - isa = PBXGroup; - children = ( - 1863A43727BA789800B52E4D /* ViewModel */, - ); - path = SwiftUI; - sourceTree = ""; - }; - 1863A43727BA789800B52E4D /* ViewModel */ = { - isa = PBXGroup; - children = ( - 1863A43827BA789800B52E4D /* StateStoreViewModel.swift */, - 1863A43927BA789800B52E4D /* BindableState.swift */, - ); - path = ViewModel; - sourceTree = ""; - }; - 1863A44027BA79FF00B52E4D /* Routers */ = { - isa = PBXGroup; - children = ( - 1863A44127BA79FF00B52E4D /* NavigationRouterStore.swift */, - 1863A44227BA79FF00B52E4D /* NavigationRouter.swift */, - 1863A44327BA79FF00B52E4D /* RootRouterType.swift */, - 1863A44427BA79FF00B52E4D /* NavigationRouterStoreProtocol.swift */, - 1863A44527BA79FF00B52E4D /* RootRouter.swift */, - 1863A44627BA79FF00B52E4D /* Presentable.swift */, - 1863A44727BA79FF00B52E4D /* NavigationModule.swift */, - 1863A44827BA79FF00B52E4D /* NavigationRouterType.swift */, + 18F2BA7F27D25B4000DD1988 /* NavigationRouterStore.swift */, + 18F2BA8027D25B4000DD1988 /* NavigationRouter.swift */, + 18F2BA8127D25B4000DD1988 /* RootRouterType.swift */, + 18F2BA8227D25B4000DD1988 /* NavigationRouterStoreProtocol.swift */, + 18F2BA8327D25B4000DD1988 /* RootRouter.swift */, + 18F2BA8427D25B4000DD1988 /* Presentable.swift */, + 18F2BA8527D25B4000DD1988 /* NavigationModule.swift */, + 18F2BA8627D25B4000DD1988 /* NavigationRouterType.swift */, ); path = Routers; sourceTree = ""; }; - 1863A45127BA7A7800B52E4D /* WeakDictionary */ = { + 18F2BA8727D25B4000DD1988 /* Activity */ = { isa = PBXGroup; children = ( - 1863A45227BA7A7800B52E4D /* WeakDictionaryKeyReference.swift */, - 1863A45327BA7A7800B52E4D /* WeakDictionaryReference.swift */, - 1863A45427BA7A7800B52E4D /* WeakDictionary.swift */, - 1863A45527BA7A7800B52E4D /* WeakKeyDictionary.swift */, + 18F2BA8827D25B4000DD1988 /* Toasts */, + 18F2BA8B27D25B4000DD1988 /* ActivityPresentable.swift */, + 18F2BA8C27D25B4000DD1988 /* ActivityCenter.swift */, + 18F2BA8D27D25B4000DD1988 /* LabelledActivityIndicatorView.swift */, + 18F2BA8E27D25B4000DD1988 /* ActivityPresenters */, + 18F2BA9127D25B4000DD1988 /* Activity.swift */, + 18F2BA9227D25B4000DD1988 /* ActivityRequest.swift */, + 18F2BA9327D25B4000DD1988 /* ActivityDismissal.swift */, + ); + path = Activity; + sourceTree = ""; + }; + 18F2BA8827D25B4000DD1988 /* Toasts */ = { + isa = PBXGroup; + children = ( + 18F2BA8927D25B4000DD1988 /* RoundedToastView.swift */, + 18F2BA8A27D25B4000DD1988 /* RectangleToastView.swift */, + ); + path = Toasts; + sourceTree = ""; + }; + 18F2BA8E27D25B4000DD1988 /* ActivityPresenters */ = { + isa = PBXGroup; + children = ( + 18F2BA8F27D25B4000DD1988 /* ToastActivityPresenter.swift */, + 18F2BA9027D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift */, + ); + path = ActivityPresenters; + sourceTree = ""; + }; + 18F2BA9527D25B4000DD1988 /* WeakDictionary */ = { + isa = PBXGroup; + children = ( + 18F2BA9627D25B4000DD1988 /* WeakDictionaryKeyReference.swift */, + 18F2BA9727D25B4000DD1988 /* WeakDictionaryReference.swift */, + 18F2BA9827D25B4000DD1988 /* WeakDictionary.swift */, + 18F2BA9927D25B4000DD1988 /* WeakKeyDictionary.swift */, ); path = WeakDictionary; sourceTree = ""; }; - 1863A45C27BAA5F100B52E4D /* Splash */ = { + 18F2BA9B27D25B4000DD1988 /* SwiftUI */ = { isa = PBXGroup; children = ( - 1863A45D27BAA60300B52E4D /* SplashViewController.swift */, - 1863A45E27BAA60300B52E4D /* SplashViewController.xib */, + 18F2BA9C27D25B4000DD1988 /* ViewModel */, + ); + path = SwiftUI; + sourceTree = ""; + }; + 18F2BA9C27D25B4000DD1988 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 18F2BA9D27D25B4000DD1988 /* StateStoreViewModel.swift */, + 18F2BA9E27D25B4000DD1988 /* BindableState.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 18F2BA9F27D25B4000DD1988 /* Screens */ = { + isa = PBXGroup; + children = ( + 18F2BAA027D25B4000DD1988 /* Splash */, + 18F2BACB27D25B4000DD1988 /* LoginScreen */, + 18F2BAA327D25B4000DD1988 /* HomeScreen */, + 18F2BABE27D25B4000DD1988 /* RoomScreen */, + ); + path = Screens; + sourceTree = ""; + }; + 18F2BAA027D25B4000DD1988 /* Splash */ = { + isa = PBXGroup; + children = ( + 18F2BAA127D25B4000DD1988 /* SplashViewController.swift */, + 18F2BAA227D25B4000DD1988 /* SplashViewController.xib */, ); path = Splash; sourceTree = ""; }; - 1863A47827BAA8A900B52E4D /* HomeScreen */ = { + 18F2BAA327D25B4000DD1988 /* HomeScreen */ = { isa = PBXGroup; children = ( - 1863A47927BAA8A900B52E4D /* HomeScreenCoordinator.swift */, - 1863A48027BAA8A900B52E4D /* HomeScreenModels.swift */, - 1863A48427BAA8A900B52E4D /* HomeScreenViewModel.swift */, - 1863A47F27BAA8A900B52E4D /* HomeScreenViewModelProtocol.swift */, - 1863A47A27BAA8A900B52E4D /* Test */, - 1863A48127BAA8A900B52E4D /* View */, + 18F2BAAB27D25B4000DD1988 /* HomeScreenModels.swift */, + 18F2BAA427D25B4000DD1988 /* HomeScreenCoordinator.swift */, + 18F2BAAA27D25B4000DD1988 /* HomeScreenViewModelProtocol.swift */, + 18F2BAAE27D25B4000DD1988 /* HomeScreenViewModel.swift */, + 18F2BAAC27D25B4000DD1988 /* View */, + 18F2BAA527D25B4000DD1988 /* Test */, ); path = HomeScreen; sourceTree = ""; }; - 1863A47A27BAA8A900B52E4D /* Test */ = { + 18F2BAA527D25B4000DD1988 /* Test */ = { isa = PBXGroup; children = ( - 1863A47B27BAA8A900B52E4D /* UI */, - 1863A47D27BAA8A900B52E4D /* Unit */, + 18F2BAA627D25B4000DD1988 /* UI */, + 18F2BAA827D25B4000DD1988 /* Unit */, ); path = Test; sourceTree = ""; }; - 1863A47B27BAA8A900B52E4D /* UI */ = { + 18F2BAA627D25B4000DD1988 /* UI */ = { isa = PBXGroup; children = ( - 1863A47C27BAA8A900B52E4D /* HomeScreenUITests.swift */, + 18F2BAA727D25B4000DD1988 /* HomeScreenUITests.swift */, ); path = UI; sourceTree = ""; }; - 1863A47D27BAA8A900B52E4D /* Unit */ = { + 18F2BAA827D25B4000DD1988 /* Unit */ = { isa = PBXGroup; children = ( - 1863A47E27BAA8A900B52E4D /* HomeScreenViewModelTests.swift */, + 18F2BAA927D25B4000DD1988 /* HomeScreenViewModelTests.swift */, ); path = Unit; sourceTree = ""; }; - 1863A48127BAA8A900B52E4D /* View */ = { + 18F2BAAC27D25B4000DD1988 /* View */ = { isa = PBXGroup; children = ( - 1863A48227BAA8A900B52E4D /* HomeScreen.swift */, + 18F2BAAD27D25B4000DD1988 /* HomeScreen.swift */, ); path = View; sourceTree = ""; }; - 1863A49227BAAA6700B52E4D /* Models */ = { + 18F2BABE27D25B4000DD1988 /* RoomScreen */ = { isa = PBXGroup; children = ( - 1863A49327BAAA6700B52E4D /* RoomModel.swift */, - 182BC41F27BE667200A30C33 /* RoomModelProtocol.swift */, - 182BC42127BE6C6900A30C33 /* MockRoomModel.swift */, + 18F2BACA27D25B4000DD1988 /* RoomScreenModels.swift */, + 18F2BAC427D25B4000DD1988 /* RoomScreenCoordinator.swift */, + 18F2BAC627D25B4000DD1988 /* RoomScreenViewModelProtocol.swift */, + 18F2BAC527D25B4000DD1988 /* RoomScreenViewModel.swift */, + 18F2BAC727D25B4000DD1988 /* View */, + 18F2BABF27D25B4000DD1988 /* Test */, ); - path = Models; + path = RoomScreen; + sourceTree = ""; + }; + 18F2BABF27D25B4000DD1988 /* Test */ = { + isa = PBXGroup; + children = ( + 18F2BAC027D25B4000DD1988 /* UI */, + 18F2BAC227D25B4000DD1988 /* Unit */, + ); + path = Test; + sourceTree = ""; + }; + 18F2BAC027D25B4000DD1988 /* UI */ = { + isa = PBXGroup; + children = ( + 18F2BAC127D25B4000DD1988 /* RoomScreenUITests.swift */, + ); + path = UI; + sourceTree = ""; + }; + 18F2BAC227D25B4000DD1988 /* Unit */ = { + isa = PBXGroup; + children = ( + 18F2BAC327D25B4000DD1988 /* RoomScreenViewModelTests.swift */, + ); + path = Unit; + sourceTree = ""; + }; + 18F2BAC727D25B4000DD1988 /* View */ = { + isa = PBXGroup; + children = ( + 18F2BAC827D25B4000DD1988 /* RoomScreen.swift */, + ); + path = View; + sourceTree = ""; + }; + 18F2BACB27D25B4000DD1988 /* LoginScreen */ = { + isa = PBXGroup; + children = ( + 18F2BAD327D25B4000DD1988 /* LoginScreenModels.swift */, + 18F2BAD427D25B4000DD1988 /* LoginScreenCoordinator.swift */, + 18F2BAD227D25B4000DD1988 /* LoginScreenViewModelProtocol.swift */, + 18F2BACC27D25B4000DD1988 /* LoginScreenViewModel.swift */, + 18F2BACD27D25B4000DD1988 /* Test */, + 18F2BAD527D25B4000DD1988 /* View */, + ); + path = LoginScreen; + sourceTree = ""; + }; + 18F2BACD27D25B4000DD1988 /* Test */ = { + isa = PBXGroup; + children = ( + 18F2BACE27D25B4000DD1988 /* UI */, + 18F2BAD027D25B4000DD1988 /* Unit */, + ); + path = Test; + sourceTree = ""; + }; + 18F2BACE27D25B4000DD1988 /* UI */ = { + isa = PBXGroup; + children = ( + 18F2BACF27D25B4000DD1988 /* LoginScreenUITests.swift */, + ); + path = UI; + sourceTree = ""; + }; + 18F2BAD027D25B4000DD1988 /* Unit */ = { + isa = PBXGroup; + children = ( + 18F2BAD127D25B4000DD1988 /* LoginScreenViewModelTests.swift */, + ); + path = Unit; + sourceTree = ""; + }; + 18F2BAD527D25B4000DD1988 /* View */ = { + isa = PBXGroup; + children = ( + 18F2BAD627D25B4000DD1988 /* LoginScreen.swift */, + ); + path = View; sourceTree = ""; }; /* End PBXGroup section */ @@ -589,7 +676,7 @@ buildActionMask = 2147483647; files = ( 1850257127B6A135002E6B18 /* LaunchScreen.storyboard in Resources */, - 1863A46027BAA60300B52E4D /* SplashViewController.xib in Resources */, + 18F2BAFA27D25B4000DD1988 /* SplashViewController.xib in Resources */, 1850257027B6A135002E6B18 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -635,52 +722,63 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1863A44B27BA79FF00B52E4D /* RootRouterType.swift in Sources */, - 182BC47327C4CD6D00A30C33 /* ToastActivityPresenter.swift in Sources */, - 1863A48C27BAA8A900B52E4D /* HomeScreenViewModel.swift in Sources */, - 1863A41727BA716A00B52E4D /* KeychainControllerProtocol.swift in Sources */, - 1863A45B27BA7B4700B52E4D /* LoginScreenCoordinator.swift in Sources */, - 182BC47127C4CD6D00A30C33 /* ActivityPresentable.swift in Sources */, - 1863A41427BA716A00B52E4D /* AuthenticationCoordinator.swift in Sources */, - 1863A43B27BA789800B52E4D /* BindableState.swift in Sources */, - 182BC47027C4CD6D00A30C33 /* RectangleToastView.swift in Sources */, - 1863A42C27BA784300B52E4D /* LoginScreenViewModel.swift in Sources */, - 1863A44F27BA79FF00B52E4D /* NavigationModule.swift in Sources */, - 1863A43027BA784300B52E4D /* LoginScreenViewModelProtocol.swift in Sources */, - 1863A48827BAA8A900B52E4D /* HomeScreenViewModelProtocol.swift in Sources */, - 1863A44D27BA79FF00B52E4D /* RootRouter.swift in Sources */, - 182BC47627C4CD6D00A30C33 /* ActivityRequest.swift in Sources */, - 1863A45F27BAA60300B52E4D /* SplashViewController.swift in Sources */, - 1863A48A27BAA8A900B52E4D /* HomeScreen.swift in Sources */, - 1863A44927BA79FF00B52E4D /* NavigationRouterStore.swift in Sources */, - 1863A45727BA7A7800B52E4D /* WeakDictionaryReference.swift in Sources */, - 1863A48927BAA8A900B52E4D /* HomeScreenModels.swift in Sources */, - 1863A45027BA79FF00B52E4D /* NavigationRouterType.swift in Sources */, - 182BC42027BE667200A30C33 /* RoomModelProtocol.swift in Sources */, - 1863A45627BA7A7800B52E4D /* WeakDictionaryKeyReference.swift in Sources */, - 182BC47927C4CE2200A30C33 /* LabelledActivityIndicatorView.swift in Sources */, - 1863A49427BAAA6700B52E4D /* RoomModel.swift in Sources */, + 18F2BADE27D25B4000DD1988 /* KeychainControllerProtocol.swift in Sources */, + 18F2BAED27D25B4000DD1988 /* FullscreenLoadingActivityPresenter.swift in Sources */, + 18F2BB0F27D25B4000DD1988 /* RoomScreen.swift in Sources */, + 18F2BAFF27D25B4000DD1988 /* HomeScreenModels.swift in Sources */, + 18F2BB1527D25B4000DD1988 /* LoginScreenViewModelProtocol.swift in Sources */, + 18F2BAEB27D25B4000DD1988 /* LabelledActivityIndicatorView.swift in Sources */, + 18F2BAE427D25B4000DD1988 /* Presentable.swift in Sources */, + 18F2BAF927D25B4000DD1988 /* SplashViewController.swift in Sources */, + 18F2BAE327D25B4000DD1988 /* RootRouter.swift in Sources */, + 18F2BAE527D25B4000DD1988 /* NavigationModule.swift in Sources */, + 18F2BB1227D25B4000DD1988 /* LoginScreenViewModel.swift in Sources */, + 18F2BAD927D25B4000DD1988 /* MockRoomProxy.swift in Sources */, + 18F2BAE727D25B4000DD1988 /* RoundedToastView.swift in Sources */, + 18F2BAF227D25B4000DD1988 /* WeakDictionaryKeyReference.swift in Sources */, + 18F2BB2427D262A900DD1988 /* RoomTimelineItemProtocol.swift in Sources */, + 18F2BAE027D25B4000DD1988 /* NavigationRouter.swift in Sources */, + 18F2BAF627D25B4000DD1988 /* Coordinator.swift in Sources */, + 18F2BAEA27D25B4000DD1988 /* ActivityCenter.swift in Sources */, + 18F2BB1A27D25BE800DD1988 /* RoomTimelineController.swift in Sources */, + 18F2BAF327D25B4000DD1988 /* WeakDictionaryReference.swift in Sources */, + 18F2BB2A27D2648900DD1988 /* RoomTimelineControllerProtocol.swift in Sources */, + 18F2BAF127D25B4000DD1988 /* MXLog.swift in Sources */, + 18F2BAF727D25B4000DD1988 /* StateStoreViewModel.swift in Sources */, + 18F2BAF427D25B4000DD1988 /* WeakDictionary.swift in Sources */, + 18F2BB1127D25B4000DD1988 /* RoomScreenModels.swift in Sources */, + 18F2BADB27D25B4000DD1988 /* AuthenticationCoordinator.swift in Sources */, 1850256F27B6A135002E6B18 /* AppDelegate.swift in Sources */, - 182BC47727C4CD6D00A30C33 /* ActivityDismissal.swift in Sources */, - 1863A41627BA716A00B52E4D /* KeychainController.swift in Sources */, - 1863A41527BA716A00B52E4D /* UserSession.swift in Sources */, - 1863A43A27BA789800B52E4D /* StateStoreViewModel.swift in Sources */, - 1863A45827BA7A7800B52E4D /* WeakDictionary.swift in Sources */, - 1863A43227BA784300B52E4D /* LoginScreen.swift in Sources */, - 1863A48527BAA8A900B52E4D /* HomeScreenCoordinator.swift in Sources */, - 1863A43F27BA790000B52E4D /* Coordinator.swift in Sources */, - 1863A41C27BA76B900B52E4D /* MXLog.swift in Sources */, - 182BC46F27C4CD6D00A30C33 /* RoundedToastView.swift in Sources */, - 182BC47527C4CD6D00A30C33 /* Activity.swift in Sources */, - 182BC47B27C4D05200A30C33 /* FullscreenLoadingActivityPresenter.swift in Sources */, - 1863A44E27BA79FF00B52E4D /* Presentable.swift in Sources */, - 182BC42227BE6C6900A30C33 /* MockRoomModel.swift in Sources */, + 18F2BAE627D25B4000DD1988 /* NavigationRouterType.swift in Sources */, + 18F2BAE927D25B4000DD1988 /* ActivityPresentable.swift in Sources */, + 18F2BAF827D25B4000DD1988 /* BindableState.swift in Sources */, + 18F2BAD827D25B4000DD1988 /* RoomProxy.swift in Sources */, + 18F2BB1827D25B4000DD1988 /* LoginScreen.swift in Sources */, + 18F2BAE227D25B4000DD1988 /* NavigationRouterStoreProtocol.swift in Sources */, + 18F2BAD727D25B4000DD1988 /* RoomProxyProtocol.swift in Sources */, + 18F2BB1727D25B4000DD1988 /* LoginScreenCoordinator.swift in Sources */, + 18F2BAF527D25B4000DD1988 /* WeakKeyDictionary.swift in Sources */, + 18F2BADF27D25B4000DD1988 /* NavigationRouterStore.swift in Sources */, + 18F2BAFE27D25B4000DD1988 /* HomeScreenViewModelProtocol.swift in Sources */, + 18F2BAE827D25B4000DD1988 /* RectangleToastView.swift in Sources */, + 18F2BB2627D262FA00DD1988 /* TextRoomTimelineItem.swift in Sources */, + 18F2BB1627D25B4000DD1988 /* LoginScreenModels.swift in Sources */, + 18F2BADA27D25B4000DD1988 /* RoomTimelineProvider.swift in Sources */, + 18F2BB0027D25B4000DD1988 /* HomeScreen.swift in Sources */, + 18F2BB2827D2647A00DD1988 /* MockRoomTimelineController.swift in Sources */, + 18F2BB0127D25B4000DD1988 /* HomeScreenViewModel.swift in Sources */, + 18F2BAF027D25B4000DD1988 /* ActivityDismissal.swift in Sources */, + 18F2BADD27D25B4000DD1988 /* KeychainController.swift in Sources */, + 18F2BAFB27D25B4000DD1988 /* HomeScreenCoordinator.swift in Sources */, + 18F2BB0C27D25B4000DD1988 /* RoomScreenCoordinator.swift in Sources */, + 18F2BB0E27D25B4000DD1988 /* RoomScreenViewModelProtocol.swift in Sources */, + 18F2BB0D27D25B4000DD1988 /* RoomScreenViewModel.swift in Sources */, + 18F2BAE127D25B4000DD1988 /* RootRouterType.swift in Sources */, 1850256C27B6A135002E6B18 /* AppCoordinator.swift in Sources */, - 1863A43127BA784300B52E4D /* LoginScreenModels.swift in Sources */, - 1863A44A27BA79FF00B52E4D /* NavigationRouter.swift in Sources */, - 182BC47227C4CD6D00A30C33 /* ActivityCenter.swift in Sources */, - 1863A45927BA7A7800B52E4D /* WeakKeyDictionary.swift in Sources */, - 1863A44C27BA79FF00B52E4D /* NavigationRouterStoreProtocol.swift in Sources */, + 18F2BADC27D25B4000DD1988 /* UserSession.swift in Sources */, + 18F2BAEF27D25B4000DD1988 /* ActivityRequest.swift in Sources */, + 18F2BAEE27D25B4000DD1988 /* Activity.swift in Sources */, + 18F2BAEC27D25B4000DD1988 /* ToastActivityPresenter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -688,8 +786,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1863A48F27BAA8CC00B52E4D /* HomeScreenViewModelTests.swift in Sources */, - 1863A43427BA786400B52E4D /* LoginScreenViewModelTests.swift in Sources */, + 18F2BB2027D25D3400DD1988 /* RoomScreenUITests.swift in Sources */, + 18F2BB1E27D25D2200DD1988 /* HomeScreenViewModelTests.swift in Sources */, + 18F2BB1D27D25D1C00DD1988 /* LoginScreenViewModelTests.swift in Sources */, 1850253F27B6918D002E6B18 /* ElementXTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -698,10 +797,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 18F2BB2127D25D3C00DD1988 /* HomeScreenUITests.swift in Sources */, 1850254B27B6918D002E6B18 /* ElementXUITestsLaunchTests.swift in Sources */, - 1863A48E27BAA8C800B52E4D /* HomeScreenUITests.swift in Sources */, - 1863A43527BA788500B52E4D /* LoginScreenUITests.swift in Sources */, 1850254927B6918D002E6B18 /* ElementXUITests.swift in Sources */, + 18F2BB2227D25D4600DD1988 /* LoginScreenUITests.swift in Sources */, + 18F2BB1F27D25D3100DD1988 /* RoomScreenViewModelTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a2a841849..39fff9cd9 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -24,7 +24,7 @@ "repositoryURL": "https://github.com/matrix-org/matrix-rust-components-swift.git", "state": { "branch": "main", - "revision": "34aa07d11d0e29145b92f42b9d86aa140d598e19", + "revision": "497122432c79488e370df2164ae5637f32f82ca3", "version": null } }, diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/AppCoordinator.swift index ce848d3a0..1733a6e54 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/AppCoordinator.swift @@ -27,7 +27,6 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { init() { splashViewController = SplashViewController() mainNavigationController = UINavigationController(rootViewController: splashViewController) - mainNavigationController.navigationBar.isHidden = true window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = mainNavigationController @@ -89,6 +88,8 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { switch result { case .logout: self?.authenticationCoordinator.logout() + case .selectRoom(let roomIdentifier): + self?.presentRoomWithIdentifier(roomIdentifier) } } @@ -96,6 +97,35 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { navigationRouter.setRootModule(coordinator) } + private func presentRoomWithIdentifier(_ roomIdentifier: String) { + guard let userSession = authenticationCoordinator.userSession else { + fatalError("User session should be already setup at this point") + } + + showLoadingIndicator() + + userSession.getRoomList { [weak self] rooms in + guard let self = self else { return } + + self.hideLoadingIndicator() + + guard let roomProxy = rooms.filter({ $0.id == roomIdentifier}).first else { + MXLog.error("Invalid room identifier: \(roomIdentifier)") + return + } + + let parameters = RoomScreenCoordinatorParameters(roomProxy: roomProxy) + let coordinator = RoomScreenCoordinator(parameters: parameters) + + coordinator.completion = { [weak self] result in + + } + + self.add(childCoordinator: coordinator) + self.navigationRouter.push(coordinator) + } + } + private func showLoadingIndicator() { let presenter = FullscreenLoadingActivityPresenter(label: "Loading", on: mainNavigationController) diff --git a/ElementX/Sources/Modules/Models/RoomModel.swift b/ElementX/Sources/Modules/Models/RoomModel.swift deleted file mode 100644 index 8aff42f23..000000000 --- a/ElementX/Sources/Modules/Models/RoomModel.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// RoomModel.swift -// ElementX -// -// Created by Stefan Ceriu on 14.02.2022. -// - -import Foundation -import UIKit -import MatrixRustSDK - -enum RoomModelError: Error { - case failedRetrievingDisplayName - case failedRetrievingAvatar -} - -struct RoomModel: RoomModelProtocol { - - private let room: Room - - init(room: Room) { - self.room = room - } - - var identifier: String { - return room.identifier() - } - - var isDirect: Bool { - return room.isDirect() - } - - var isPublic: Bool { - return room.isPublic() - } - - var isSpace: Bool { - return room.isSpace() - } - - var isEncrypted: Bool { - return room.isEncrypted() - } - - var name: String? { - return room.name() - } - - var displayName: String { - do { - return try room.displayName() - } catch { - MXLog.error("Failed retrieving room name with error: \(error)") - return "Error" - } - } - - var topic: String? { - return room.topic() - } - - var lastMessage: String? { - guard let lastMessage = try? room.messages().last else { - return "Last message unknown" - } - - return "\(lastMessage.sender()): \(lastMessage.content())" - } - - var avatarURL: URL? { - guard let urlString = room.avatarUrl() else { - return nil - } - - return URL(string: urlString) - } - - func loadDisplayName(_ completion: @escaping (Result) -> Void) { - DispatchQueue.global(qos: .background).async { - do { - let displayName = try room.displayName() - - DispatchQueue.main.async { - completion(.success(displayName)) - } - } catch { - DispatchQueue.main.async { - completion(.failure(RoomModelError.failedRetrievingDisplayName)) - } - } - } - } - - func loadAvatar(_ completion: @escaping (Result) -> Void) { - DispatchQueue.global(qos: .background).async { - do { - let avatarData = try room.avatar() - - DispatchQueue.main.async { - completion(.success(UIImage(data: Data(bytes: avatarData, count: avatarData.count)))) - } - } catch { - DispatchQueue.main.async { - completion(.failure(RoomModelError.failedRetrievingAvatar)) - } - } - } - } -} diff --git a/ElementX/Sources/Modules/Other/Activity/Activity.swift b/ElementX/Sources/Other/Activity/Activity.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/Activity.swift rename to ElementX/Sources/Other/Activity/Activity.swift diff --git a/ElementX/Sources/Modules/Other/Activity/ActivityCenter.swift b/ElementX/Sources/Other/Activity/ActivityCenter.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/ActivityCenter.swift rename to ElementX/Sources/Other/Activity/ActivityCenter.swift diff --git a/ElementX/Sources/Modules/Other/Activity/ActivityDismissal.swift b/ElementX/Sources/Other/Activity/ActivityDismissal.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/ActivityDismissal.swift rename to ElementX/Sources/Other/Activity/ActivityDismissal.swift diff --git a/ElementX/Sources/Modules/Other/Activity/ActivityPresentable.swift b/ElementX/Sources/Other/Activity/ActivityPresentable.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/ActivityPresentable.swift rename to ElementX/Sources/Other/Activity/ActivityPresentable.swift diff --git a/ElementX/Sources/Modules/Other/Activity/ActivityPresenters/FullscreenLoadingActivityPresenter.swift b/ElementX/Sources/Other/Activity/ActivityPresenters/FullscreenLoadingActivityPresenter.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/ActivityPresenters/FullscreenLoadingActivityPresenter.swift rename to ElementX/Sources/Other/Activity/ActivityPresenters/FullscreenLoadingActivityPresenter.swift diff --git a/ElementX/Sources/Modules/Other/Activity/ActivityPresenters/ToastActivityPresenter.swift b/ElementX/Sources/Other/Activity/ActivityPresenters/ToastActivityPresenter.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/ActivityPresenters/ToastActivityPresenter.swift rename to ElementX/Sources/Other/Activity/ActivityPresenters/ToastActivityPresenter.swift diff --git a/ElementX/Sources/Modules/Other/Activity/ActivityRequest.swift b/ElementX/Sources/Other/Activity/ActivityRequest.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/ActivityRequest.swift rename to ElementX/Sources/Other/Activity/ActivityRequest.swift diff --git a/ElementX/Sources/Modules/Other/Activity/LabelledActivityIndicatorView.swift b/ElementX/Sources/Other/Activity/LabelledActivityIndicatorView.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/LabelledActivityIndicatorView.swift rename to ElementX/Sources/Other/Activity/LabelledActivityIndicatorView.swift diff --git a/ElementX/Sources/Modules/Other/Activity/Toasts/RectangleToastView.swift b/ElementX/Sources/Other/Activity/Toasts/RectangleToastView.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/Toasts/RectangleToastView.swift rename to ElementX/Sources/Other/Activity/Toasts/RectangleToastView.swift diff --git a/ElementX/Sources/Modules/Other/Activity/Toasts/RoundedToastView.swift b/ElementX/Sources/Other/Activity/Toasts/RoundedToastView.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Activity/Toasts/RoundedToastView.swift rename to ElementX/Sources/Other/Activity/Toasts/RoundedToastView.swift diff --git a/ElementX/Sources/Modules/Other/Coordinator.swift b/ElementX/Sources/Other/Coordinator.swift similarity index 100% rename from ElementX/Sources/Modules/Other/Coordinator.swift rename to ElementX/Sources/Other/Coordinator.swift diff --git a/ElementX/Sources/Modules/Other/MXLog.swift b/ElementX/Sources/Other/MXLog.swift similarity index 100% rename from ElementX/Sources/Modules/Other/MXLog.swift rename to ElementX/Sources/Other/MXLog.swift diff --git a/ElementX/Sources/Modules/Routers/NavigationModule.swift b/ElementX/Sources/Other/Routers/NavigationModule.swift similarity index 100% rename from ElementX/Sources/Modules/Routers/NavigationModule.swift rename to ElementX/Sources/Other/Routers/NavigationModule.swift diff --git a/ElementX/Sources/Modules/Routers/NavigationRouter.swift b/ElementX/Sources/Other/Routers/NavigationRouter.swift similarity index 100% rename from ElementX/Sources/Modules/Routers/NavigationRouter.swift rename to ElementX/Sources/Other/Routers/NavigationRouter.swift diff --git a/ElementX/Sources/Modules/Routers/NavigationRouterStore.swift b/ElementX/Sources/Other/Routers/NavigationRouterStore.swift similarity index 100% rename from ElementX/Sources/Modules/Routers/NavigationRouterStore.swift rename to ElementX/Sources/Other/Routers/NavigationRouterStore.swift diff --git a/ElementX/Sources/Modules/Routers/NavigationRouterStoreProtocol.swift b/ElementX/Sources/Other/Routers/NavigationRouterStoreProtocol.swift similarity index 100% rename from ElementX/Sources/Modules/Routers/NavigationRouterStoreProtocol.swift rename to ElementX/Sources/Other/Routers/NavigationRouterStoreProtocol.swift diff --git a/ElementX/Sources/Modules/Routers/NavigationRouterType.swift b/ElementX/Sources/Other/Routers/NavigationRouterType.swift similarity index 100% rename from ElementX/Sources/Modules/Routers/NavigationRouterType.swift rename to ElementX/Sources/Other/Routers/NavigationRouterType.swift diff --git a/ElementX/Sources/Modules/Routers/Presentable.swift b/ElementX/Sources/Other/Routers/Presentable.swift similarity index 100% rename from ElementX/Sources/Modules/Routers/Presentable.swift rename to ElementX/Sources/Other/Routers/Presentable.swift diff --git a/ElementX/Sources/Modules/Routers/RootRouter.swift b/ElementX/Sources/Other/Routers/RootRouter.swift similarity index 100% rename from ElementX/Sources/Modules/Routers/RootRouter.swift rename to ElementX/Sources/Other/Routers/RootRouter.swift diff --git a/ElementX/Sources/Modules/Routers/RootRouterType.swift b/ElementX/Sources/Other/Routers/RootRouterType.swift similarity index 100% rename from ElementX/Sources/Modules/Routers/RootRouterType.swift rename to ElementX/Sources/Other/Routers/RootRouterType.swift diff --git a/ElementX/Sources/Modules/Other/SwiftUI/ViewModel/BindableState.swift b/ElementX/Sources/Other/SwiftUI/ViewModel/BindableState.swift similarity index 100% rename from ElementX/Sources/Modules/Other/SwiftUI/ViewModel/BindableState.swift rename to ElementX/Sources/Other/SwiftUI/ViewModel/BindableState.swift diff --git a/ElementX/Sources/Modules/Other/SwiftUI/ViewModel/StateStoreViewModel.swift b/ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift similarity index 100% rename from ElementX/Sources/Modules/Other/SwiftUI/ViewModel/StateStoreViewModel.swift rename to ElementX/Sources/Other/SwiftUI/ViewModel/StateStoreViewModel.swift diff --git a/ElementX/Sources/Modules/Other/WeakDictionary/WeakDictionary.swift b/ElementX/Sources/Other/WeakDictionary/WeakDictionary.swift similarity index 100% rename from ElementX/Sources/Modules/Other/WeakDictionary/WeakDictionary.swift rename to ElementX/Sources/Other/WeakDictionary/WeakDictionary.swift diff --git a/ElementX/Sources/Modules/Other/WeakDictionary/WeakDictionaryKeyReference.swift b/ElementX/Sources/Other/WeakDictionary/WeakDictionaryKeyReference.swift similarity index 100% rename from ElementX/Sources/Modules/Other/WeakDictionary/WeakDictionaryKeyReference.swift rename to ElementX/Sources/Other/WeakDictionary/WeakDictionaryKeyReference.swift diff --git a/ElementX/Sources/Modules/Other/WeakDictionary/WeakDictionaryReference.swift b/ElementX/Sources/Other/WeakDictionary/WeakDictionaryReference.swift similarity index 100% rename from ElementX/Sources/Modules/Other/WeakDictionary/WeakDictionaryReference.swift rename to ElementX/Sources/Other/WeakDictionary/WeakDictionaryReference.swift diff --git a/ElementX/Sources/Modules/Other/WeakDictionary/WeakKeyDictionary.swift b/ElementX/Sources/Other/WeakDictionary/WeakKeyDictionary.swift similarity index 100% rename from ElementX/Sources/Modules/Other/WeakDictionary/WeakKeyDictionary.swift rename to ElementX/Sources/Other/WeakDictionary/WeakKeyDictionary.swift diff --git a/ElementX/Sources/Modules/HomeScreen/HomeScreenCoordinator.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift similarity index 93% rename from ElementX/Sources/Modules/HomeScreen/HomeScreenCoordinator.swift rename to ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift index ac47ba31e..79b142fef 100644 --- a/ElementX/Sources/Modules/HomeScreen/HomeScreenCoordinator.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenCoordinator.swift @@ -24,6 +24,7 @@ struct HomeScreenCoordinatorParameters { enum HomeScreenCoordinatorResult { case logout + case selectRoom(roomIdentifier: String) } final class HomeScreenCoordinator: Coordinator, Presentable { @@ -70,12 +71,14 @@ final class HomeScreenCoordinator: Coordinator, Presentable { break } }) + case .selectRoom(let roomIdentifier): + self.completion?(.selectRoom(roomIdentifier: roomIdentifier)) } } parameters.userSession.callbacks.sink { [weak self] result in switch result { - case .updatedData: + case .updatedRoomsList: self?.updateRoomsList() } }.store(in: &cancellables) diff --git a/ElementX/Sources/Modules/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift similarity index 82% rename from ElementX/Sources/Modules/HomeScreen/HomeScreenModels.swift rename to ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 82ed9f716..9db2e4846 100644 --- a/ElementX/Sources/Modules/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -20,12 +20,14 @@ import UIKit enum HomeScreenViewModelResult { case logout case loadUserAvatar + case selectRoom(roomIdentifier: String) } enum HomeScreenViewAction { case logout case loadUserAvatar - case loadRoomData(roomId: String) + case loadRoomData(roomIdentifier: String) + case selectRoom(roomIdentifier: String) } struct HomeScreenViewState: BindableState { @@ -65,3 +67,11 @@ struct HomeScreenRoom: Identifiable { let isDirect: Bool let isEncrypted: Bool } + +extension MutableCollection where Element == HomeScreenRoom { + mutating func updateEach(_ update: (inout Element) -> Void) { + for index in indices { + update(&self[index]) + } + } +} diff --git a/ElementX/Sources/Modules/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift similarity index 69% rename from ElementX/Sources/Modules/HomeScreen/HomeScreenViewModel.swift rename to ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index ab1a99ca4..bd3a953de 100644 --- a/ElementX/Sources/Modules/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -15,6 +15,7 @@ // import SwiftUI +import Combine import Kingfisher @available(iOS 14, *) @@ -24,19 +25,15 @@ typealias HomeScreenViewModelType = StateStoreViewModel() + private var roomList: [RoomProxyProtocol]? { didSet { self.state.isLoadingRooms = (roomList == nil) } } + private let imageCache: ImageCache - // MARK: Public - var completion: ((HomeScreenViewModelResult) -> Void)? // MARK: - Setup @@ -52,23 +49,38 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol switch viewAction { case .logout: self.completion?(.logout) - case .loadRoomData(let roomId): - self.loadAvatarForRoomWithIdentifier(roomId) - self.loadRoomDisplayNameForRoomWithIdentifier(roomId) + case .loadRoomData(let roomIdentifier): + self.loadAvatarForRoomWithIdentifier(roomIdentifier) + self.loadRoomDisplayNameForRoomWithIdentifier(roomIdentifier) case .loadUserAvatar: self.completion?(.loadUserAvatar) + case .selectRoom(let roomIdentifier): + self.completion?(.selectRoom(roomIdentifier: roomIdentifier)) } } - func updateWithRoomList(_ roomList: [RoomModelProtocol]) { + func updateWithRoomList(_ roomList: [RoomProxyProtocol]) { + + roomUpdateListeners.removeAll() + self.roomList = roomList - state.rooms = roomList.map { roomModel in - HomeScreenRoom(id: roomModel.identifier, - displayName: roomModel.name, - topic: roomModel.topic, - lastMessage: roomModel.lastMessage, - isDirect: roomModel.isDirect, - isEncrypted: roomModel.isEncrypted) + + roomList.forEach({ roomProxy in + roomProxy.paginateBackwards(start: 0, finish: 1) + roomProxy.startLiveEventListener() + roomProxy.callbacks.sink { [weak self] callback in + switch callback { + case .updatedLastMessage: + self?.updateRoomForProxy(roomProxy) + default: + break + } + } + .store(in: &roomUpdateListeners) + }) + + state.rooms = roomList.map { roomProxy in + roomFromProxy(roomProxy) } } @@ -79,7 +91,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol // MARK: - Private private func loadAvatarForRoomWithIdentifier(_ roomIdentifier: String) { - guard let room = roomList?.filter({ $0.identifier == roomIdentifier }).first, + guard let room = roomList?.filter({ $0.id == roomIdentifier }).first, let cacheKey = room.avatarURL?.path else { return } @@ -116,7 +128,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } private func loadRoomDisplayNameForRoomWithIdentifier(_ roomIdentifier: String) { - guard let room = roomList?.filter({ $0.identifier == roomIdentifier }).first else { + guard let room = roomList?.filter({ $0.id == roomIdentifier }).first else { return } @@ -147,4 +159,23 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol self.state.rooms[index].avatar = avatar } + + private func updateRoomForProxy(_ roomProxy: RoomProxyProtocol) { + state.rooms.updateEach { room in + if room.id != roomProxy.id { + return + } + + room = roomFromProxy(roomProxy) + } + } + + private func roomFromProxy(_ roomProxy: RoomProxyProtocol) -> HomeScreenRoom { + HomeScreenRoom(id: roomProxy.id, + displayName: roomProxy.name, + topic: roomProxy.topic, + lastMessage: roomProxy.lastMessage, + isDirect: roomProxy.isDirect, + isEncrypted: roomProxy.isEncrypted) + } } diff --git a/ElementX/Sources/Modules/HomeScreen/HomeScreenViewModelProtocol.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift similarity index 93% rename from ElementX/Sources/Modules/HomeScreen/HomeScreenViewModelProtocol.swift rename to ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift index ee80a0e67..2c72ecb57 100644 --- a/ElementX/Sources/Modules/HomeScreen/HomeScreenViewModelProtocol.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModelProtocol.swift @@ -22,7 +22,7 @@ protocol HomeScreenViewModelProtocol { var context: HomeScreenViewModelType.Context { get } - func updateWithRoomList(_ roomList: [RoomModelProtocol]) + func updateWithRoomList(_ roomList: [RoomProxyProtocol]) func updateWithUserAvatar(_ avatar: UIImage?) } diff --git a/ElementX/Sources/Modules/HomeScreen/Test/UI/HomeScreenUITests.swift b/ElementX/Sources/Screens/HomeScreen/Test/UI/HomeScreenUITests.swift similarity index 100% rename from ElementX/Sources/Modules/HomeScreen/Test/UI/HomeScreenUITests.swift rename to ElementX/Sources/Screens/HomeScreen/Test/UI/HomeScreenUITests.swift diff --git a/ElementX/Sources/Modules/HomeScreen/Test/Unit/HomeScreenViewModelTests.swift b/ElementX/Sources/Screens/HomeScreen/Test/Unit/HomeScreenViewModelTests.swift similarity index 100% rename from ElementX/Sources/Modules/HomeScreen/Test/Unit/HomeScreenViewModelTests.swift rename to ElementX/Sources/Screens/HomeScreen/Test/Unit/HomeScreenViewModelTests.swift diff --git a/ElementX/Sources/Modules/HomeScreen/View/HomeScreen.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift similarity index 74% rename from ElementX/Sources/Modules/HomeScreen/View/HomeScreen.swift rename to ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift index 1967646b7..af0bd571f 100644 --- a/ElementX/Sources/Modules/HomeScreen/View/HomeScreen.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreen.swift @@ -106,40 +106,44 @@ struct RoomCell: View { let context: HomeScreenViewModel.Context var body: some View { - HStack(spacing: 16.0) { - if let avatar = room.avatar { - Image(uiImage: avatar) - .resizable() - .scaledToFill() - .frame(width: 40, height: 40) - .mask(Circle()) - } else { - let _ = context.send(viewAction: .loadRoomData(roomId: room.id)) - Image(systemName: "person.3") - .frame(width: 40, height: 40) - } - - VStack(alignment: .leading, spacing: 4.0) { - Text(roomName(room)) - .font(.headline) - .fontWeight(.regular) - - if let roomTopic = room.topic, roomTopic.count > 0 { - Text(roomTopic) - .font(.footnote) - .fontWeight(.bold) - .lineLimit(1) + Button { + context.send(viewAction: .selectRoom(roomIdentifier: room.id)) + } label: { + HStack(spacing: 16.0) { + if let avatar = room.avatar { + Image(uiImage: avatar) + .resizable() + .scaledToFill() + .frame(width: 40, height: 40) + .mask(Circle()) + } else { + let _ = context.send(viewAction: .loadRoomData(roomIdentifier: room.id)) + Image(systemName: "person.3") + .frame(width: 40, height: 40) } - if let lastMessage = room.lastMessage { - Text(lastMessage) - .font(.footnote) - .fontWeight(.medium) - .lineLimit(1) + VStack(alignment: .leading, spacing: 4.0) { + Text(roomName(room)) + .font(.headline) + .fontWeight(.regular) + + if let roomTopic = room.topic, roomTopic.count > 0 { + Text(roomTopic) + .font(.footnote) + .fontWeight(.bold) + .lineLimit(1) + } + + if let lastMessage = room.lastMessage { + Text(lastMessage) + .font(.footnote) + .fontWeight(.medium) + .lineLimit(1) + } } } + .frame(minHeight: 60.0) } - .frame(minHeight: 60.0) } private func roomName(_ room: HomeScreenRoom) -> String { @@ -153,9 +157,9 @@ struct HomeScreen_Previews: PreviewProvider { static var previews: some View { let viewModel = HomeScreenViewModel(userDisplayName: "Johnny Appleseed", imageCache: ImageCache.default) - let rooms = [MockRoomModel(displayName: "Alfa"), - MockRoomModel(displayName: "Beta"), - MockRoomModel(displayName: "Omega")] + let rooms = [MockRoomProxy(displayName: "Alfa"), + MockRoomProxy(displayName: "Beta"), + MockRoomProxy(displayName: "Omega")] viewModel.updateWithRoomList(rooms) diff --git a/ElementX/Sources/Modules/Authentication/LoginScreen/LoginScreenCoordinator.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenCoordinator.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/LoginScreen/LoginScreenCoordinator.swift rename to ElementX/Sources/Screens/LoginScreen/LoginScreenCoordinator.swift diff --git a/ElementX/Sources/Modules/Authentication/LoginScreen/LoginScreenModels.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenModels.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/LoginScreen/LoginScreenModels.swift rename to ElementX/Sources/Screens/LoginScreen/LoginScreenModels.swift diff --git a/ElementX/Sources/Modules/Authentication/LoginScreen/LoginScreenViewModel.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/LoginScreen/LoginScreenViewModel.swift rename to ElementX/Sources/Screens/LoginScreen/LoginScreenViewModel.swift diff --git a/ElementX/Sources/Modules/Authentication/LoginScreen/LoginScreenViewModelProtocol.swift b/ElementX/Sources/Screens/LoginScreen/LoginScreenViewModelProtocol.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/LoginScreen/LoginScreenViewModelProtocol.swift rename to ElementX/Sources/Screens/LoginScreen/LoginScreenViewModelProtocol.swift diff --git a/ElementX/Sources/Modules/Authentication/LoginScreen/Test/UI/LoginScreenUITests.swift b/ElementX/Sources/Screens/LoginScreen/Test/UI/LoginScreenUITests.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/LoginScreen/Test/UI/LoginScreenUITests.swift rename to ElementX/Sources/Screens/LoginScreen/Test/UI/LoginScreenUITests.swift diff --git a/ElementX/Sources/Modules/Authentication/LoginScreen/Test/Unit/LoginScreenViewModelTests.swift b/ElementX/Sources/Screens/LoginScreen/Test/Unit/LoginScreenViewModelTests.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/LoginScreen/Test/Unit/LoginScreenViewModelTests.swift rename to ElementX/Sources/Screens/LoginScreen/Test/Unit/LoginScreenViewModelTests.swift diff --git a/ElementX/Sources/Modules/Authentication/LoginScreen/View/LoginScreen.swift b/ElementX/Sources/Screens/LoginScreen/View/LoginScreen.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/LoginScreen/View/LoginScreen.swift rename to ElementX/Sources/Screens/LoginScreen/View/LoginScreen.swift diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift new file mode 100644 index 000000000..22a6ea331 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -0,0 +1,67 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct RoomScreenCoordinatorParameters { + let roomProxy: RoomProxyProtocol +} + +final class RoomScreenCoordinator: Coordinator, Presentable { + + // MARK: - Properties + + // MARK: Private + + private let parameters: RoomScreenCoordinatorParameters + private let roomScreenHostingController: UIViewController + private var roomScreenViewModel: RoomScreenViewModelProtocol + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var completion: ((RoomScreenViewModelResult) -> Void)? + + // MARK: - Setup + + @available(iOS 14.0, *) + init(parameters: RoomScreenCoordinatorParameters) { + self.parameters = parameters + + let timelineProvider = RoomTimelineProvider(roomProxy: parameters.roomProxy) + let timelineController = RoomTimelineController(timelineProvider: timelineProvider) + + let viewModel = RoomScreenViewModel(roomProxy: parameters.roomProxy, timelineController: timelineController) + let view = RoomScreen(context: viewModel.context) + roomScreenViewModel = viewModel + roomScreenHostingController = UIHostingController(rootView: view) + } + + // MARK: - Public + func start() { + MXLog.debug("[RoomScreenCoordinator] did start.") + roomScreenViewModel.completion = { [weak self] result in + MXLog.debug("[RoomScreenCoordinator] RoomScreenViewModel did complete with result: \(result).") + guard let self = self else { return } + self.completion?(result) + } + } + + func toPresentable() -> UIViewController { + return self.roomScreenHostingController + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift new file mode 100644 index 000000000..b3b1abb7d --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -0,0 +1,50 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +enum RoomScreenViewModelResult { + +} + +enum RoomScreenViewAction { + case loadPreviousPage +} + +private var dateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .short + dateFormatter.timeStyle = .short + return dateFormatter +}() + +struct RoomScreenMessage: Identifiable, Equatable { + + let id: String + let sender: String + let text: String + let originServerTs: Date + + var timestamp: String { + dateFormatter.string(from: originServerTs) + } +} + +struct RoomScreenViewState: BindableState { + var roomTitle: String? + var isLoading: Bool = false + var messages: [RoomScreenMessage] = [] +} diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift new file mode 100644 index 000000000..94b802d3e --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -0,0 +1,74 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@available(iOS 14, *) +typealias RoomScreenViewModelType = StateStoreViewModel +@available(iOS 14, *) +class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol { + + private struct Constants { + static let backPaginationPageSize: UInt = 20 + } + + private let roomProxy: RoomProxyProtocol + private let timelineController: RoomTimelineControllerProtocol + + var completion: ((RoomScreenViewModelResult) -> Void)? + + // MARK: - Setup + + init(roomProxy: RoomProxyProtocol, timelineController: RoomTimelineControllerProtocol) { + self.roomProxy = roomProxy + self.timelineController = timelineController + + super.init(initialViewState: RoomScreenViewState()) + + state.messages = buildRoomScreenMessages(timelineController.timelineItems) + + timelineController.callbacks.sink { [weak self] callback in + guard let self = self else { return } + + switch callback { + case .updatedTimelineItems: + self.state.messages = self.buildRoomScreenMessages(timelineController.timelineItems) + } + }.store(in: &cancellables) + + timelineController.paginateBackwards(Constants.backPaginationPageSize) + } + + // MARK: - Public + + override func process(viewAction: RoomScreenViewAction) { + switch viewAction { + case .loadPreviousPage: + timelineController.paginateBackwards(Constants.backPaginationPageSize) + } + } + + // MARK: - Private + + private func buildRoomScreenMessages(_ timelineItems: [RoomTimelineItemProtocol]) -> [RoomScreenMessage] { + timelineItems.map { RoomScreenMessage(id: $0.id, + sender: $0.senderDisplayName, + text: $0.text, + originServerTs: $0.originServerTs) } + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModelProtocol.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModelProtocol.swift new file mode 100644 index 000000000..557b8e2e8 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModelProtocol.swift @@ -0,0 +1,24 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol RoomScreenViewModelProtocol { + + var completion: ((RoomScreenViewModelResult) -> Void)? { get set } + @available(iOS 14, *) + var context: RoomScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/RoomScreen/Test/UI/RoomScreenUITests.swift b/ElementX/Sources/Screens/RoomScreen/Test/UI/RoomScreenUITests.swift new file mode 100644 index 000000000..e6085fff9 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/Test/UI/RoomScreenUITests.swift @@ -0,0 +1,17 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest diff --git a/ElementX/Sources/Screens/RoomScreen/Test/Unit/RoomScreenViewModelTests.swift b/ElementX/Sources/Screens/RoomScreen/Test/Unit/RoomScreenViewModelTests.swift new file mode 100644 index 000000000..d1481d731 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/Test/Unit/RoomScreenViewModelTests.swift @@ -0,0 +1,21 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest + +class RoomScreenViewModelTests: XCTestCase { + +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift new file mode 100644 index 000000000..d8f7e5998 --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -0,0 +1,85 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct RoomScreen: View { + + let timelineBottomDividerIdentifier = "TimelineBottomDividerIdentifier" + + @ObservedObject var context: RoomScreenViewModel.Context + @State var backPaginationMessageIdentifier: String? + + var body: some View { + ScrollViewReader { reader in + List { + if backPaginationMessageIdentifier != nil { + HStack { + Spacer() + ProgressView() + Spacer() + } + } else { + Rectangle() + .onAppear { + let _ = MXLog.debug("Request load previous page") + backPaginationMessageIdentifier = context.viewState.messages.first?.id + context.send(viewAction: .loadPreviousPage) + } + .frame(height: 0.0) + } + ForEach(context.viewState.messages) { message in + VStack(alignment: .leading) { + HStack { + Text(message.sender) + Spacer() + Text(message.timestamp) + } + .font(.footnote) + Text(message.text) + } + .listRowSeparator(.hidden) + } + + Divider() + .id(timelineBottomDividerIdentifier) + .listRowSeparator(.hidden) + } + .listStyle(.plain) + .onAppear { + reader.scrollTo(timelineBottomDividerIdentifier, anchor: .bottom) + } + .onChange(of: context.viewState.messages) { _ in + if backPaginationMessageIdentifier != nil { + self.backPaginationMessageIdentifier = nil + return + } + + reader.scrollTo(timelineBottomDividerIdentifier, anchor: .bottom) + } + } + } +} + +// MARK: - Previews + +struct RoomScreen_Previews: PreviewProvider { + static var previews: some View { + let viewModel = RoomScreenViewModel(roomProxy: MockRoomProxy(displayName: "Test"), + timelineController: MockRoomTimelineController()) + RoomScreen(context: viewModel.context) + } +} diff --git a/ElementX/Sources/Modules/Splash/SplashViewController.swift b/ElementX/Sources/Screens/Splash/SplashViewController.swift similarity index 100% rename from ElementX/Sources/Modules/Splash/SplashViewController.swift rename to ElementX/Sources/Screens/Splash/SplashViewController.swift diff --git a/ElementX/Sources/Modules/Splash/SplashViewController.xib b/ElementX/Sources/Screens/Splash/SplashViewController.xib similarity index 100% rename from ElementX/Sources/Modules/Splash/SplashViewController.xib rename to ElementX/Sources/Screens/Splash/SplashViewController.xib diff --git a/ElementX/Sources/Modules/Templates/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift b/ElementX/Sources/Screens/Templates/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift similarity index 100% rename from ElementX/Sources/Modules/Templates/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift rename to ElementX/Sources/Screens/Templates/SimpleScreenExample/Coordinator/TemplateSimpleScreenCoordinator.swift diff --git a/ElementX/Sources/Modules/Templates/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift b/ElementX/Sources/Screens/Templates/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift similarity index 100% rename from ElementX/Sources/Modules/Templates/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift rename to ElementX/Sources/Screens/Templates/SimpleScreenExample/MockTemplateSimpleScreenScreenState.swift diff --git a/ElementX/Sources/Modules/Templates/SimpleScreenExample/TemplateSimpleScreenModels.swift b/ElementX/Sources/Screens/Templates/SimpleScreenExample/TemplateSimpleScreenModels.swift similarity index 100% rename from ElementX/Sources/Modules/Templates/SimpleScreenExample/TemplateSimpleScreenModels.swift rename to ElementX/Sources/Screens/Templates/SimpleScreenExample/TemplateSimpleScreenModels.swift diff --git a/ElementX/Sources/Modules/Templates/SimpleScreenExample/TemplateSimpleScreenViewModel.swift b/ElementX/Sources/Screens/Templates/SimpleScreenExample/TemplateSimpleScreenViewModel.swift similarity index 100% rename from ElementX/Sources/Modules/Templates/SimpleScreenExample/TemplateSimpleScreenViewModel.swift rename to ElementX/Sources/Screens/Templates/SimpleScreenExample/TemplateSimpleScreenViewModel.swift diff --git a/ElementX/Sources/Modules/Templates/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift b/ElementX/Sources/Screens/Templates/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift similarity index 100% rename from ElementX/Sources/Modules/Templates/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift rename to ElementX/Sources/Screens/Templates/SimpleScreenExample/TemplateSimpleScreenViewModelProtocol.swift diff --git a/ElementX/Sources/Modules/Templates/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift b/ElementX/Sources/Screens/Templates/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift similarity index 100% rename from ElementX/Sources/Modules/Templates/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift rename to ElementX/Sources/Screens/Templates/SimpleScreenExample/Test/UI/TemplateSimpleScreenUITests.swift diff --git a/ElementX/Sources/Modules/Templates/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift b/ElementX/Sources/Screens/Templates/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift similarity index 100% rename from ElementX/Sources/Modules/Templates/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift rename to ElementX/Sources/Screens/Templates/SimpleScreenExample/Test/Unit/TemplateSimpleScreenViewModelTests.swift diff --git a/ElementX/Sources/Modules/Templates/SimpleScreenExample/View/TemplateSimpleScreen.swift b/ElementX/Sources/Screens/Templates/SimpleScreenExample/View/TemplateSimpleScreen.swift similarity index 100% rename from ElementX/Sources/Modules/Templates/SimpleScreenExample/View/TemplateSimpleScreen.swift rename to ElementX/Sources/Screens/Templates/SimpleScreenExample/View/TemplateSimpleScreen.swift diff --git a/ElementX/Sources/Modules/Authentication/AuthenticationCoordinator.swift b/ElementX/Sources/Services/Authentication/AuthenticationCoordinator.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/AuthenticationCoordinator.swift rename to ElementX/Sources/Services/Authentication/AuthenticationCoordinator.swift diff --git a/ElementX/Sources/Modules/Authentication/KeychainController.swift b/ElementX/Sources/Services/Authentication/KeychainController.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/KeychainController.swift rename to ElementX/Sources/Services/Authentication/KeychainController.swift diff --git a/ElementX/Sources/Modules/Authentication/KeychainControllerProtocol.swift b/ElementX/Sources/Services/Authentication/KeychainControllerProtocol.swift similarity index 100% rename from ElementX/Sources/Modules/Authentication/KeychainControllerProtocol.swift rename to ElementX/Sources/Services/Authentication/KeychainControllerProtocol.swift diff --git a/ElementX/Sources/Modules/Authentication/UserSession.swift b/ElementX/Sources/Services/Authentication/UserSession.swift similarity index 74% rename from ElementX/Sources/Modules/Authentication/UserSession.swift rename to ElementX/Sources/Services/Authentication/UserSession.swift index 610bfe237..e08613f33 100644 --- a/ElementX/Sources/Modules/Authentication/UserSession.swift +++ b/ElementX/Sources/Services/Authentication/UserSession.swift @@ -11,7 +11,7 @@ import Combine import UIKit enum UserSessionCallback { - case updatedData + case updatedRoomsList } enum UserSessionError: Error { @@ -19,20 +19,27 @@ enum UserSessionError: Error { } private class WeakUserSessionWrapper: ClientDelegate { - weak var userSession: UserSession? + private weak var userSession: UserSession? init(userSession: UserSession) { self.userSession = userSession } func didReceiveSyncUpdate() { - userSession?.didReceiveSyncUpdate() + DispatchQueue.main.async { + self.userSession?.didReceiveSyncUpdate() + } } } class UserSession: ClientDelegate { private let client: Client + private var rooms: [RoomProxy] = [] { + didSet { + self.callbacks.send(.updatedRoomsList) + } + } deinit { client.setDelegate(delegate: nil) @@ -81,12 +88,27 @@ class UserSession: ClientDelegate { } } - func getRoomList(_ completion: @escaping ([RoomModel]) -> Void) { + func getRoomList(_ completion: @escaping ([RoomProxyProtocol]) -> Void) { + fetchRoomList(completion) + } + + // MARK: ClientDelegate + + func didReceiveSyncUpdate() { + fetchRoomList { [weak self] rooms in + guard let self = self else { return } + if self.rooms != rooms { + self.rooms = rooms + } + } + } + + // MARK: Private + + func fetchRoomList(_ completion: @escaping ([RoomProxy]) -> Void) { DispatchQueue.global(qos: .background).async { - let conversations = self.client.conversations() - - let rooms = conversations.map { - return RoomModel(room: $0) + let rooms = self.client.conversations().map { + return RoomProxy(room: $0) } DispatchQueue.main.async { @@ -94,12 +116,4 @@ class UserSession: ClientDelegate { } } } - - // MARK: ClientDelegate - - func didReceiveSyncUpdate() { - DispatchQueue.main.async { - self.callbacks.send(.updatedData) - } - } } diff --git a/ElementX/Sources/Modules/Models/MockRoomModel.swift b/ElementX/Sources/Services/MockRoomProxy.swift similarity index 68% rename from ElementX/Sources/Modules/Models/MockRoomModel.swift rename to ElementX/Sources/Services/MockRoomProxy.swift index e1e7adcc8..1fcfea454 100644 --- a/ElementX/Sources/Modules/Models/MockRoomModel.swift +++ b/ElementX/Sources/Services/MockRoomProxy.swift @@ -1,5 +1,5 @@ // -// MockRoomModel.swift +// MockRoomProxy.swift // ElementX // // Created by Stefan Ceriu on 17.02.2022. @@ -7,9 +7,10 @@ import Foundation import UIKit +import Combine -struct MockRoomModel: RoomModelProtocol { - let identifier = UUID().uuidString +struct MockRoomProxy: RoomProxyProtocol { + let id = UUID().uuidString let name: String? = nil let displayName: String @@ -23,6 +24,8 @@ struct MockRoomModel: RoomModelProtocol { let isPublic = Bool.random() let isEncrypted = Bool.random() + var callbacks = PassthroughSubject() + func loadDisplayName(_ completion: @escaping (Result) -> Void) { completion(.success(displayName)) } @@ -30,4 +33,12 @@ struct MockRoomModel: RoomModelProtocol { func loadAvatar(_ completion: (Result) -> Void) { completion(.success(UIImage(systemName: "wand.and.stars"))) } + + func startLiveEventListener() { + + } + + func paginateBackwards(start: UInt, finish: UInt) { + + } } diff --git a/ElementX/Sources/Services/RoomProxy.swift b/ElementX/Sources/Services/RoomProxy.swift new file mode 100644 index 000000000..bba30c9ec --- /dev/null +++ b/ElementX/Sources/Services/RoomProxy.swift @@ -0,0 +1,152 @@ +// +// RoomProxy.swift +// ElementX +// +// Created by Stefan Ceriu on 14.02.2022. +// + +import Foundation +import UIKit +import Combine + +import MatrixRustSDK + +enum RoomProxyError: Error { + case failedRetrievingDisplayName + case failedRetrievingAvatar +} + +private class WeakRoomProxyWrapper: RoomDelegate { + private weak var roomProxy: RoomProxy? + + init(roomProxy: RoomProxy) { + self.roomProxy = roomProxy + } + + func didReceiveMessage(message: Message) { + DispatchQueue.main.async { + self.roomProxy?.appendMessage(message) + } + } + + func didPaginateBackwards(messages: [Message]) { + DispatchQueue.main.async { + self.roomProxy?.preprendMessages(messages) + } + } +} + +class RoomProxy: RoomProxyProtocol, Equatable { + private let room: Room + + let callbacks = PassthroughSubject() + + init(room: Room) { + self.room = room + self.room.setDelegate(delegate: WeakRoomProxyWrapper(roomProxy: self)) + } + + var id: String { + return room.id() + } + + var isDirect: Bool { + return room.isDirect() + } + + var isPublic: Bool { + return room.isPublic() + } + + var isSpace: Bool { + return room.isSpace() + } + + var isEncrypted: Bool { + return room.isEncrypted() + } + + var name: String? { + return room.name() + } + + var topic: String? { + return room.topic() + } + + var lastMessage: String? { + didSet { + callbacks.send(.updatedLastMessage) + } + } + + var avatarURL: URL? { + guard let urlString = room.avatarUrl() else { + return nil + } + + return URL(string: urlString) + } + + func loadDisplayName(_ completion: @escaping (Result) -> Void) { + DispatchQueue.global(qos: .background).async { + do { + let displayName = try self.room.displayName() + + DispatchQueue.main.async { + completion(.success(displayName)) + } + } catch { + DispatchQueue.main.async { + completion(.failure(RoomProxyError.failedRetrievingDisplayName)) + } + } + } + } + + func loadAvatar(_ completion: @escaping (Result) -> Void) { + DispatchQueue.global(qos: .background).async { + do { + let avatarData = try self.room.avatar() + + DispatchQueue.main.async { + completion(.success(UIImage(data: Data(bytes: avatarData, count: avatarData.count)))) + } + } catch { + DispatchQueue.main.async { + completion(.failure(RoomProxyError.failedRetrievingAvatar)) + } + } + } + } + + func startLiveEventListener() { + room.startLiveEventListener() + } + + func paginateBackwards(start: UInt, finish: UInt) { + room.paginateBackwards(from: UInt8(start), to: UInt8(finish)) + } + + // MARK: - Equatable + + static func == (lhs: RoomProxy, rhs: RoomProxy) -> Bool { + lhs.id == rhs.id + } + + // MARK: - Private + + fileprivate func preprendMessages(_ messages: [Message]) { + if lastMessage == nil { + lastMessage = messages.last?.content() + } + + callbacks.send(.prependedMessages(messages)) + } + + fileprivate func appendMessage(_ message: Message) { + lastMessage = message.content() + + callbacks.send(.addedMessage(message)) + } +} diff --git a/ElementX/Sources/Modules/Models/RoomModelProtocol.swift b/ElementX/Sources/Services/RoomProxyProtocol.swift similarity index 55% rename from ElementX/Sources/Modules/Models/RoomModelProtocol.swift rename to ElementX/Sources/Services/RoomProxyProtocol.swift index e4bc8036c..2555f25b6 100644 --- a/ElementX/Sources/Modules/Models/RoomModelProtocol.swift +++ b/ElementX/Sources/Services/RoomProxyProtocol.swift @@ -1,14 +1,22 @@ // -// RoomModelProtocol.swift +// RoomProxyProtocol.swift // ElementX // // Created by Stefan Ceriu on 17.02.2022. // import UIKit +import Combine +import MatrixRustSDK -protocol RoomModelProtocol { - var identifier: String { get } +enum RoomProxyCallback { + case prependedMessages([Message]) + case addedMessage(Message) + case updatedLastMessage +} + +protocol RoomProxyProtocol { + var id: String { get } var isDirect: Bool { get } var isPublic: Bool { get } var isSpace: Bool { get } @@ -23,4 +31,9 @@ protocol RoomModelProtocol { func loadDisplayName(_ completion: @escaping (Result) -> Void) func loadAvatar(_ completion: @escaping (Result) -> Void) + + func startLiveEventListener() + func paginateBackwards(start: UInt, finish: UInt) + + var callbacks: PassthroughSubject { get } } diff --git a/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift new file mode 100644 index 000000000..3d3de1f41 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/MockRoomTimelineController.swift @@ -0,0 +1,20 @@ +// +// MockRoomTimelineController.swift +// ElementX +// +// Created by Stefan Ceriu on 04.03.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation +import Combine + +class MockRoomTimelineController: RoomTimelineControllerProtocol { + let timelineItems: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(id: UUID().uuidString, senderDisplayName: "Anne", text: "You rock!", originServerTs: .now), + TextRoomTimelineItem(id: UUID().uuidString, senderDisplayName: "Bob", text: "You rule!", originServerTs: .now)] + let callbacks = PassthroughSubject() + + func paginateBackwards(_ count: UInt) { + + } +} diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift new file mode 100644 index 000000000..bb6be0c7a --- /dev/null +++ b/ElementX/Sources/Services/Timeline/RoomTimelineController.swift @@ -0,0 +1,48 @@ +// +// RoomTimelineController.swift +// ElementX +// +// Created by Stefan Ceriu on 04.03.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation +import Combine +import MatrixRustSDK + +enum RoomTimelineControllerCallback { + case updatedTimelineItems +} + +class RoomTimelineController: RoomTimelineControllerProtocol { + private let timelineProvider: RoomTimelineProvider + private var cancellables = Set() + + let callbacks = PassthroughSubject() + + private(set) var timelineItems = [RoomTimelineItemProtocol]() + + init(timelineProvider: RoomTimelineProvider) { + self.timelineProvider = timelineProvider + + self.timelineProvider.callbacks.sink { [weak self] callback in + guard let self = self else { return } + + switch callback { + case .updatedMessages: + self.timelineItems = self.timelineProvider.messages.map { message in + let timestamp = Date(timeIntervalSince1970: TimeInterval(message.originServerTs())) + return TextRoomTimelineItem(id: message.id(), + senderDisplayName: message.sender(), + text: message.content(), + originServerTs: timestamp) + } + self.callbacks.send(.updatedTimelineItems) + } + }.store(in: &cancellables) + } + + func paginateBackwards(_ count: UInt) { + timelineProvider.paginateBackwards(count) + } +} diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift new file mode 100644 index 000000000..85bb02398 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/RoomTimelineControllerProtocol.swift @@ -0,0 +1,17 @@ +// +// RoomTimelineControllerProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 04.03.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation +import Combine + +protocol RoomTimelineControllerProtocol { + var timelineItems: [RoomTimelineItemProtocol] { get } + var callbacks: PassthroughSubject { get } + + func paginateBackwards(_ count: UInt) +} diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/RoomTimelineItemProtocol.swift new file mode 100644 index 000000000..66e9a8bff --- /dev/null +++ b/ElementX/Sources/Services/Timeline/RoomTimelineItemProtocol.swift @@ -0,0 +1,16 @@ +// +// RoomTimelineItemProtocol.swift +// ElementX +// +// Created by Stefan Ceriu on 04.03.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation + +protocol RoomTimelineItemProtocol { + var id: String { get } + var senderDisplayName: String { get } + var text: String { get } + var originServerTs: Date { get } +} diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift new file mode 100644 index 000000000..5862afa96 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift @@ -0,0 +1,61 @@ +// +// RoomTimeline.swift +// ElementX +// +// Created by Stefan Ceriu on 04.03.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation +import Combine +import MatrixRustSDK + +enum RoomTimelineCallback { + case updatedMessages +} + +class RoomTimelineProvider { + private let roomProxy: RoomProxyProtocol + private var cancellables = Set() + + private var paginationCounter: UInt = 0 + private var isWaitingPaginationResponse = false + + let callbacks = PassthroughSubject() + private(set) var messages = [Message]() + + init(roomProxy: RoomProxyProtocol) { + self.roomProxy = roomProxy + + self.roomProxy.startLiveEventListener() + + self.roomProxy.callbacks.sink { [weak self] callback in + guard let self = self else { return } + + switch callback { + case .addedMessage(let message): + self.messages.append(message) + case .prependedMessages(let messages): + self.messages.insert(contentsOf: messages.reversed(), at: 0) + self.isWaitingPaginationResponse = false + case .updatedLastMessage: + break + } + + self.callbacks.send(.updatedMessages) + + }.store(in: &cancellables) + } + + func paginateBackwards(_ count: UInt) { + if isWaitingPaginationResponse { + return + } + + self.roomProxy.paginateBackwards(start: paginationCounter, finish: count) + isWaitingPaginationResponse = true + + // This is not in any way correct but it will do for now. + self.paginationCounter += count + } +} diff --git a/ElementX/Sources/Services/Timeline/TextRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TextRoomTimelineItem.swift new file mode 100644 index 000000000..9368a1ab9 --- /dev/null +++ b/ElementX/Sources/Services/Timeline/TextRoomTimelineItem.swift @@ -0,0 +1,16 @@ +// +// TextRoomTimelineItem.swift +// ElementX +// +// Created by Stefan Ceriu on 04.03.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation + +struct TextRoomTimelineItem: RoomTimelineItemProtocol { + let id: String + let senderDisplayName: String + let text: String + let originServerTs: Date +} diff --git a/Scripts/createSwiftUISimpleScreen.sh b/Scripts/createSwiftUISimpleScreen.sh index 0e510f899..f8b95465a 100755 --- a/Scripts/createSwiftUISimpleScreen.sh +++ b/Scripts/createSwiftUISimpleScreen.sh @@ -5,7 +5,7 @@ if [ ! $# -eq 2 ]; then exit 1 fi -MODULE_DIR="../ElementX/Sources/Modules" +MODULE_DIR="../ElementX/Sources/Modules/Screens" OUTPUT_DIR=$MODULE_DIR/$1 SCREEN_NAME=$2 SCREEN_VAR_NAME=`echo $SCREEN_NAME | awk '{ print tolower(substr($0, 1, 1)) substr($0, 2) }'` diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 5fcf410d0..d7e9524ac 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -12,6 +12,7 @@ end lane :ios_adhoc do build_ios_app( scheme: "ElementX", + clean: true, export_method: "ad-hoc", output_directory: "build", export_options: { @@ -25,6 +26,7 @@ end lane :mac_adhoc do build_mac_app( scheme: "ElementX", + clean: true, export_method: "mac-application", output_directory: "build" )