mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
Initial project setup.
This commit is contained in:
parent
a499570f39
commit
a3fcc0f612
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo.
|
||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
50
.swiftlint.yml
Executable file
50
.swiftlint.yml
Executable file
@ -0,0 +1,50 @@
|
||||
# # rule identifiers to exclude from running
|
||||
disabled_rules:
|
||||
- trailing_whitespace
|
||||
- unused_setter_value
|
||||
|
||||
# some rules are only opt-in
|
||||
opt_in_rules:
|
||||
- force_unwrapping
|
||||
- private_action
|
||||
- explicit_init
|
||||
|
||||
# paths to include during linting. `--path` is ignored if present.
|
||||
included:
|
||||
- ElementX
|
||||
|
||||
line_length:
|
||||
warning: 250
|
||||
error: 1000
|
||||
|
||||
file_length:
|
||||
warning: 800
|
||||
error: 1000
|
||||
|
||||
type_name:
|
||||
min_length: 3 # only warning
|
||||
max_length: # warning and error
|
||||
warning: 150
|
||||
error: 1000
|
||||
|
||||
custom_rules:
|
||||
print_deprecation:
|
||||
regex: "\\b(print)\\b"
|
||||
match_kinds: identifier
|
||||
message: "MXLog should be used instead of print()"
|
||||
severity: error
|
||||
|
||||
print_ln_deprecation:
|
||||
regex: "\\b(println)\\b"
|
||||
match_kinds: identifier
|
||||
message: "MXLog should be used instead of println()"
|
||||
severity: error
|
||||
|
||||
os_log_deprecation:
|
||||
regex: "\\b(os_log)\\b"
|
||||
match_kinds: identifier
|
||||
message: "MXLog should be used instead of os_log()"
|
||||
severity: error
|
||||
|
||||
|
||||
|
995
ElementX.xcodeproj/project.pbxproj
Normal file
995
ElementX.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,995 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 55;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1850253F27B6918D002E6B18 /* ElementXTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850253E27B6918D002E6B18 /* ElementXTests.swift */; };
|
||||
1850254927B6918D002E6B18 /* ElementXUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850254827B6918D002E6B18 /* ElementXUITests.swift */; };
|
||||
1850254B27B6918D002E6B18 /* ElementXUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850254A27B6918D002E6B18 /* ElementXUITestsLaunchTests.swift */; };
|
||||
1850255927B69388002E6B18 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 1850255827B69388002E6B18 /* MatrixRustSDK */; };
|
||||
1850256C27B6A135002E6B18 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850256227B6A135002E6B18 /* AppCoordinator.swift */; };
|
||||
1850256F27B6A135002E6B18 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1850256527B6A135002E6B18 /* AppDelegate.swift */; };
|
||||
1850257027B6A135002E6B18 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1850256827B6A135002E6B18 /* Assets.xcassets */; };
|
||||
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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
1850253B27B6918D002E6B18 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 1850251C27B6918C002E6B18 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 1850252327B6918C002E6B18;
|
||||
remoteInfo = ElementX;
|
||||
};
|
||||
1850254527B6918D002E6B18 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 1850251C27B6918C002E6B18 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 1850252327B6918C002E6B18;
|
||||
remoteInfo = ElementX;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
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 = "<group>"; };
|
||||
1850254427B6918D002E6B18 /* ElementXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ElementXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
1850254827B6918D002E6B18 /* ElementXUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXUITests.swift; sourceTree = "<group>"; };
|
||||
1850254A27B6918D002E6B18 /* ElementXUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXUITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||
1850256227B6A135002E6B18 /* AppCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
|
||||
1850256527B6A135002E6B18 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
1850256727B6A135002E6B18 /* ElementX.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = "<group>"; };
|
||||
1850256827B6A135002E6B18 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
1850256A27B6A135002E6B18 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
1863A40E27BA716A00B52E4D /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = "<group>"; };
|
||||
1863A40F27BA716A00B52E4D /* UserSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = "<group>"; };
|
||||
1863A41027BA716A00B52E4D /* KeychainController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = "<group>"; };
|
||||
1863A41127BA716A00B52E4D /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = "<group>"; };
|
||||
1863A41B27BA76B900B52E4D /* MXLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = "<group>"; };
|
||||
1863A41F27BA784300B52E4D /* LoginScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
1863A42427BA784300B52E4D /* LoginScreenUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = "<group>"; };
|
||||
1863A42627BA784300B52E4D /* LoginScreenViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
1863A42727BA784300B52E4D /* LoginScreenViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
1863A42827BA784300B52E4D /* LoginScreenModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenModels.swift; sourceTree = "<group>"; };
|
||||
1863A42A27BA784300B52E4D /* LoginScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; };
|
||||
1863A43827BA789800B52E4D /* StateStoreViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = "<group>"; };
|
||||
1863A43927BA789800B52E4D /* BindableState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = "<group>"; };
|
||||
1863A43D27BA790000B52E4D /* Coordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
|
||||
1863A44127BA79FF00B52E4D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = "<group>"; };
|
||||
1863A44227BA79FF00B52E4D /* NavigationRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouter.swift; sourceTree = "<group>"; };
|
||||
1863A44327BA79FF00B52E4D /* RootRouterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = "<group>"; };
|
||||
1863A44427BA79FF00B52E4D /* NavigationRouterStoreProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterStoreProtocol.swift; sourceTree = "<group>"; };
|
||||
1863A44527BA79FF00B52E4D /* RootRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = "<group>"; };
|
||||
1863A44627BA79FF00B52E4D /* Presentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Presentable.swift; sourceTree = "<group>"; };
|
||||
1863A44727BA79FF00B52E4D /* NavigationModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = "<group>"; };
|
||||
1863A44827BA79FF00B52E4D /* NavigationRouterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationRouterType.swift; sourceTree = "<group>"; };
|
||||
1863A45227BA7A7800B52E4D /* WeakDictionaryKeyReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionaryKeyReference.swift; sourceTree = "<group>"; };
|
||||
1863A45327BA7A7800B52E4D /* WeakDictionaryReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionaryReference.swift; sourceTree = "<group>"; };
|
||||
1863A45427BA7A7800B52E4D /* WeakDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakDictionary.swift; sourceTree = "<group>"; };
|
||||
1863A45527BA7A7800B52E4D /* WeakKeyDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakKeyDictionary.swift; sourceTree = "<group>"; };
|
||||
1863A45A27BA7B4700B52E4D /* LoginScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
1863A45D27BAA60300B52E4D /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = "<group>"; };
|
||||
1863A45E27BAA60300B52E4D /* SplashViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SplashViewController.xib; sourceTree = "<group>"; };
|
||||
1863A47927BAA8A900B52E4D /* HomeScreenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
1863A47C27BAA8A900B52E4D /* HomeScreenUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenUITests.swift; sourceTree = "<group>"; };
|
||||
1863A47E27BAA8A900B52E4D /* HomeScreenViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
1863A47F27BAA8A900B52E4D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
1863A48027BAA8A900B52E4D /* HomeScreenModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenModels.swift; sourceTree = "<group>"; };
|
||||
1863A48227BAA8A900B52E4D /* HomeScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = "<group>"; };
|
||||
1863A48427BAA8A900B52E4D /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
1863A49327BAAA6700B52E4D /* RoomModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomModel.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
1850252127B6918C002E6B18 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1863A3FC27BA5A9100B52E4D /* KeychainAccess in Frameworks */,
|
||||
1850255927B69388002E6B18 /* MatrixRustSDK in Frameworks */,
|
||||
1863A40627BA6DFC00B52E4D /* SwiftyBeaver in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1850253727B6918D002E6B18 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1850254127B6918D002E6B18 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
1850251B27B6918C002E6B18 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1850252627B6918C002E6B18 /* ElementX */,
|
||||
1850253D27B6918D002E6B18 /* ElementXTests */,
|
||||
1850254727B6918D002E6B18 /* ElementXUITests */,
|
||||
1850252527B6918C002E6B18 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1850252527B6918C002E6B18 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1850252427B6918C002E6B18 /* ElementX.app */,
|
||||
1850253A27B6918D002E6B18 /* ElementXTests.xctest */,
|
||||
1850254427B6918D002E6B18 /* ElementXUITests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1850252627B6918C002E6B18 /* ElementX */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1850256127B6A135002E6B18 /* Sources */,
|
||||
1850256627B6A135002E6B18 /* Supporting Files */,
|
||||
);
|
||||
path = ElementX;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1850253D27B6918D002E6B18 /* ElementXTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1850253E27B6918D002E6B18 /* ElementXTests.swift */,
|
||||
);
|
||||
path = ElementXTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1850254727B6918D002E6B18 /* ElementXUITests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1850254827B6918D002E6B18 /* ElementXUITests.swift */,
|
||||
1850254A27B6918D002E6B18 /* ElementXUITestsLaunchTests.swift */,
|
||||
);
|
||||
path = ElementXUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1850256127B6A135002E6B18 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1850256527B6A135002E6B18 /* AppDelegate.swift */,
|
||||
1850256227B6A135002E6B18 /* AppCoordinator.swift */,
|
||||
1863A40927BA716A00B52E4D /* Modules */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1850256627B6A135002E6B18 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1850256727B6A135002E6B18 /* ElementX.entitlements */,
|
||||
1850256827B6A135002E6B18 /* Assets.xcassets */,
|
||||
1850256927B6A135002E6B18 /* LaunchScreen.storyboard */,
|
||||
);
|
||||
path = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A40927BA716A00B52E4D /* Modules */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A45C27BAA5F100B52E4D /* Splash */,
|
||||
1863A40D27BA716A00B52E4D /* Authentication */,
|
||||
1863A47827BAA8A900B52E4D /* HomeScreen */,
|
||||
1863A49227BAAA6700B52E4D /* Models */,
|
||||
1863A44027BA79FF00B52E4D /* Routers */,
|
||||
1863A41A27BA76B900B52E4D /* Other */,
|
||||
);
|
||||
path = Modules;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A40D27BA716A00B52E4D /* Authentication */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A40E27BA716A00B52E4D /* AuthenticationCoordinator.swift */,
|
||||
1863A40F27BA716A00B52E4D /* UserSession.swift */,
|
||||
1863A41027BA716A00B52E4D /* KeychainController.swift */,
|
||||
1863A41127BA716A00B52E4D /* KeychainControllerProtocol.swift */,
|
||||
1863A41D27BA784300B52E4D /* LoginScreen */,
|
||||
);
|
||||
path = Authentication;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A41A27BA76B900B52E4D /* Other */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A43D27BA790000B52E4D /* Coordinator.swift */,
|
||||
1863A41B27BA76B900B52E4D /* MXLog.swift */,
|
||||
1863A43627BA789800B52E4D /* SwiftUI */,
|
||||
1863A45127BA7A7800B52E4D /* WeakDictionary */,
|
||||
);
|
||||
path = Other;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A41D27BA784300B52E4D /* LoginScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A45A27BA7B4700B52E4D /* LoginScreenCoordinator.swift */,
|
||||
1863A42827BA784300B52E4D /* LoginScreenModels.swift */,
|
||||
1863A41F27BA784300B52E4D /* LoginScreenViewModel.swift */,
|
||||
1863A42727BA784300B52E4D /* LoginScreenViewModelProtocol.swift */,
|
||||
1863A42227BA784300B52E4D /* Test */,
|
||||
1863A42927BA784300B52E4D /* View */,
|
||||
);
|
||||
path = LoginScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A42227BA784300B52E4D /* Test */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A42327BA784300B52E4D /* UI */,
|
||||
1863A42527BA784300B52E4D /* Unit */,
|
||||
);
|
||||
path = Test;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A42327BA784300B52E4D /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A42427BA784300B52E4D /* LoginScreenUITests.swift */,
|
||||
);
|
||||
path = UI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A42527BA784300B52E4D /* Unit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A42627BA784300B52E4D /* LoginScreenViewModelTests.swift */,
|
||||
);
|
||||
path = Unit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A42927BA784300B52E4D /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A42A27BA784300B52E4D /* LoginScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A43627BA789800B52E4D /* SwiftUI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A43727BA789800B52E4D /* ViewModel */,
|
||||
);
|
||||
path = SwiftUI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A43727BA789800B52E4D /* ViewModel */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A43827BA789800B52E4D /* StateStoreViewModel.swift */,
|
||||
1863A43927BA789800B52E4D /* BindableState.swift */,
|
||||
);
|
||||
path = ViewModel;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 */,
|
||||
);
|
||||
path = Routers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A45127BA7A7800B52E4D /* WeakDictionary */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A45227BA7A7800B52E4D /* WeakDictionaryKeyReference.swift */,
|
||||
1863A45327BA7A7800B52E4D /* WeakDictionaryReference.swift */,
|
||||
1863A45427BA7A7800B52E4D /* WeakDictionary.swift */,
|
||||
1863A45527BA7A7800B52E4D /* WeakKeyDictionary.swift */,
|
||||
);
|
||||
path = WeakDictionary;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A45C27BAA5F100B52E4D /* Splash */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A45D27BAA60300B52E4D /* SplashViewController.swift */,
|
||||
1863A45E27BAA60300B52E4D /* SplashViewController.xib */,
|
||||
);
|
||||
path = Splash;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A47827BAA8A900B52E4D /* HomeScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A47927BAA8A900B52E4D /* HomeScreenCoordinator.swift */,
|
||||
1863A48027BAA8A900B52E4D /* HomeScreenModels.swift */,
|
||||
1863A48427BAA8A900B52E4D /* HomeScreenViewModel.swift */,
|
||||
1863A47F27BAA8A900B52E4D /* HomeScreenViewModelProtocol.swift */,
|
||||
1863A47A27BAA8A900B52E4D /* Test */,
|
||||
1863A48127BAA8A900B52E4D /* View */,
|
||||
);
|
||||
path = HomeScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A47A27BAA8A900B52E4D /* Test */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A47B27BAA8A900B52E4D /* UI */,
|
||||
1863A47D27BAA8A900B52E4D /* Unit */,
|
||||
);
|
||||
path = Test;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A47B27BAA8A900B52E4D /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A47C27BAA8A900B52E4D /* HomeScreenUITests.swift */,
|
||||
);
|
||||
path = UI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A47D27BAA8A900B52E4D /* Unit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A47E27BAA8A900B52E4D /* HomeScreenViewModelTests.swift */,
|
||||
);
|
||||
path = Unit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A48127BAA8A900B52E4D /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A48227BAA8A900B52E4D /* HomeScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1863A49227BAAA6700B52E4D /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1863A49327BAAA6700B52E4D /* RoomModel.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
1850252327B6918C002E6B18 /* ElementX */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 1850254E27B6918D002E6B18 /* Build configuration list for PBXNativeTarget "ElementX" */;
|
||||
buildPhases = (
|
||||
1850252027B6918C002E6B18 /* Sources */,
|
||||
1850252127B6918C002E6B18 /* Frameworks */,
|
||||
1850252227B6918C002E6B18 /* Resources */,
|
||||
184230FC27BAAC5800033771 /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = ElementX;
|
||||
packageProductDependencies = (
|
||||
1850255827B69388002E6B18 /* MatrixRustSDK */,
|
||||
1863A3FB27BA5A9100B52E4D /* KeychainAccess */,
|
||||
1863A40527BA6DFC00B52E4D /* SwiftyBeaver */,
|
||||
);
|
||||
productName = ElementX;
|
||||
productReference = 1850252427B6918C002E6B18 /* ElementX.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
1850253927B6918D002E6B18 /* ElementXTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 1850255127B6918D002E6B18 /* Build configuration list for PBXNativeTarget "ElementXTests" */;
|
||||
buildPhases = (
|
||||
1850253627B6918D002E6B18 /* Sources */,
|
||||
1850253727B6918D002E6B18 /* Frameworks */,
|
||||
1850253827B6918D002E6B18 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
1850253C27B6918D002E6B18 /* PBXTargetDependency */,
|
||||
);
|
||||
name = ElementXTests;
|
||||
productName = ElementXTests;
|
||||
productReference = 1850253A27B6918D002E6B18 /* ElementXTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
1850254327B6918D002E6B18 /* ElementXUITests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 1850255427B6918D002E6B18 /* Build configuration list for PBXNativeTarget "ElementXUITests" */;
|
||||
buildPhases = (
|
||||
1850254027B6918D002E6B18 /* Sources */,
|
||||
1850254127B6918D002E6B18 /* Frameworks */,
|
||||
1850254227B6918D002E6B18 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
1850254627B6918D002E6B18 /* PBXTargetDependency */,
|
||||
);
|
||||
name = ElementXUITests;
|
||||
productName = ElementXUITests;
|
||||
productReference = 1850254427B6918D002E6B18 /* ElementXUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.ui-testing";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
1850251C27B6918C002E6B18 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1320;
|
||||
LastUpgradeCheck = 1320;
|
||||
TargetAttributes = {
|
||||
1850252327B6918C002E6B18 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
};
|
||||
1850253927B6918D002E6B18 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
TestTargetID = 1850252327B6918C002E6B18;
|
||||
};
|
||||
1850254327B6918D002E6B18 = {
|
||||
CreatedOnToolsVersion = 13.2.1;
|
||||
TestTargetID = 1850252327B6918C002E6B18;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 1850251F27B6918C002E6B18 /* Build configuration list for PBXProject "ElementX" */;
|
||||
compatibilityVersion = "Xcode 13.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 1850251B27B6918C002E6B18;
|
||||
packageReferences = (
|
||||
1850255727B69388002E6B18 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */,
|
||||
1863A3FA27BA5A9100B52E4D /* XCRemoteSwiftPackageReference "KeychainAccess" */,
|
||||
1863A40427BA6DFC00B52E4D /* XCRemoteSwiftPackageReference "SwiftyBeaver" */,
|
||||
);
|
||||
productRefGroup = 1850252527B6918C002E6B18 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
1850252327B6918C002E6B18 /* ElementX */,
|
||||
1850253927B6918D002E6B18 /* ElementXTests */,
|
||||
1850254327B6918D002E6B18 /* ElementXUITests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
1850252227B6918C002E6B18 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1850257127B6A135002E6B18 /* LaunchScreen.storyboard in Resources */,
|
||||
1863A46027BAA60300B52E4D /* SplashViewController.xib in Resources */,
|
||||
1850257027B6A135002E6B18 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1850253827B6918D002E6B18 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1850254227B6918D002E6B18 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
184230FC27BAAC5800033771 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
1850252027B6918C002E6B18 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1863A44B27BA79FF00B52E4D /* RootRouterType.swift in Sources */,
|
||||
1863A48C27BAA8A900B52E4D /* HomeScreenViewModel.swift in Sources */,
|
||||
1863A41727BA716A00B52E4D /* KeychainControllerProtocol.swift in Sources */,
|
||||
1863A45B27BA7B4700B52E4D /* LoginScreenCoordinator.swift in Sources */,
|
||||
1863A41427BA716A00B52E4D /* AuthenticationCoordinator.swift in Sources */,
|
||||
1863A43B27BA789800B52E4D /* BindableState.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 */,
|
||||
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 */,
|
||||
1863A45627BA7A7800B52E4D /* WeakDictionaryKeyReference.swift in Sources */,
|
||||
1863A49427BAAA6700B52E4D /* RoomModel.swift in Sources */,
|
||||
1850256F27B6A135002E6B18 /* AppDelegate.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 */,
|
||||
1863A44E27BA79FF00B52E4D /* Presentable.swift in Sources */,
|
||||
1850256C27B6A135002E6B18 /* AppCoordinator.swift in Sources */,
|
||||
1863A43127BA784300B52E4D /* LoginScreenModels.swift in Sources */,
|
||||
1863A44A27BA79FF00B52E4D /* NavigationRouter.swift in Sources */,
|
||||
1863A45927BA7A7800B52E4D /* WeakKeyDictionary.swift in Sources */,
|
||||
1863A44C27BA79FF00B52E4D /* NavigationRouterStoreProtocol.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1850253627B6918D002E6B18 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1863A48F27BAA8CC00B52E4D /* HomeScreenViewModelTests.swift in Sources */,
|
||||
1863A43427BA786400B52E4D /* LoginScreenViewModelTests.swift in Sources */,
|
||||
1850253F27B6918D002E6B18 /* ElementXTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
1850254027B6918D002E6B18 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
1850254B27B6918D002E6B18 /* ElementXUITestsLaunchTests.swift in Sources */,
|
||||
1863A48E27BAA8C800B52E4D /* HomeScreenUITests.swift in Sources */,
|
||||
1863A43527BA788500B52E4D /* LoginScreenUITests.swift in Sources */,
|
||||
1850254927B6918D002E6B18 /* ElementXUITests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
1850253C27B6918D002E6B18 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 1850252327B6918C002E6B18 /* ElementX */;
|
||||
targetProxy = 1850253B27B6918D002E6B18 /* PBXContainerItemProxy */;
|
||||
};
|
||||
1850254627B6918D002E6B18 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 1850252327B6918C002E6B18 /* ElementX */;
|
||||
targetProxy = 1850254527B6918D002E6B18 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
1850256927B6A135002E6B18 /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
1850256A27B6A135002E6B18 /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
1850254C27B6918D002E6B18 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
1850254D27B6918D002E6B18 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
INFOPLIST_FILE = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
1850254F27B6918D002E6B18 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = ElementX/ElementX.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.element.ElementX;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
1850255027B6918D002E6B18 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = ElementX/ElementX.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.element.ElementX;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
1850255227B6918D002E6B18 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.element.ElementXTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ElementX.app/ElementX";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
1850255327B6918D002E6B18 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.element.ElementXTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ElementX.app/ElementX";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
1850255527B6918D002E6B18 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.element.ElementXUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = ElementX;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
1850255627B6918D002E6B18 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 7J4U792NQT;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = io.element.ElementXUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = ElementX;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
1850251F27B6918C002E6B18 /* Build configuration list for PBXProject "ElementX" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
1850254C27B6918D002E6B18 /* Debug */,
|
||||
1850254D27B6918D002E6B18 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
1850254E27B6918D002E6B18 /* Build configuration list for PBXNativeTarget "ElementX" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
1850254F27B6918D002E6B18 /* Debug */,
|
||||
1850255027B6918D002E6B18 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
1850255127B6918D002E6B18 /* Build configuration list for PBXNativeTarget "ElementXTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
1850255227B6918D002E6B18 /* Debug */,
|
||||
1850255327B6918D002E6B18 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
1850255427B6918D002E6B18 /* Build configuration list for PBXNativeTarget "ElementXUITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
1850255527B6918D002E6B18 /* Debug */,
|
||||
1850255627B6918D002E6B18 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
1850255727B69388002E6B18 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift.git";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
1863A3FA27BA5A9100B52E4D /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
1863A40427BA6DFC00B52E4D /* XCRemoteSwiftPackageReference "SwiftyBeaver" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SwiftyBeaver/SwiftyBeaver";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
1850255827B69388002E6B18 /* MatrixRustSDK */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 1850255727B69388002E6B18 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */;
|
||||
productName = MatrixRustSDK;
|
||||
};
|
||||
1863A3FB27BA5A9100B52E4D /* KeychainAccess */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 1863A3FA27BA5A9100B52E4D /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||
productName = KeychainAccess;
|
||||
};
|
||||
1863A40527BA6DFC00B52E4D /* SwiftyBeaver */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 1863A40427BA6DFC00B52E4D /* XCRemoteSwiftPackageReference "SwiftyBeaver" */;
|
||||
productName = SwiftyBeaver;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 1850251C27B6918C002E6B18 /* Project object */;
|
||||
}
|
7
ElementX.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
ElementX.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "KeychainAccess",
|
||||
"repositoryURL": "https://github.com/kishikawakatsumi/KeychainAccess",
|
||||
"state": {
|
||||
"branch": "master",
|
||||
"revision": "6299daec1d74be12164fec090faf9ed14d0da9d6",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "MatrixRustSDK",
|
||||
"repositoryURL": "https://github.com/matrix-org/matrix-rust-components-swift.git",
|
||||
"state": {
|
||||
"branch": "main",
|
||||
"revision": "cb680b1783849ecabd0bdf61f65faff767ce32c8",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "SwiftyBeaver",
|
||||
"repositoryURL": "https://github.com/SwiftyBeaver/SwiftyBeaver",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "2c039501d6eeb4d4cd4aec4a8d884ad28862e044",
|
||||
"version": "1.9.5"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
78
ElementX/Sources/AppCoordinator.swift
Normal file
78
ElementX/Sources/AppCoordinator.swift
Normal file
@ -0,0 +1,78 @@
|
||||
//
|
||||
// AppCoordinator.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 11.02.2022.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
private let window: UIWindow
|
||||
|
||||
private let mainNavigationController: UINavigationController
|
||||
private let splashViewController: UIViewController
|
||||
|
||||
private let navigationRouter: NavigationRouter
|
||||
|
||||
private let keychainController: KeychainControllerProtocol
|
||||
private let authenticationCoordinator: AuthenticationCoordinator!
|
||||
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
init() {
|
||||
splashViewController = SplashViewController()
|
||||
mainNavigationController = UINavigationController(rootViewController: splashViewController)
|
||||
mainNavigationController.setNavigationBarHidden(true, animated: false)
|
||||
window = UIWindow(frame: UIScreen.main.bounds)
|
||||
window.rootViewController = mainNavigationController
|
||||
|
||||
navigationRouter = NavigationRouter(navigationController: mainNavigationController)
|
||||
|
||||
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
|
||||
fatalError("Should have a valid bundle identifier at this point")
|
||||
}
|
||||
|
||||
keychainController = KeychainController(identifier: bundleIdentifier)
|
||||
authenticationCoordinator = AuthenticationCoordinator(keychainController: keychainController,
|
||||
navigationRouter: navigationRouter)
|
||||
authenticationCoordinator.delegate = self
|
||||
}
|
||||
|
||||
func start() {
|
||||
window.makeKeyAndVisible()
|
||||
authenticationCoordinator.start()
|
||||
}
|
||||
|
||||
// MARK: - AuthenticationCoordinatorDelegate
|
||||
|
||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didFailWithError error: AuthenticationCoordinatorError) {
|
||||
|
||||
}
|
||||
|
||||
func authenticationCoordinatorDidSetupUserSession(_ authenticationCoordinator: AuthenticationCoordinator) {
|
||||
presentHomeScreen()
|
||||
}
|
||||
|
||||
func authenticationCoordinatorDidTearDownUserSession(_ authenticationCoordinator: AuthenticationCoordinator) {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func presentHomeScreen() {
|
||||
guard let userSession = authenticationCoordinator.userSession else {
|
||||
fatalError("User session should be already setup at this point")
|
||||
}
|
||||
|
||||
let parameters = HomeScreenCoordinatorParameters(userSession: userSession)
|
||||
let coordinator = HomeScreenCoordinator(parameters: parameters)
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
navigationRouter.setRootModule(coordinator)
|
||||
}
|
||||
|
||||
private func restart() {
|
||||
|
||||
}
|
||||
}
|
20
ElementX/Sources/AppDelegate.swift
Normal file
20
ElementX/Sources/AppDelegate.swift
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 11.02.2022.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
private var appCoordinator: AppCoordinator!
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
appCoordinator = AppCoordinator()
|
||||
appCoordinator.start()
|
||||
return true
|
||||
}
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
//
|
||||
// AuthenticationCoordinator.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 11.02.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
enum AuthenticationCoordinatorError: Error {
|
||||
case failedLoggingIn
|
||||
case failedRestoringLogin
|
||||
case failedSettingUpSession
|
||||
}
|
||||
|
||||
protocol AuthenticationCoordinatorDelegate: AnyObject {
|
||||
func authenticationCoordinatorDidSetupUserSession(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
func authenticationCoordinatorDidTearDownUserSession(_ authenticationCoordinator: AuthenticationCoordinator)
|
||||
|
||||
func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator,
|
||||
didFailWithError error: AuthenticationCoordinatorError)
|
||||
}
|
||||
|
||||
class AuthenticationCoordinator: Coordinator {
|
||||
|
||||
private let keychainController: KeychainControllerProtocol
|
||||
private let navigationRouter: NavigationRouter
|
||||
|
||||
private(set) var userSession: UserSession?
|
||||
var childCoordinators: [Coordinator] = []
|
||||
|
||||
weak var delegate: AuthenticationCoordinatorDelegate?
|
||||
|
||||
init(keychainController: KeychainControllerProtocol,
|
||||
navigationRouter: NavigationRouter) {
|
||||
self.keychainController = keychainController
|
||||
self.navigationRouter = navigationRouter
|
||||
}
|
||||
|
||||
func start() {
|
||||
let availableRestoreTokens = keychainController.restoreTokens()
|
||||
|
||||
guard let usernameTokenTuple = availableRestoreTokens.first else {
|
||||
startNewLoginFlow()
|
||||
return
|
||||
}
|
||||
|
||||
restorePreviousLogin(usernameTokenTuple)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func startNewLoginFlow() {
|
||||
let parameters = LoginScreenCoordinatorParameters()
|
||||
let coordinator = LoginScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.completion = { [weak self, weak coordinator] result in
|
||||
guard let self = self, let coordinator = coordinator else {
|
||||
return
|
||||
}
|
||||
|
||||
switch result {
|
||||
case .login(let result):
|
||||
do {
|
||||
self.setupUserSessionForClient(try loginNewClient(basePath: self.baseDirectoryPathForUsername(result.username),
|
||||
username: result.username,
|
||||
password: result.password))
|
||||
|
||||
self.remove(childCoordinator: coordinator)
|
||||
self.navigationRouter.dismissModule()
|
||||
} catch {
|
||||
self.delegate?.authenticationCoordinator(self, didFailWithError: .failedLoggingIn)
|
||||
MXLog.error("Failed logging in user with error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
navigationRouter.present(coordinator)
|
||||
|
||||
coordinator.start()
|
||||
}
|
||||
|
||||
private func restorePreviousLogin(_ usernameTokenTuple: (username: String, token: String)) {
|
||||
do {
|
||||
setupUserSessionForClient(try loginWithToken(basePath: baseDirectoryPathForUsername(usernameTokenTuple.username),
|
||||
restoreToken: usernameTokenTuple.token))
|
||||
} catch {
|
||||
delegate?.authenticationCoordinator(self, didFailWithError: .failedRestoringLogin)
|
||||
MXLog.error("Failed restoring login with error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func setupUserSessionForClient(_ client: Client) {
|
||||
|
||||
do {
|
||||
let restoreToken = try client.restoreToken()
|
||||
let userId = try client.userId()
|
||||
|
||||
keychainController.setRestoreToken(restoreToken, forUsername: userId)
|
||||
} catch {
|
||||
delegate?.authenticationCoordinator(self, didFailWithError: .failedSettingUpSession)
|
||||
MXLog.error("Failed setting up user session with error: \(error)")
|
||||
}
|
||||
|
||||
userSession = UserSession(client: client)
|
||||
delegate?.authenticationCoordinatorDidSetupUserSession(self)
|
||||
}
|
||||
|
||||
private func baseDirectoryPathForUsername(_ username: String) -> String {
|
||||
guard var url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
|
||||
fatalError("Should always be able to retrieve the caches directory")
|
||||
}
|
||||
|
||||
url = url.appendingPathComponent(username)
|
||||
|
||||
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
return url.path
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
//
|
||||
// KeychainController.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 14.02.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KeychainAccess
|
||||
|
||||
class KeychainController: KeychainControllerProtocol {
|
||||
|
||||
struct Constants {
|
||||
static let restoreTokenGroupKey = "restoreTokens"
|
||||
}
|
||||
|
||||
private let keychain: Keychain
|
||||
|
||||
init(identifier: String) {
|
||||
keychain = Keychain(service: identifier)
|
||||
}
|
||||
|
||||
func setRestoreToken(_ token: String, forUsername username: String) {
|
||||
do {
|
||||
try keychain.set(token, key: username)
|
||||
} catch {
|
||||
MXLog.error("Failed storing user restore token")
|
||||
}
|
||||
}
|
||||
|
||||
func restoreTokenForUsername(_ username: String) -> String? {
|
||||
do {
|
||||
return try keychain.get(username)
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving user restore token")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func restoreTokens() -> [(username: String, token: String)] {
|
||||
keychain.allKeys().compactMap { username in
|
||||
guard let token = restoreTokenForUsername(username) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (username, token)
|
||||
}
|
||||
}
|
||||
|
||||
func removeAllTokens() {
|
||||
do {
|
||||
try keychain.removeAll()
|
||||
} catch {
|
||||
MXLog.error("Failed removing all tokens")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
//
|
||||
// KeychainControllerProtocol.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 14.02.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol KeychainControllerProtocol {
|
||||
func setRestoreToken(_ token: String, forUsername username: String)
|
||||
func restoreTokenForUsername(_ username: String) -> String?
|
||||
func restoreTokens() -> [(username: String, token: String)]
|
||||
func removeAllTokens()
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
//
|
||||
// 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 LoginScreenCoordinatorParameters {
|
||||
|
||||
}
|
||||
|
||||
final class LoginScreenCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: LoginScreenCoordinatorParameters
|
||||
private let loginScreenHostingController: UIViewController
|
||||
private var loginScreenViewModel: LoginScreenViewModelProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: ((LoginScreenViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
init(parameters: LoginScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
loginScreenViewModel = LoginScreenViewModel()
|
||||
let view = LoginScreen(context: loginScreenViewModel.context)
|
||||
|
||||
loginScreenHostingController = UIHostingController(rootView: view)
|
||||
loginScreenHostingController.isModalInPresentation = true
|
||||
|
||||
loginScreenViewModel.completion = { [weak self] result in
|
||||
MXLog.debug("[LoginScreenCoordinator] LoginScreenViewModel did complete with result: \(result).")
|
||||
guard let self = self else { return }
|
||||
self.completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.loginScreenHostingController
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
//
|
||||
// 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 LoginScreenViewModelResult {
|
||||
case login((username: String, password: String))
|
||||
}
|
||||
|
||||
enum LoginScreenViewAction {
|
||||
case login
|
||||
}
|
||||
|
||||
struct LoginScreenViewState: BindableState {
|
||||
var bindings: LoginScreenViewStateBindings
|
||||
}
|
||||
|
||||
struct LoginScreenViewStateBindings {
|
||||
var username: String
|
||||
var password: String
|
||||
}
|
||||
|
||||
struct LoginScreenErrorAlertInfo: Identifiable {
|
||||
enum AlertType {
|
||||
case genericFailure
|
||||
}
|
||||
|
||||
let id: AlertType
|
||||
let title: String
|
||||
let subtitle: String
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
//
|
||||
// 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 LoginScreenViewModelType = StateStoreViewModel<LoginScreenViewState,
|
||||
Never,
|
||||
LoginScreenViewAction>
|
||||
@available(iOS 14, *)
|
||||
class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: ((LoginScreenViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init() {
|
||||
super.init(initialViewState: LoginScreenViewState(bindings: LoginScreenViewStateBindings(username: "@stefan.ceriu-element01:matrix.org",
|
||||
password: "radeon")))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: LoginScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .login:
|
||||
completion?(.login((username: context.username, password: context.password)))
|
||||
}
|
||||
}
|
||||
}
|
@ -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 LoginScreenViewModelProtocol {
|
||||
|
||||
var completion: ((LoginScreenViewModelResult) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
var context: LoginScreenViewModelType.Context { get }
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
//
|
||||
// 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
|
||||
import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class LoginScreenUITests: MockScreenTest {
|
||||
|
||||
override class var screenType: MockScreenState.Type {
|
||||
return MockLoginScreenScreenState.self
|
||||
}
|
||||
|
||||
override class func createTest() -> MockScreenTest {
|
||||
return LoginScreenUITests(selector: #selector(verifyLoginScreenScreen))
|
||||
}
|
||||
|
||||
func verifyLoginScreenScreen() throws {
|
||||
guard let screenState = screenState as? MockLoginScreenScreenState else { fatalError("no screen") }
|
||||
switch screenState {
|
||||
case .promptType(let promptType):
|
||||
verifyLoginScreenPromptType(promptType: promptType)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyLoginScreenPromptType(promptType: LoginScreenPromptType) {
|
||||
let title = app.staticTexts["title"]
|
||||
XCTAssert(title.exists)
|
||||
XCTAssertEqual(title.label, promptType.title)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class LoginScreenViewModelTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
//
|
||||
// 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.0, *)
|
||||
struct LoginScreen: View {
|
||||
|
||||
@ObservedObject var context: LoginScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
TextField("Username", text: $context.username)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
SecureField("Enter a password", text: $context.password)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
|
||||
Button { context.send(viewAction: .login) } label: {
|
||||
Text("Login")
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.padding(.horizontal, 50)
|
||||
}
|
||||
.padding(.horizontal, 8.0)
|
||||
.navigationTitle("Login")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct LoginScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let viewModel = LoginScreenViewModel()
|
||||
LoginScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
44
ElementX/Sources/Modules/Authentication/UserSession.swift
Normal file
44
ElementX/Sources/Modules/Authentication/UserSession.swift
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// UserSession.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 14.02.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
class UserSession {
|
||||
|
||||
private let client: Client
|
||||
|
||||
init(client: Client) {
|
||||
self.client = client
|
||||
|
||||
if !client.hasFirstSynced() {
|
||||
MXLog.info("Started initial sync")
|
||||
client.startSync()
|
||||
MXLog.info("Finished intial sync")
|
||||
}
|
||||
}
|
||||
|
||||
func roomList() -> [RoomModel] {
|
||||
client.conversations().compactMap { room in
|
||||
do {
|
||||
return RoomModel(displayName: try room.displayName())
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving room info with error: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var displayName: String? {
|
||||
do {
|
||||
return try client.displayName()
|
||||
} catch {
|
||||
MXLog.error("Failed retrieving room info with error: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
//
|
||||
// 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 HomeScreenCoordinatorParameters {
|
||||
let userSession: UserSession
|
||||
}
|
||||
|
||||
final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: HomeScreenCoordinatorParameters
|
||||
private let homeScreenHostingController: UIViewController
|
||||
private var homeScreenViewModel: HomeScreenViewModelProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: ((HomeScreenViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
init(parameters: HomeScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let viewModel = HomeScreenViewModel(username: self.parameters.userSession.displayName ?? "💥")
|
||||
let view = HomeScreen(context: viewModel.context)
|
||||
homeScreenViewModel = viewModel
|
||||
homeScreenHostingController = UIHostingController(rootView: view)
|
||||
|
||||
homeScreenViewModel.completion = { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
self.completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.homeScreenHostingController
|
||||
}
|
||||
}
|
31
ElementX/Sources/Modules/HomeScreen/HomeScreenModels.swift
Normal file
31
ElementX/Sources/Modules/HomeScreen/HomeScreenModels.swift
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// 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 HomeScreenViewModelResult {
|
||||
case logout
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct HomeScreenViewState: BindableState {
|
||||
let username: String
|
||||
}
|
||||
|
||||
enum HomeScreenViewAction {
|
||||
case logout
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
//
|
||||
// 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 HomeScreenViewModelType = StateStoreViewModel<HomeScreenViewState,
|
||||
Never,
|
||||
HomeScreenViewAction>
|
||||
@available(iOS 14, *)
|
||||
class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: ((HomeScreenViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(username: String) {
|
||||
super.init(initialViewState: HomeScreenViewState(username: username))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: HomeScreenViewAction) {
|
||||
|
||||
}
|
||||
}
|
@ -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 HomeScreenViewModelProtocol {
|
||||
|
||||
var completion: ((HomeScreenViewModelResult) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
var context: HomeScreenViewModelType.Context { get }
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
//
|
||||
// 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
|
||||
import ElementX
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class HomeScreenUITests: MockScreenTest {
|
||||
|
||||
override class var screenType: MockScreenState.Type {
|
||||
return MockHomeScreenScreenState.self
|
||||
}
|
||||
|
||||
override class func createTest() -> MockScreenTest {
|
||||
return HomeScreenUITests(selector: #selector(verifyHomeScreenScreen))
|
||||
}
|
||||
|
||||
func verifyHomeScreenScreen() throws {
|
||||
guard let screenState = screenState as? MockHomeScreenScreenState else { fatalError("no screen") }
|
||||
switch screenState {
|
||||
case .promptType(let promptType):
|
||||
verifyHomeScreenPromptType(promptType: promptType)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyHomeScreenPromptType(promptType: HomeScreenPromptType) {
|
||||
let title = app.staticTexts["title"]
|
||||
XCTAssert(title.exists)
|
||||
XCTAssertEqual(title.label, promptType.title)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class HomeScreenViewModelTests: XCTestCase {
|
||||
override func setUpWithError() throws {
|
||||
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
|
||||
}
|
||||
}
|
41
ElementX/Sources/Modules/HomeScreen/View/HomeScreen.swift
Normal file
41
ElementX/Sources/Modules/HomeScreen/View/HomeScreen.swift
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// 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.0, *)
|
||||
struct HomeScreen: View {
|
||||
|
||||
@ObservedObject var context: HomeScreenViewModel.Context
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Hello, \(context.viewState.username)!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct HomeScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let viewModel = HomeScreenViewModel(username: "Johnny Appleseed")
|
||||
HomeScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
13
ElementX/Sources/Modules/Models/RoomModel.swift
Normal file
13
ElementX/Sources/Modules/Models/RoomModel.swift
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// RoomModel.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 14.02.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct RoomModel {
|
||||
let displayName: String
|
||||
}
|
51
ElementX/Sources/Modules/Other/Coordinator.swift
Executable file
51
ElementX/Sources/Modules/Other/Coordinator.swift
Executable file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2019 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 UIKit
|
||||
|
||||
/// Protocol describing a [Coordinator](http://khanlou.com/2015/10/coordinators-redux/).
|
||||
/// Coordinators are the objects which control the navigation flow of the application.
|
||||
/// It helps to isolate and reuse view controllers and pass dependencies down the navigation hierarchy.
|
||||
protocol Coordinator: AnyObject {
|
||||
|
||||
/// Starts job of the coordinator.
|
||||
func start()
|
||||
|
||||
/// Child coordinators to retain. Prevent them from getting deallocated.
|
||||
var childCoordinators: [Coordinator] { get set }
|
||||
|
||||
/// Stores coordinator to the `childCoordinators` array.
|
||||
///
|
||||
/// - Parameter childCoordinator: Child coordinator to store.
|
||||
func add(childCoordinator: Coordinator)
|
||||
|
||||
/// Remove coordinator from the `childCoordinators` array.
|
||||
///
|
||||
/// - Parameter childCoordinator: Child coordinator to remove.
|
||||
func remove(childCoordinator: Coordinator)
|
||||
}
|
||||
|
||||
// `Coordinator` default implementation
|
||||
extension Coordinator {
|
||||
|
||||
func add(childCoordinator coordinator: Coordinator) {
|
||||
childCoordinators.append(coordinator)
|
||||
}
|
||||
|
||||
func remove(childCoordinator: Coordinator) {
|
||||
self.childCoordinators = self.childCoordinators.filter { $0 !== childCoordinator }
|
||||
}
|
||||
}
|
179
ElementX/Sources/Modules/Other/MXLog.swift
Normal file
179
ElementX/Sources/Modules/Other/MXLog.swift
Normal file
@ -0,0 +1,179 @@
|
||||
//
|
||||
// Copyright 2021 The Matrix.org Foundation C.I.C
|
||||
//
|
||||
// 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
|
||||
import SwiftyBeaver
|
||||
|
||||
/// Various MXLog configuration options. Used in conjunction with `MXLog.configure()`
|
||||
@objc public class MXLogConfiguration: NSObject {
|
||||
|
||||
/// the desired log level. `.verbose` by default.
|
||||
@objc public var logLevel: MXLogLevel = MXLogLevel.verbose
|
||||
|
||||
/// whether logs should be written directly to files. `false` by default.
|
||||
@objc public var redirectLogsToFiles: Bool = false
|
||||
|
||||
/// the maximum total space to use for log files in bytes. `100MB` by default.
|
||||
@objc public var logFilesSizeLimit: UInt = 100 * 1024 * 1024 // 100MB
|
||||
|
||||
/// the maximum number of log files to use before rolling. `50` by default.
|
||||
@objc public var maxLogFilesCount: UInt = 50
|
||||
|
||||
/// the subname for log files. Files will be named as 'console-[subLogName].log'. `nil` by default
|
||||
@objc public var subLogName: String?
|
||||
}
|
||||
|
||||
/// MXLog logging levels. Use .none to disable logging entirely.
|
||||
@objc public enum MXLogLevel: UInt {
|
||||
case none
|
||||
case verbose
|
||||
case debug
|
||||
case info
|
||||
case warning
|
||||
case error
|
||||
}
|
||||
|
||||
private var logger: SwiftyBeaver.Type = {
|
||||
let logger = SwiftyBeaver.self
|
||||
MXLog.configureLogger(logger, withConfiguration: MXLogConfiguration())
|
||||
return logger
|
||||
}()
|
||||
|
||||
/**
|
||||
Logging utility that provies multiple logging levels as well as file output and rolling.
|
||||
Its purpose is to provide a common entry for customizing logging and should be used throughout the code.
|
||||
Please see `MXLog.h` for Objective-C options.
|
||||
*/
|
||||
@objc public class MXLog: NSObject {
|
||||
|
||||
/// Method used to customize MXLog's behavior.
|
||||
/// Called automatically when first accessing the logger with the default values.
|
||||
/// Please see `MXLogConfiguration` for all available options.
|
||||
/// - Parameters:
|
||||
/// - configuration: the `MXLogConfiguration` instance to use
|
||||
@objc static public func configure(_ configuration: MXLogConfiguration) {
|
||||
configureLogger(logger, withConfiguration: configuration)
|
||||
}
|
||||
|
||||
public static func verbose(_ message: @autoclosure () -> Any,
|
||||
_ file: String = #file,
|
||||
_ function: String = #function,
|
||||
line: Int = #line,
|
||||
context: Any? = nil) {
|
||||
logger.verbose(message(), file, function, line: line, context: context)
|
||||
}
|
||||
|
||||
@available(swift, obsoleted: 5.4)
|
||||
@objc public static func logVerbose(_ message: String, file: String, function: String, line: Int) {
|
||||
logger.verbose(message, file, function, line: line)
|
||||
}
|
||||
|
||||
public static func debug(_ message: @autoclosure () -> Any,
|
||||
_ file: String = #file,
|
||||
_ function: String = #function,
|
||||
line: Int = #line,
|
||||
context: Any? = nil) {
|
||||
logger.debug(message(), file, function, line: line, context: context)
|
||||
}
|
||||
|
||||
@available(swift, obsoleted: 5.4)
|
||||
@objc public static func logDebug(_ message: String, file: String, function: String, line: Int) {
|
||||
logger.debug(message, file, function, line: line)
|
||||
}
|
||||
|
||||
public static func info(_ message: @autoclosure () -> Any,
|
||||
_ file: String = #file,
|
||||
_ function: String = #function,
|
||||
line: Int = #line,
|
||||
context: Any? = nil) {
|
||||
logger.info(message(), file, function, line: line, context: context)
|
||||
}
|
||||
|
||||
@available(swift, obsoleted: 5.4)
|
||||
@objc public static func logInfo(_ message: String, file: String, function: String, line: Int) {
|
||||
logger.info(message, file, function, line: line)
|
||||
}
|
||||
|
||||
public static func warning(_ message: @autoclosure () -> Any, _
|
||||
file: String = #file,
|
||||
_ function: String = #function,
|
||||
line: Int = #line,
|
||||
context: Any? = nil) {
|
||||
logger.warning(message(), file, function, line: line, context: context)
|
||||
}
|
||||
|
||||
@available(swift, obsoleted: 5.4)
|
||||
@objc public static func logWarning(_ message: String, file: String, function: String, line: Int) {
|
||||
logger.warning(message, file, function, line: line)
|
||||
}
|
||||
|
||||
public static func error(_ message: @autoclosure () -> Any,
|
||||
_ file: String = #file,
|
||||
_ function: String = #function,
|
||||
line: Int = #line,
|
||||
context: Any? = nil) {
|
||||
logger.error(message(), file, function, line: line, context: context)
|
||||
}
|
||||
|
||||
@available(swift, obsoleted: 5.4)
|
||||
@objc public static func logError(_ message: String, file: String, function: String, line: Int) {
|
||||
logger.error(message, file, function, line: line)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
fileprivate static func configureLogger(_ logger: SwiftyBeaver.Type, withConfiguration configuration: MXLogConfiguration) {
|
||||
// if let subLogName = configuration.subLogName {
|
||||
// MXLogger.setSubLogName(subLogName)
|
||||
// }
|
||||
//
|
||||
// MXLogger.redirectNSLog(toFiles: configuration.redirectLogsToFiles,
|
||||
// numberOfFiles: configuration.maxLogFilesCount,
|
||||
// sizeLimit: configuration.logFilesSizeLimit)
|
||||
//
|
||||
guard configuration.logLevel != .none else {
|
||||
return
|
||||
}
|
||||
|
||||
let consoleDestination = ConsoleDestination()
|
||||
consoleDestination.useNSLog = true
|
||||
consoleDestination.asynchronously = false
|
||||
consoleDestination.format = "$C $N.$F():$l $M"
|
||||
consoleDestination.levelColor.verbose = ""
|
||||
consoleDestination.levelColor.debug = ""
|
||||
consoleDestination.levelColor.info = ""
|
||||
consoleDestination.levelColor.warning = "⚠️"
|
||||
consoleDestination.levelColor.error = "🚨"
|
||||
|
||||
switch configuration.logLevel {
|
||||
case .verbose:
|
||||
consoleDestination.minLevel = .verbose
|
||||
case .debug:
|
||||
consoleDestination.minLevel = .debug
|
||||
case .info:
|
||||
consoleDestination.minLevel = .info
|
||||
case .warning:
|
||||
consoleDestination.minLevel = .warning
|
||||
case .error:
|
||||
consoleDestination.minLevel = .error
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
|
||||
logger.removeAllDestinations()
|
||||
logger.addDestination(consoleDestination)
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Represents a specific portion of the ViewState that can be bound to with SwiftUI's [2-way binding](https://developer.apple.com/documentation/swiftui/binding).
|
||||
protocol BindableState {
|
||||
/// The associated type of the Bindable State. Defaults to Void.
|
||||
associatedtype BindStateType = Void
|
||||
var bindings: BindStateType { get set }
|
||||
}
|
||||
|
||||
extension BindableState where BindStateType == Void {
|
||||
/// We provide a default implementation for the Void type so that we can have `ViewState` that
|
||||
/// just doesn't include/take advantage of the bindings.
|
||||
var bindings: Void {
|
||||
get {
|
||||
|
||||
}
|
||||
set {
|
||||
fatalError("Can't bind to the default Void binding.")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
//
|
||||
// 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
|
||||
import Combine
|
||||
|
||||
/// A constrained and concise interface for interacting with the ViewModel.
|
||||
///
|
||||
/// This class is closely bound to`StateStoreViewModel`. It provides the exact interface the view should need to interact
|
||||
/// ViewModel (as modelled on our previous template architecture with the addition of two-way binding):
|
||||
/// - The ability read/observe view state
|
||||
/// - The ability to send view events
|
||||
/// - The ability to bind state to a specific portion of the view state safely.
|
||||
/// This class was brought about a little bit by necessity. The most idiomatic way of interacting with SwiftUI is via `@Published`
|
||||
/// properties which which are property wrappers and therefore can't be defined within protocols.
|
||||
/// A similar approach is taken in libraries like [CombineFeedback](https://github.com/sergdort/CombineFeedback).
|
||||
/// It provides a nice layer of consistency and also safety. As we are not passing the `ViewModel` to the view directly, shortcuts/hacks
|
||||
/// can't be made into the `ViewModel`.
|
||||
@available(iOS 14, *)
|
||||
@dynamicMemberLookup
|
||||
class ViewModelContext<ViewState: BindableState, ViewAction>: ObservableObject {
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
fileprivate let viewActions: PassthroughSubject<ViewAction, Never>
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// Get-able/Observable `Published` property for the `ViewState`
|
||||
@Published fileprivate(set) var viewState: ViewState
|
||||
|
||||
/// Set-able/Bindable access to the bindable state.
|
||||
subscript<T>(dynamicMember keyPath: WritableKeyPath<ViewState.BindStateType, T>) -> T {
|
||||
get { viewState.bindings[keyPath: keyPath] }
|
||||
set { viewState.bindings[keyPath: keyPath] = newValue }
|
||||
}
|
||||
|
||||
// MARK: Setup
|
||||
|
||||
init(initialViewState: ViewState) {
|
||||
self.viewActions = PassthroughSubject()
|
||||
self.viewState = initialViewState
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// Send a `ViewAction` to the `ViewModel` for processing.
|
||||
/// - Parameter viewAction: The `ViewAction` to send to the `ViewModel`.
|
||||
func send(viewAction: ViewAction) {
|
||||
viewActions.send(viewAction)
|
||||
}
|
||||
}
|
||||
|
||||
/// A common ViewModel implementation for handling of `State`, `StateAction`s and `ViewAction`s
|
||||
///
|
||||
/// Generic type State is constrained to the BindableState protocol in that it may contain (but doesn't have to)
|
||||
/// a specific portion of state that can be safely bound to.
|
||||
/// If we decide to add more features to our state management (like doing state processing off the main thread)
|
||||
/// we can do it in this centralised place.
|
||||
@available(iOS 14, *)
|
||||
class StateStoreViewModel<State: BindableState, StateAction, ViewAction> {
|
||||
|
||||
typealias Context = ViewModelContext<State, ViewAction>
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// For storing subscription references.
|
||||
///
|
||||
/// Left as public for `ViewModel` implementations convenience.
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
/// Constrained interface for passing to Views.
|
||||
var context: Context
|
||||
|
||||
var state: State {
|
||||
get { context.viewState }
|
||||
set { context.viewState = newValue }
|
||||
}
|
||||
|
||||
// MARK: Setup
|
||||
|
||||
init(initialViewState: State) {
|
||||
self.context = Context(initialViewState: initialViewState)
|
||||
self.context.viewActions.sink { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
self.process(viewAction: action)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
/// Send state actions to modify the state within the reducer.
|
||||
/// - Parameter action: The state action to send to the reducer.
|
||||
@available(*, deprecated, message: "Mutate state directly instead")
|
||||
func dispatch(action: StateAction) {
|
||||
Self.reducer(state: &context.viewState, action: action)
|
||||
}
|
||||
|
||||
/// Send state actions from a publisher to modify the state within the reducer.
|
||||
/// - Parameter actionPublisher: The publisher that produces actions to be sent to the reducer
|
||||
@available(*, deprecated, message: "Mutate state directly instead")
|
||||
func dispatch(actionPublisher: AnyPublisher<StateAction, Never>) {
|
||||
actionPublisher.sink { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
Self.reducer(state: &self.context.viewState, action: action)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
/// Override to handle mutations to the `State`
|
||||
///
|
||||
/// A redux style reducer, all modifications to state happen here.
|
||||
/// - Parameters:
|
||||
/// - state: The `inout` state to be modified,
|
||||
/// - action: The action that defines which state modification should take place.
|
||||
class func reducer(state: inout State, action: StateAction) {
|
||||
// Default implementation, -no-op
|
||||
}
|
||||
|
||||
/// Override to handles incoming `ViewAction`s from the `ViewModel`.
|
||||
/// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation.
|
||||
func process(viewAction: ViewAction) {
|
||||
// Default implementation, -no-op
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
//
|
||||
// WeakDictionary.swift
|
||||
// WeakDictionary
|
||||
//
|
||||
// Created by Nicholas Cross on 19/10/2016.
|
||||
// Copyright © 2016 Nicholas Cross. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct WeakDictionary<Key: Hashable, Value: AnyObject> {
|
||||
|
||||
private var storage: [Key: WeakDictionaryReference<Value>]
|
||||
|
||||
public init() {
|
||||
self.init(storage: [Key: WeakDictionaryReference<Value>]())
|
||||
}
|
||||
|
||||
public init(dictionary: [Key: Value]) {
|
||||
var newStorage = [Key: WeakDictionaryReference<Value>]()
|
||||
dictionary.forEach({ key, value in newStorage[key] = WeakDictionaryReference<Value>(value: value) })
|
||||
self.init(storage: newStorage)
|
||||
}
|
||||
|
||||
private init(storage: [Key: WeakDictionaryReference<Value>]) {
|
||||
self.storage = storage
|
||||
}
|
||||
|
||||
public mutating func reap() {
|
||||
storage = weakDictionary().storage
|
||||
}
|
||||
|
||||
public func weakDictionary() -> WeakDictionary<Key, Value> {
|
||||
return self[startIndex ..< endIndex]
|
||||
}
|
||||
|
||||
public func dictionary() -> [Key: Value] {
|
||||
var newStorage = [Key: Value]()
|
||||
|
||||
storage.forEach { key, value in
|
||||
if let retainedValue = value.value {
|
||||
newStorage[key] = retainedValue
|
||||
}
|
||||
}
|
||||
|
||||
return newStorage
|
||||
}
|
||||
}
|
||||
|
||||
extension WeakDictionary: Collection {
|
||||
|
||||
public typealias Index = DictionaryIndex<Key, WeakDictionaryReference<Value>>
|
||||
|
||||
public var startIndex: Index {
|
||||
return storage.startIndex
|
||||
}
|
||||
|
||||
public var endIndex: Index {
|
||||
return storage.endIndex
|
||||
}
|
||||
|
||||
public func index(after index: Index) -> Index {
|
||||
return storage.index(after: index)
|
||||
}
|
||||
|
||||
public subscript(position: Index) -> (Key, WeakDictionaryReference<Value>) {
|
||||
return storage[position]
|
||||
}
|
||||
|
||||
public subscript(key: Key) -> Value? {
|
||||
get {
|
||||
guard let valueRef = storage[key] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return valueRef.value
|
||||
}
|
||||
|
||||
set {
|
||||
guard let value = newValue else {
|
||||
storage[key] = nil
|
||||
return
|
||||
}
|
||||
|
||||
storage[key] = WeakDictionaryReference<Value>(value: value)
|
||||
}
|
||||
}
|
||||
|
||||
public subscript(bounds: Range<Index>) -> WeakDictionary<Key, Value> {
|
||||
let subStorage = storage[bounds.lowerBound ..< bounds.upperBound]
|
||||
var newStorage = [Key: WeakDictionaryReference<Value>]()
|
||||
|
||||
subStorage.filter { _, value in return value.value != nil }
|
||||
.forEach { key, value in newStorage[key] = value }
|
||||
|
||||
return WeakDictionary<Key, Value>(storage: newStorage)
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary where Value: AnyObject {
|
||||
public func weakDictionary() -> WeakDictionary<Key, Value> {
|
||||
return WeakDictionary<Key, Value>(dictionary: self)
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
//
|
||||
// WeakDictionaryKeyReference.swift
|
||||
// WeakDictionary-iOS
|
||||
//
|
||||
// Created by Nicholas Cross on 2/1/19.
|
||||
// Copyright © 2019 Nicholas Cross. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct WeakDictionaryKey<Key: AnyObject & Hashable, Value: AnyObject> : Hashable {
|
||||
|
||||
private weak var baseKey: Key?
|
||||
private let hash: Int
|
||||
private var retainedValue: Value?
|
||||
private let nilKeyHash = UUID().hashValue
|
||||
|
||||
public init(key: Key, value: Value? = nil) {
|
||||
baseKey = key
|
||||
retainedValue = value
|
||||
hash = key.hashValue
|
||||
}
|
||||
|
||||
public static func == (lhs: WeakDictionaryKey, rhs: WeakDictionaryKey) -> Bool {
|
||||
return (lhs.baseKey != nil && rhs.baseKey != nil && lhs.baseKey == rhs.baseKey)
|
||||
|| lhs.hashValue == rhs.hashValue
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return baseKey != nil ? hash : nilKeyHash
|
||||
}
|
||||
|
||||
public var key: Key? {
|
||||
return baseKey
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
//
|
||||
// WeakDictionaryReference.swift
|
||||
// WeakDictionary-iOS
|
||||
//
|
||||
// Created by Nicholas Cross on 2/1/19.
|
||||
// Copyright © 2019 Nicholas Cross. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct WeakDictionaryReference<Value: AnyObject> {
|
||||
private weak var referencedValue: Value?
|
||||
|
||||
init(value: Value) {
|
||||
referencedValue = value
|
||||
}
|
||||
|
||||
public var value: Value? {
|
||||
return referencedValue
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
//
|
||||
// WeakKeyDictionary.swift
|
||||
// WeakDictionary-iOS
|
||||
//
|
||||
// Created by Nicholas Cross on 2/1/19.
|
||||
// Copyright © 2019 Nicholas Cross. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct WeakKeyDictionary<Key: AnyObject & Hashable, Value: AnyObject> {
|
||||
|
||||
private var storage: WeakDictionary<WeakDictionaryKey<Key, Value>, Value>
|
||||
private let valuesRetainedByKey: Bool
|
||||
|
||||
public init(valuesRetainedByKey: Bool = false) {
|
||||
self.init(
|
||||
storage: WeakDictionary<WeakDictionaryKey<Key, Value>, Value>(),
|
||||
valuesRetainedByKey: valuesRetainedByKey
|
||||
)
|
||||
}
|
||||
|
||||
public init(dictionary: [Key: Value], valuesRetainedByKey: Bool = false) {
|
||||
var newStorage = WeakDictionary<WeakDictionaryKey<Key, Value>, Value>()
|
||||
|
||||
dictionary.forEach { key, value in
|
||||
var keyRef: WeakDictionaryKey<Key, Value>!
|
||||
|
||||
if valuesRetainedByKey {
|
||||
keyRef = WeakDictionaryKey<Key, Value>(key: key, value: value)
|
||||
} else {
|
||||
keyRef = WeakDictionaryKey<Key, Value>(key: key)
|
||||
}
|
||||
|
||||
newStorage[keyRef] = value
|
||||
}
|
||||
|
||||
self.init(storage: newStorage, valuesRetainedByKey: valuesRetainedByKey)
|
||||
}
|
||||
|
||||
private init(storage: WeakDictionary<WeakDictionaryKey<Key, Value>, Value>, valuesRetainedByKey: Bool = false) {
|
||||
self.storage = storage
|
||||
self.valuesRetainedByKey = valuesRetainedByKey
|
||||
}
|
||||
|
||||
public mutating func reap() {
|
||||
storage = weakKeyDictionary().storage
|
||||
}
|
||||
|
||||
public func weakDictionary() -> WeakDictionary<Key, Value> {
|
||||
return dictionary().weakDictionary()
|
||||
}
|
||||
|
||||
public func weakKeyDictionary() -> WeakKeyDictionary<Key, Value> {
|
||||
return self[startIndex ..< endIndex]
|
||||
}
|
||||
|
||||
public func dictionary() -> [Key: Value] {
|
||||
var newStorage = [Key: Value]()
|
||||
|
||||
storage.forEach { key, value in
|
||||
if let retainedKey = key.key, let retainedValue = value.value {
|
||||
newStorage[retainedKey] = retainedValue
|
||||
}
|
||||
}
|
||||
|
||||
return newStorage
|
||||
}
|
||||
}
|
||||
|
||||
extension WeakKeyDictionary: Collection {
|
||||
|
||||
public typealias Index = DictionaryIndex<WeakDictionaryKey<Key, Value>, WeakDictionaryReference<Value>>
|
||||
|
||||
public var startIndex: Index {
|
||||
return storage.startIndex
|
||||
}
|
||||
|
||||
public var endIndex: Index {
|
||||
return storage.endIndex
|
||||
}
|
||||
|
||||
public func index(after index: Index) -> Index {
|
||||
return storage.index(after: index)
|
||||
}
|
||||
|
||||
public subscript(position: Index) -> (WeakDictionaryKey<Key, Value>, WeakDictionaryReference<Value>) {
|
||||
return storage[position]
|
||||
}
|
||||
|
||||
public subscript(key: Key) -> Value? {
|
||||
get {
|
||||
return storage[WeakDictionaryKey<Key, Value>(key: key)]
|
||||
}
|
||||
|
||||
set {
|
||||
let retainedValue = valuesRetainedByKey ? newValue : nil
|
||||
let weakKey = WeakDictionaryKey<Key, Value>(key: key, value: retainedValue)
|
||||
storage[weakKey] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public subscript(bounds: Range<Index>) -> WeakKeyDictionary<Key, Value> {
|
||||
let subStorage = storage[bounds.lowerBound ..< bounds.upperBound]
|
||||
var newStorage = WeakDictionary<WeakDictionaryKey<Key, Value>, Value>()
|
||||
|
||||
subStorage.filter { key, value in return key.key != nil && value.value != nil }
|
||||
.forEach { key, value in newStorage[key] = value.value }
|
||||
|
||||
return WeakKeyDictionary<Key, Value>(storage: newStorage)
|
||||
}
|
||||
}
|
||||
|
||||
extension WeakDictionary where Key: AnyObject {
|
||||
public func weakKeyDictionary(valuesRetainedByKey: Bool = false) -> WeakKeyDictionary<Key, Value> {
|
||||
return WeakKeyDictionary<Key, Value>(dictionary: dictionary(), valuesRetainedByKey: valuesRetainedByKey)
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary where Key: AnyObject, Value: AnyObject {
|
||||
public func weakKeyDictionary(valuesRetainedByKey: Bool = false) -> WeakKeyDictionary<Key, Value> {
|
||||
return WeakKeyDictionary<Key, Value>(dictionary: self, valuesRetainedByKey: valuesRetainedByKey)
|
||||
}
|
||||
}
|
36
ElementX/Sources/Modules/Routers/NavigationModule.swift
Normal file
36
ElementX/Sources/Modules/Routers/NavigationModule.swift
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
/// Structure used to pass modules to routers with pop completion blocks.
|
||||
struct NavigationModule {
|
||||
/// Actual presentable of the module
|
||||
let presentable: Presentable
|
||||
|
||||
/// Block to be called when the module is popped
|
||||
let popCompletion: (() -> Void)?
|
||||
}
|
||||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
extension NavigationModule: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
return "NavigationModule: \(presentable), pop completion: \(String(describing: popCompletion))"
|
||||
}
|
||||
|
||||
}
|
405
ElementX/Sources/Modules/Routers/NavigationRouter.swift
Executable file
405
ElementX/Sources/Modules/Routers/NavigationRouter.swift
Executable file
@ -0,0 +1,405 @@
|
||||
/*
|
||||
Copyright 2019 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 UIKit
|
||||
|
||||
/// `NavigationRouter` is a concrete implementation of NavigationRouterType.
|
||||
final class NavigationRouter: NSObject, NavigationRouterType {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var completions: [UIViewController : () -> Void]
|
||||
private let navigationController: UINavigationController
|
||||
|
||||
/// Stores the association between the added Presentable and his view controller.
|
||||
/// They can be the same if the controller is not added via his Coordinator or it is a simple UIViewController.
|
||||
private var storedModules = WeakDictionary<UIViewController, AnyObject>()
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/// Returns the presentables associated to each view controller
|
||||
var modules: [Presentable] {
|
||||
return self.viewControllers.map { (viewController) -> Presentable in
|
||||
return self.module(for: viewController)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the view controllers stack
|
||||
var viewControllers: [UIViewController] {
|
||||
return navigationController.viewControllers
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(navigationController: UINavigationController) {
|
||||
self.navigationController = navigationController
|
||||
self.completions = [:]
|
||||
super.init()
|
||||
self.navigationController.delegate = self
|
||||
|
||||
// Post local notification on NavigationRouter creation
|
||||
let userInfo: [String: Any] = [NavigationRouter.NotificationUserInfoKey.navigationRouter: self,
|
||||
NavigationRouter.NotificationUserInfoKey.navigationController: navigationController]
|
||||
NotificationCenter.default.post(name: NavigationRouter.didCreate, object: self, userInfo: userInfo)
|
||||
}
|
||||
|
||||
deinit {
|
||||
// Post local notification on NavigationRouter deinit
|
||||
let userInfo: [String: Any] = [NavigationRouter.NotificationUserInfoKey.navigationRouter: self,
|
||||
NavigationRouter.NotificationUserInfoKey.navigationController: navigationController]
|
||||
NotificationCenter.default.post(name: NavigationRouter.willDestroy, object: self, userInfo: userInfo)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func present(_ module: Presentable, animated: Bool = true) {
|
||||
MXLog.debug("[NavigationRouter] Present \(module)")
|
||||
navigationController.present(module.toPresentable(), animated: animated, completion: nil)
|
||||
}
|
||||
|
||||
func dismissModule(animated: Bool = true, completion: (() -> Void)? = nil) {
|
||||
MXLog.debug("[NavigationRouter] Dismiss presented module")
|
||||
navigationController.dismiss(animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
func setRootModule(_ module: Presentable, hideNavigationBar: Bool = false, animated: Bool = false, popCompletion: (() -> Void)? = nil) {
|
||||
MXLog.debug("[NavigationRouter] Set root module \(module)")
|
||||
|
||||
let controller = module.toPresentable()
|
||||
|
||||
// Avoid setting a UINavigationController onto stack
|
||||
guard controller is UINavigationController == false else {
|
||||
MXLog.error("Cannot add a UINavigationController to NavigationRouter")
|
||||
return
|
||||
}
|
||||
|
||||
self.addModule(module, for: controller)
|
||||
|
||||
let controllersToPop = self.navigationController.viewControllers.reversed()
|
||||
|
||||
controllersToPop.forEach {
|
||||
self.willPopViewController($0)
|
||||
}
|
||||
|
||||
if let popCompletion = popCompletion {
|
||||
completions[controller] = popCompletion
|
||||
}
|
||||
|
||||
self.willPushViewController(controller)
|
||||
|
||||
navigationController.setViewControllers([controller], animated: animated)
|
||||
navigationController.isNavigationBarHidden = hideNavigationBar
|
||||
|
||||
// Pop old view controllers
|
||||
controllersToPop.forEach {
|
||||
self.didPopViewController($0)
|
||||
}
|
||||
|
||||
// Add again controller to module association, in case same module instance is added back
|
||||
self.addModule(module, for: controller)
|
||||
|
||||
self.didPushViewController(controller)
|
||||
}
|
||||
|
||||
func setModules(_ modules: [NavigationModule], hideNavigationBar: Bool, animated: Bool) {
|
||||
|
||||
MXLog.debug("[NavigationRouter] Set modules \(modules)")
|
||||
|
||||
let controllers = modules.map { (module) -> UIViewController in
|
||||
let controller = module.presentable.toPresentable()
|
||||
self.addModule(module.presentable, for: controller)
|
||||
return controller
|
||||
}
|
||||
|
||||
let controllersToPop = self.navigationController.viewControllers.reversed()
|
||||
|
||||
controllersToPop.forEach {
|
||||
self.willPopViewController($0)
|
||||
}
|
||||
|
||||
controllers.forEach {
|
||||
self.willPushViewController($0)
|
||||
}
|
||||
|
||||
// Set new view controllers
|
||||
navigationController.setViewControllers(controllers, animated: animated)
|
||||
navigationController.isNavigationBarHidden = hideNavigationBar
|
||||
|
||||
// Pop old view controllers
|
||||
controllersToPop.forEach {
|
||||
self.didPopViewController($0)
|
||||
}
|
||||
|
||||
// Add again controller to module association, in case same modules instance are added back
|
||||
modules.forEach { (module) in
|
||||
self.addModule(module.presentable, for: module.presentable.toPresentable())
|
||||
}
|
||||
|
||||
controllers.forEach {
|
||||
self.didPushViewController($0)
|
||||
}
|
||||
}
|
||||
|
||||
func popToRootModule(animated: Bool) {
|
||||
MXLog.debug("[NavigationRouter] Pop to root module")
|
||||
|
||||
let controllers = self.navigationController.viewControllers
|
||||
|
||||
if controllers.count > 1 {
|
||||
let controllersToPop = controllers[1..<controllers.count]
|
||||
|
||||
controllersToPop.reversed().forEach {
|
||||
self.willPopViewController($0)
|
||||
}
|
||||
}
|
||||
|
||||
if let controllers = navigationController.popToRootViewController(animated: animated) {
|
||||
controllers.reversed().forEach {
|
||||
self.didPopViewController($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func popToModule(_ module: Presentable, animated: Bool) {
|
||||
MXLog.debug("[NavigationRouter] Pop to module \(module)")
|
||||
|
||||
let controller = module.toPresentable()
|
||||
let controllersBeforePop = self.navigationController.viewControllers
|
||||
|
||||
if let controllerIndex = controllersBeforePop.firstIndex(of: controller) {
|
||||
let controllersToPop = controllersBeforePop[controllerIndex..<controllersBeforePop.count]
|
||||
|
||||
controllersToPop.reversed().forEach {
|
||||
self.willPopViewController($0)
|
||||
}
|
||||
}
|
||||
|
||||
if let controllers = navigationController.popToViewController(controller, animated: animated) {
|
||||
controllers.reversed().forEach {
|
||||
self.didPopViewController($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func push(_ module: Presentable, animated: Bool = true, popCompletion: (() -> Void)? = nil) {
|
||||
MXLog.debug("[NavigationRouter] Push module \(module)")
|
||||
|
||||
let controller = module.toPresentable()
|
||||
|
||||
// Avoid pushing UINavigationController onto stack
|
||||
guard controller is UINavigationController == false else {
|
||||
MXLog.error("Cannot push a UINavigationController to NavigationRouter")
|
||||
return
|
||||
}
|
||||
|
||||
self.addModule(module, for: controller)
|
||||
|
||||
if let completion = popCompletion {
|
||||
completions[controller] = completion
|
||||
}
|
||||
|
||||
self.willPushViewController(controller)
|
||||
|
||||
navigationController.pushViewController(controller, animated: animated)
|
||||
|
||||
self.didPushViewController(controller)
|
||||
}
|
||||
|
||||
func push(_ modules: [NavigationModule], animated: Bool) {
|
||||
MXLog.debug("[NavigationRouter] Push modules \(modules)")
|
||||
|
||||
// Avoid pushing any UINavigationController onto stack
|
||||
guard modules.first(where: { $0.presentable.toPresentable() is UINavigationController }) == nil else {
|
||||
MXLog.error("Cannot push a UINavigationController to NavigationRouter")
|
||||
return
|
||||
}
|
||||
|
||||
for module in modules {
|
||||
let controller = module.presentable.toPresentable()
|
||||
self.addModule(module.presentable, for: controller)
|
||||
|
||||
if let completion = module.popCompletion {
|
||||
completions[controller] = completion
|
||||
}
|
||||
|
||||
self.willPushViewController(controller)
|
||||
}
|
||||
|
||||
var viewControllers = navigationController.viewControllers
|
||||
viewControllers.append(contentsOf: modules.map({ $0.presentable.toPresentable() }))
|
||||
navigationController.setViewControllers(viewControllers, animated: animated)
|
||||
|
||||
for module in modules {
|
||||
let controller = module.presentable.toPresentable()
|
||||
self.didPushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
||||
func popModule(animated: Bool = true) {
|
||||
MXLog.debug("[NavigationRouter] Pop module")
|
||||
|
||||
if let lastController = navigationController.viewControllers.last {
|
||||
self.willPopViewController(lastController)
|
||||
}
|
||||
|
||||
if let controller = navigationController.popViewController(animated: animated) {
|
||||
self.didPopViewController(controller)
|
||||
}
|
||||
}
|
||||
|
||||
func popAllModules(animated: Bool) {
|
||||
MXLog.debug("[NavigationRouter] Pop all modules")
|
||||
|
||||
let controllersToPop = self.navigationController.viewControllers.reversed()
|
||||
|
||||
controllersToPop.forEach {
|
||||
self.willPopViewController($0)
|
||||
}
|
||||
|
||||
navigationController.setViewControllers([], animated: animated)
|
||||
|
||||
controllersToPop.forEach {
|
||||
self.didPopViewController($0)
|
||||
}
|
||||
}
|
||||
|
||||
func contains(_ module: Presentable) -> Bool {
|
||||
|
||||
let controller = module.toPresentable()
|
||||
return self.navigationController.viewControllers.contains(controller)
|
||||
}
|
||||
|
||||
// MARK: Presentable
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return navigationController
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func module(for viewController: UIViewController) -> Presentable {
|
||||
|
||||
guard let module = self.storedModules[viewController] as? Presentable else {
|
||||
return viewController
|
||||
}
|
||||
return module
|
||||
}
|
||||
|
||||
private func addModule(_ module: Presentable, for viewController: UIViewController) {
|
||||
self.storedModules[viewController] = module as AnyObject
|
||||
}
|
||||
|
||||
private func removeModule(for viewController: UIViewController) {
|
||||
self.storedModules[viewController] = nil
|
||||
}
|
||||
|
||||
private func runCompletion(for controller: UIViewController) {
|
||||
guard let completion = completions[controller] else {
|
||||
return
|
||||
}
|
||||
completion()
|
||||
completions.removeValue(forKey: controller)
|
||||
}
|
||||
|
||||
private func willPushViewController(_ viewController: UIViewController) {
|
||||
self.postNotification(withName: NavigationRouter.willPushModule, for: viewController)
|
||||
}
|
||||
|
||||
private func didPushViewController(_ viewController: UIViewController) {
|
||||
self.postNotification(withName: NavigationRouter.didPushModule, for: viewController)
|
||||
}
|
||||
|
||||
private func willPopViewController(_ viewController: UIViewController) {
|
||||
self.postNotification(withName: NavigationRouter.willPopModule, for: viewController)
|
||||
}
|
||||
|
||||
private func didPopViewController(_ viewController: UIViewController) {
|
||||
self.postNotification(withName: NavigationRouter.didPopModule, for: viewController)
|
||||
|
||||
// Call completion closure associated to the view controller
|
||||
// So associated coordinator can be deallocated
|
||||
runCompletion(for: viewController)
|
||||
|
||||
self.removeModule(for: viewController)
|
||||
}
|
||||
|
||||
private func postNotification(withName name: Notification.Name, for viewController: UIViewController) {
|
||||
|
||||
let module = self.module(for: viewController)
|
||||
|
||||
let userInfo: [String: Any] = [
|
||||
NotificationUserInfoKey.navigationRouter: self,
|
||||
NotificationUserInfoKey.module: module,
|
||||
NotificationUserInfoKey.viewController: viewController
|
||||
]
|
||||
NotificationCenter.default.post(name: name, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UINavigationControllerDelegate
|
||||
extension NavigationRouter: UINavigationControllerDelegate {
|
||||
|
||||
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
|
||||
|
||||
// TODO: Try to post `NavigationRouter.willPopModule` notification here
|
||||
}
|
||||
|
||||
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
|
||||
|
||||
// Ensure the view controller is popping
|
||||
guard let poppedViewController = navigationController.transitionCoordinator?.viewController(forKey: .from),
|
||||
!navigationController.viewControllers.contains(poppedViewController) else {
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.debug("[NavigationRouter] Popped module: \(poppedViewController)")
|
||||
|
||||
self.didPopViewController(poppedViewController)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NavigationRouter notification constants
|
||||
extension NavigationRouter {
|
||||
|
||||
// MARK: Notification names
|
||||
|
||||
public static let willPushModule = Notification.Name("NavigationRouterWillPushModule")
|
||||
public static let didPushModule = Notification.Name("NavigationRouterDidPushModule")
|
||||
public static let willPopModule = Notification.Name("NavigationRouterWillPopModule")
|
||||
public static let didPopModule = Notification.Name("NavigationRouterDidPopModule")
|
||||
|
||||
public static let didCreate = Notification.Name("NavigationRouterDidCreate")
|
||||
public static let willDestroy = Notification.Name("NavigationRouterWillDestroy")
|
||||
|
||||
// MARK: Notification keys
|
||||
|
||||
public struct NotificationUserInfoKey {
|
||||
|
||||
/// The associated view controller (UIViewController).
|
||||
static let viewController = "viewController"
|
||||
|
||||
/// The associated module (Presentable), can the view controller itself or is Coordinator
|
||||
static let module = "module"
|
||||
|
||||
/// The navigation router that send the notification (NavigationRouterType)
|
||||
static let navigationRouter = "navigationRouter"
|
||||
|
||||
/// The navigation controller (UINavigationController) associated to the navigation router
|
||||
static let navigationController = "navigationController"
|
||||
}
|
||||
}
|
96
ElementX/Sources/Modules/Routers/NavigationRouterStore.swift
Normal file
96
ElementX/Sources/Modules/Routers/NavigationRouterStore.swift
Normal file
@ -0,0 +1,96 @@
|
||||
//
|
||||
// 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 UIKit
|
||||
|
||||
/// `NavigationRouterStore` enables to get a NavigationRouter from a UINavigationController instance.
|
||||
class NavigationRouterStore: NavigationRouterStoreProtocol {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
static let shared = NavigationRouterStore()
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// FIXME: WeakDictionary does not work with protocol
|
||||
// Find a way to use NavigationRouterType as value
|
||||
private var navigationRouters = WeakDictionary<UINavigationController, NavigationRouter>()
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
/// As we are ensuring that there is only one navigation controller per NavigationRouter, the class here should be used as a singleton.
|
||||
private init() {
|
||||
self.registerNavigationRouterNotifications()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func navigationRouter(for navigationController: UINavigationController) -> NavigationRouterType {
|
||||
|
||||
if let existingNavigationRouter = self.findNavigationRouter(for: navigationController) {
|
||||
return existingNavigationRouter
|
||||
}
|
||||
|
||||
let navigationRouter = NavigationRouter(navigationController: UINavigationController())
|
||||
return navigationRouter
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func findNavigationRouter(for navigationController: UINavigationController) -> NavigationRouterType? {
|
||||
return self.navigationRouters[navigationController]
|
||||
}
|
||||
|
||||
private func removeNavigationRouter(for navigationController: UINavigationController) {
|
||||
self.navigationRouters[navigationController] = nil
|
||||
}
|
||||
|
||||
private func registerNavigationRouterNotifications() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(navigationRouterDidCreate(_:)), name: NavigationRouter.didCreate, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(navigationRouterWillDestroy(_:)), name: NavigationRouter.willDestroy, object: nil)
|
||||
}
|
||||
|
||||
@objc private func navigationRouterDidCreate(_ notification: Notification) {
|
||||
|
||||
guard let userInfo = notification.userInfo,
|
||||
let navigationRouter = userInfo[NavigationRouter.NotificationUserInfoKey.navigationRouter] as? NavigationRouterType,
|
||||
let navigationController = userInfo[NavigationRouter.NotificationUserInfoKey.navigationController] as? UINavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
if let existingNavigationRouter = self.findNavigationRouter(for: navigationController) {
|
||||
fatalError("\(existingNavigationRouter) is already tied to the same navigation controller as \(navigationRouter). We should have only one NavigationRouter per navigation controller")
|
||||
} else {
|
||||
// FIXME: WeakDictionary does not work with protocol
|
||||
// Find a way to avoid this cast
|
||||
self.navigationRouters[navigationController] = navigationRouter as? NavigationRouter
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func navigationRouterWillDestroy(_ notification: Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let navigationRouter = userInfo[NavigationRouter.NotificationUserInfoKey.navigationRouter] as? NavigationRouterType,
|
||||
let navigationController = userInfo[NavigationRouter.NotificationUserInfoKey.navigationController] as? UINavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
if let existingNavigationRouter = self.findNavigationRouter(for: navigationController), existingNavigationRouter !== navigationRouter {
|
||||
fatalError("\(existingNavigationRouter) is already tied to the same navigation controller as \(navigationRouter). We should have only one NavigationRouter per navigation controller")
|
||||
}
|
||||
|
||||
self.removeNavigationRouter(for: navigationController)
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
//
|
||||
// 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 UIKit
|
||||
|
||||
/// `NavigationRouterStoreProtocol` describes a structure that enables to get a NavigationRouter from a UINavigationController instance.
|
||||
protocol NavigationRouterStoreProtocol {
|
||||
|
||||
/// Gets the existing navigation router for the supplied controller, creating a new one if it doesn't yet exist.
|
||||
/// Note: The store only holds a weak reference to the returned router. It is the caller's responsibility to retain it.
|
||||
func navigationRouter(for navigationController: UINavigationController) -> NavigationRouterType
|
||||
}
|
135
ElementX/Sources/Modules/Routers/NavigationRouterType.swift
Executable file
135
ElementX/Sources/Modules/Routers/NavigationRouterType.swift
Executable file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
Copyright 2019 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 UIKit
|
||||
|
||||
/// Protocol describing a router that wraps a UINavigationController and add convenient completion handlers. Completions are called when a Presentable is removed.
|
||||
/// Routers are used to be passed between coordinators. They handles only `physical` navigation.
|
||||
protocol NavigationRouterType: AnyObject, Presentable {
|
||||
|
||||
/// Present modally a view controller on the navigation controller
|
||||
///
|
||||
/// - Parameter module: The Presentable to present.
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
func present(_ module: Presentable, animated: Bool)
|
||||
|
||||
/// Dismiss presented view controller from navigation controller
|
||||
///
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
/// - Parameter completion: Animation completion (not the pop completion).
|
||||
func dismissModule(animated: Bool, completion: (() -> Void)?)
|
||||
|
||||
/// Set root view controller of navigation controller
|
||||
///
|
||||
/// - Parameter module: The Presentable to set as root.
|
||||
/// - Parameter hideNavigationBar: Specify true to hide the UINavigationBar.
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
/// - Parameter popCompletion: Completion called when `module` is removed from the navigation stack.
|
||||
func setRootModule(_ module: Presentable, hideNavigationBar: Bool, animated: Bool, popCompletion: (() -> Void)?)
|
||||
|
||||
/// Set view controllers stack of navigation controller
|
||||
/// - Parameters:
|
||||
/// - modules: The modules stack to set.
|
||||
/// - hideNavigationBar: Specify true to hide the UINavigationBar.
|
||||
/// - animated: Specify true to animate the transition.
|
||||
func setModules(_ modules: [NavigationModule], hideNavigationBar: Bool, animated: Bool)
|
||||
|
||||
/// Pop to root view controller of navigation controller and remove all others
|
||||
///
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
func popToRootModule(animated: Bool)
|
||||
|
||||
/// Pops view controllers until the specified view controller is at the top of the navigation stack
|
||||
///
|
||||
/// - Parameter module: The Presentable that should to be at the top of the stack.
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
func popToModule(_ module: Presentable, animated: Bool)
|
||||
|
||||
/// Push a view controller on navigation controller stack
|
||||
///
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
/// - Parameter popCompletion: Completion called when `module` is removed from the navigation stack.
|
||||
func push(_ module: Presentable, animated: Bool, popCompletion: (() -> Void)?)
|
||||
|
||||
/// Push some view controllers on navigation controller stack
|
||||
///
|
||||
/// - Parameter modules: Modules to push
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
func push(_ modules: [NavigationModule], animated: Bool)
|
||||
|
||||
/// Pop last view controller from navigation controller stack
|
||||
///
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
func popModule(animated: Bool)
|
||||
|
||||
/// Pops all view controllers
|
||||
///
|
||||
/// - Parameter animated: Specify true to animate the transition.
|
||||
func popAllModules(animated: Bool)
|
||||
|
||||
/// Returns the modules that are currently in the navigation stack
|
||||
var modules: [Presentable] { get }
|
||||
|
||||
/// Check if the navigation controller contains the given presentable.
|
||||
/// - Parameter module: The presentable for which to check the existence.
|
||||
func contains(_ module: Presentable) -> Bool
|
||||
}
|
||||
|
||||
// `NavigationRouterType` default implementation
|
||||
extension NavigationRouterType {
|
||||
|
||||
func setRootModule(_ module: Presentable) {
|
||||
setRootModule(module, hideNavigationBar: false, animated: false, popCompletion: nil)
|
||||
}
|
||||
|
||||
func setRootModule(_ module: Presentable, popCompletion: (() -> Void)?) {
|
||||
setRootModule(module, hideNavigationBar: false, animated: false, popCompletion: popCompletion)
|
||||
}
|
||||
|
||||
func setModules(_ modules: [NavigationModule], animated: Bool) {
|
||||
setModules(modules, hideNavigationBar: false, animated: animated)
|
||||
}
|
||||
|
||||
func setModules(_ modules: [Presentable], animated: Bool) {
|
||||
setModules(modules, hideNavigationBar: false, animated: animated)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Presentable <--> NavigationModule Transitive Methods
|
||||
|
||||
extension NavigationRouterType {
|
||||
|
||||
func setRootModule(_ module: NavigationModule) {
|
||||
setRootModule(module.presentable, popCompletion: module.popCompletion)
|
||||
}
|
||||
|
||||
func push(_ module: NavigationModule, animated: Bool) {
|
||||
push(module.presentable, animated: animated, popCompletion: module.popCompletion)
|
||||
}
|
||||
|
||||
func setModules(_ modules: [Presentable], hideNavigationBar: Bool, animated: Bool) {
|
||||
setModules(modules.map { $0.toModule() },
|
||||
hideNavigationBar: hideNavigationBar,
|
||||
animated: animated)
|
||||
}
|
||||
|
||||
func push(_ modules: [Presentable], animated: Bool) {
|
||||
push(modules.map { $0.toModule() },
|
||||
animated: animated)
|
||||
}
|
||||
|
||||
}
|
38
ElementX/Sources/Modules/Routers/Presentable.swift
Executable file
38
ElementX/Sources/Modules/Routers/Presentable.swift
Executable file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2019 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 UIKit
|
||||
|
||||
/// Protocol used to pass UIViewControllers to routers
|
||||
protocol Presentable {
|
||||
func toPresentable() -> UIViewController
|
||||
}
|
||||
|
||||
extension UIViewController: Presentable {
|
||||
public func toPresentable() -> UIViewController {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension Presentable {
|
||||
|
||||
/// Returns a new module from the presentable without a pop completion block
|
||||
/// - Returns: Module
|
||||
func toModule() -> NavigationModule {
|
||||
return NavigationModule(presentable: self, popCompletion: nil)
|
||||
}
|
||||
|
||||
}
|
87
ElementX/Sources/Modules/Routers/RootRouter.swift
Executable file
87
ElementX/Sources/Modules/Routers/RootRouter.swift
Executable file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
Copyright 2020 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 UIKit
|
||||
|
||||
/// `RootRouter` is a concrete implementation of RootRouterType.
|
||||
final class RootRouter: RootRouterType {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
// `rootViewController` animation constants
|
||||
private enum RootViewControllerUpdateAnimation {
|
||||
static let duration: TimeInterval = 0.3
|
||||
static let options: UIView.AnimationOptions = .transitionCrossDissolve
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private var presentedModule: Presentable?
|
||||
|
||||
let window: UIWindow
|
||||
|
||||
/// The root view controller currently presented
|
||||
var rootViewController: UIViewController? {
|
||||
return self.window.rootViewController
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(window: UIWindow) {
|
||||
self.window = window
|
||||
}
|
||||
|
||||
// MARK: - Public methods
|
||||
|
||||
func setRootModule(_ module: Presentable) {
|
||||
self.updateRootViewController(rootViewController: module.toPresentable(), animated: false, completion: nil)
|
||||
self.window.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
func dismissRootModule(animated: Bool, completion: (() -> Void)?) {
|
||||
self.updateRootViewController(rootViewController: nil, animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
func presentModule(_ module: Presentable, animated: Bool, completion: (() -> Void)?) {
|
||||
let viewControllerPresenter = self.rootViewController?.presentedViewController ?? self.rootViewController
|
||||
|
||||
viewControllerPresenter?.present(module.toPresentable(), animated: animated, completion: completion)
|
||||
self.presentedModule = module
|
||||
}
|
||||
|
||||
func dismissModule(animated: Bool, completion: (() -> Void)?) {
|
||||
self.presentedModule?.toPresentable().dismiss(animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - Private methods
|
||||
|
||||
private func updateRootViewController(rootViewController: UIViewController?, animated: Bool, completion: (() -> Void)?) {
|
||||
|
||||
if animated {
|
||||
UIView.transition(with: window, duration: RootViewControllerUpdateAnimation.duration, options: RootViewControllerUpdateAnimation.options, animations: {
|
||||
let oldState: Bool = UIView.areAnimationsEnabled
|
||||
UIView.setAnimationsEnabled(false)
|
||||
self.window.rootViewController = rootViewController
|
||||
UIView.setAnimationsEnabled(oldState)
|
||||
}, completion: { (_: Bool) -> Void in
|
||||
completion?()
|
||||
})
|
||||
} else {
|
||||
self.window.rootViewController = rootViewController
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
49
ElementX/Sources/Modules/Routers/RootRouterType.swift
Executable file
49
ElementX/Sources/Modules/Routers/RootRouterType.swift
Executable file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2020 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 UIKit
|
||||
|
||||
/// Protocol describing a router that wraps the root navigation of the application.
|
||||
/// Routers are used to be passed between coordinators. They handles only `physical` navigation.
|
||||
protocol RootRouterType: AnyObject {
|
||||
|
||||
/// Update the root view controller
|
||||
///
|
||||
/// - Parameter module: The new root view controller to set
|
||||
func setRootModule(_ module: Presentable)
|
||||
|
||||
/// Dismiss the root view controller
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - animated: Specify true to animate the transition.
|
||||
/// - completion: The closure executed after the view controller is dismissed.
|
||||
func dismissRootModule(animated: Bool, completion: (() -> Void)?)
|
||||
|
||||
/// Present modally a view controller on the root view controller
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - module: Specify true to animate the transition.
|
||||
/// - animated: Specify true to animate the transition.
|
||||
/// - completion: Animation completion.
|
||||
func presentModule(_ module: Presentable, animated: Bool, completion: (() -> Void)?)
|
||||
|
||||
/// Dismiss modally presented view controller from root view controller
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - animated: Specify true to animate the transition.
|
||||
/// - completion: Animation completion.
|
||||
func dismissModule(animated: Bool, completion: (() -> Void)?)
|
||||
}
|
12
ElementX/Sources/Modules/Splash/SplashViewController.swift
Normal file
12
ElementX/Sources/Modules/Splash/SplashViewController.swift
Normal file
@ -0,0 +1,12 @@
|
||||
//
|
||||
// SplashViewController.swift
|
||||
// ElementX
|
||||
//
|
||||
// Created by Stefan Ceriu on 14.02.2022.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SplashViewController: UIViewController {
|
||||
|
||||
}
|
45
ElementX/Sources/Modules/Splash/SplashViewController.xib
Normal file
45
ElementX/Sources/Modules/Splash/SplashViewController.xib
Normal file
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SplashViewController" customModule="ElementX" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="app-logo" translatesAutoresizingMaskIntoConstraints="NO" id="ue7-fB-5XS">
|
||||
<rect key="frame" x="87" y="328" width="240" height="240"/>
|
||||
<color key="tintColor" red="0.050980392156862744" green="0.74117647058823533" blue="0.54509803921568623" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ue7-fB-5XS" secondAttribute="bottom" constant="16" id="Cip-Sc-gaF"/>
|
||||
<constraint firstItem="ue7-fB-5XS" firstAttribute="top" relation="greaterThanOrEqual" secondItem="fnl-2z-Ty3" secondAttribute="top" constant="16" id="Clt-cS-YAr"/>
|
||||
<constraint firstItem="ue7-fB-5XS" firstAttribute="centerY" secondItem="i5M-Pr-FkT" secondAttribute="centerY" id="N3w-Jf-MRA"/>
|
||||
<constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ue7-fB-5XS" secondAttribute="trailing" constant="16" id="WfN-3K-kpr"/>
|
||||
<constraint firstItem="ue7-fB-5XS" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="ujr-SL-AyX"/>
|
||||
<constraint firstItem="ue7-fB-5XS" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="16" id="yNl-Wu-AES"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="137.68115942028987" y="153.34821428571428"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="app-logo" width="240" height="240"/>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
@ -0,0 +1,64 @@
|
||||
//
|
||||
// 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 TemplateSimpleScreenCoordinatorParameters {
|
||||
let promptType: TemplateSimpleScreenPromptType
|
||||
}
|
||||
|
||||
final class TemplateSimpleScreenCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: TemplateSimpleScreenCoordinatorParameters
|
||||
private let templateSimpleScreenHostingController: UIViewController
|
||||
private var templateSimpleScreenViewModel: TemplateSimpleScreenViewModelProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: ((TemplateSimpleScreenViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
init(parameters: TemplateSimpleScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let viewModel = TemplateSimpleScreenViewModel(promptType: parameters.promptType)
|
||||
let view = TemplateSimpleScreen(viewModel: viewModel.context)
|
||||
templateSimpleScreenViewModel = viewModel
|
||||
templateSimpleScreenHostingController = VectorHostingController(rootView: view)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
MXLog.debug("[TemplateSimpleScreenCoordinator] did start.")
|
||||
templateSimpleScreenViewModel.completion = { [weak self] result in
|
||||
MXLog.debug("[TemplateSimpleScreenCoordinator] TemplateSimpleScreenViewModel did complete with result: \(result).")
|
||||
guard let self = self else { return }
|
||||
self.completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.templateSimpleScreenHostingController
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
//
|
||||
// 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
|
||||
import SwiftUI
|
||||
|
||||
/// Using an enum for the screen allows you define the different state cases with
|
||||
/// the relevant associated data for each case.
|
||||
@available(iOS 14.0, *)
|
||||
enum MockTemplateSimpleScreenScreenState: MockScreenState, CaseIterable {
|
||||
// A case for each state you want to represent
|
||||
// with specific, minimal associated data that will allow you
|
||||
// mock that screen.
|
||||
case promptType(TemplateSimpleScreenPromptType)
|
||||
|
||||
/// The associated screen
|
||||
var screenType: Any.Type {
|
||||
TemplateSimpleScreen.self
|
||||
}
|
||||
|
||||
/// A list of screen state definitions
|
||||
static var allCases: [MockTemplateSimpleScreenScreenState] {
|
||||
// Each of the presence statuses
|
||||
TemplateSimpleScreenPromptType.allCases.map(MockTemplateSimpleScreenScreenState.promptType)
|
||||
}
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
var screenView: ([Any], AnyView) {
|
||||
let promptType: TemplateSimpleScreenPromptType
|
||||
switch self {
|
||||
case .promptType(let type):
|
||||
promptType = type
|
||||
}
|
||||
let viewModel = TemplateSimpleScreenViewModel(promptType: promptType)
|
||||
|
||||
// can simulate service and viewModel actions here if needs be.
|
||||
|
||||
return (
|
||||
[promptType, viewModel],
|
||||
AnyView(TemplateSimpleScreen(viewModel: viewModel.context)
|
||||
.addDependency(MockAvatarService.example))
|
||||
)
|
||||
}
|
||||
}
|
@ -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 Foundation
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
enum TemplateSimpleScreenPromptType {
|
||||
case regular
|
||||
case upgrade
|
||||
}
|
||||
|
||||
extension TemplateSimpleScreenPromptType: Identifiable, CaseIterable {
|
||||
var id: Self { self }
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .regular:
|
||||
return VectorL10n.roomCreationMakePublicPromptTitle
|
||||
case .upgrade:
|
||||
return VectorL10n.roomDetailsHistorySectionPromptTitle
|
||||
}
|
||||
}
|
||||
|
||||
var image: ImageAsset {
|
||||
switch self {
|
||||
case .regular:
|
||||
return Asset.Images.appSymbol
|
||||
case .upgrade:
|
||||
return Asset.Images.keyVerificationSuccessShield
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: View model
|
||||
|
||||
enum TemplateSimpleScreenViewModelResult {
|
||||
case accept
|
||||
case cancel
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct TemplateSimpleScreenViewState: BindableState {
|
||||
var promptType: TemplateSimpleScreenPromptType
|
||||
var count: Int
|
||||
}
|
||||
|
||||
enum TemplateSimpleScreenViewAction {
|
||||
case incrementCount
|
||||
case decrementCount
|
||||
case accept
|
||||
case cancel
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
//
|
||||
// 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 TemplateSimpleScreenViewModelType = StateStoreViewModel<TemplateSimpleScreenViewState,
|
||||
Never,
|
||||
TemplateSimpleScreenViewAction>
|
||||
@available(iOS 14, *)
|
||||
class TemplateSimpleScreenViewModel: TemplateSimpleScreenViewModelType, TemplateSimpleScreenViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: ((TemplateSimpleScreenViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(promptType: TemplateSimpleScreenPromptType, initialCount: Int = 0) {
|
||||
super.init(initialViewState: TemplateSimpleScreenViewState(promptType: promptType, count: 0))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: TemplateSimpleScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .accept:
|
||||
completion?(.accept)
|
||||
case .cancel:
|
||||
completion?(.cancel)
|
||||
case .incrementCount:
|
||||
state.count += 1
|
||||
case .decrementCount:
|
||||
state.count -= 1
|
||||
}
|
||||
}
|
||||
}
|
@ -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 TemplateSimpleScreenViewModelProtocol {
|
||||
|
||||
var completion: ((TemplateSimpleScreenViewModelResult) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
var context: TemplateSimpleScreenViewModelType.Context { get }
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
//
|
||||
// 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
|
||||
import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class TemplateSimpleScreenUITests: MockScreenTest {
|
||||
|
||||
override class var screenType: MockScreenState.Type {
|
||||
return MockTemplateSimpleScreenScreenState.self
|
||||
}
|
||||
|
||||
override class func createTest() -> MockScreenTest {
|
||||
return TemplateSimpleScreenUITests(selector: #selector(verifyTemplateSimpleScreenScreen))
|
||||
}
|
||||
|
||||
func verifyTemplateSimpleScreenScreen() throws {
|
||||
guard let screenState = screenState as? MockTemplateSimpleScreenScreenState else { fatalError("no screen") }
|
||||
switch screenState {
|
||||
case .promptType(let promptType):
|
||||
verifyTemplateSimpleScreenPromptType(promptType: promptType)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyTemplateSimpleScreenPromptType(promptType: TemplateSimpleScreenPromptType) {
|
||||
let title = app.staticTexts["title"]
|
||||
XCTAssert(title.exists)
|
||||
XCTAssertEqual(title.label, promptType.title)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@testable import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class TemplateSimpleScreenViewModelTests: XCTestCase {
|
||||
private enum Constants {
|
||||
static let counterInitialValue = 0
|
||||
}
|
||||
|
||||
var viewModel: TemplateSimpleScreenViewModelProtocol!
|
||||
var context: TemplateSimpleScreenViewModelType.Context!
|
||||
|
||||
override func setUpWithError() throws {
|
||||
viewModel = TemplateSimpleScreenViewModel(promptType: .regular, initialCount: Constants.counterInitialValue)
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
XCTAssertEqual(context.viewState.count, Constants.counterInitialValue)
|
||||
}
|
||||
|
||||
func testCounter() throws {
|
||||
context.send(viewAction: .incrementCount)
|
||||
XCTAssertEqual(context.viewState.count, 1)
|
||||
|
||||
context.send(viewAction: .incrementCount)
|
||||
XCTAssertEqual(context.viewState.count, 2)
|
||||
|
||||
context.send(viewAction: .decrementCount)
|
||||
XCTAssertEqual(context.viewState.count, 1)
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
//
|
||||
// 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.0, *)
|
||||
struct TemplateSimpleScreen: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
private var horizontalPadding: CGFloat {
|
||||
horizontalSizeClass == .regular ? 50 : 16
|
||||
}
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@ObservedObject var viewModel: TemplateSimpleScreenViewModel.Context
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
ScrollView(showsIndicators: false) {
|
||||
mainContent
|
||||
.padding(.top, 50)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
}
|
||||
|
||||
buttons
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
|
||||
}
|
||||
}
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.accentColor(theme.colors.accent)
|
||||
}
|
||||
|
||||
/// The main content of the view to be shown in a scroll view.
|
||||
var mainContent: some View {
|
||||
VStack(spacing: 36) {
|
||||
Text(viewModel.viewState.promptType.title)
|
||||
.font(theme.fonts.title1B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
.accessibilityIdentifier("title")
|
||||
|
||||
Image(viewModel.viewState.promptType.image.name)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 100)
|
||||
.foregroundColor(theme.colors.accent)
|
||||
|
||||
HStack {
|
||||
Text("Counter: \(viewModel.viewState.count)")
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
|
||||
Button("-") {
|
||||
viewModel.send(viewAction: .decrementCount)
|
||||
}
|
||||
|
||||
Button("+") {
|
||||
viewModel.send(viewAction: .incrementCount)
|
||||
}
|
||||
}
|
||||
.font(theme.fonts.title3)
|
||||
}
|
||||
}
|
||||
|
||||
/// The action buttons shown at the bottom of the view.
|
||||
var buttons: some View {
|
||||
VStack {
|
||||
Button { viewModel.send(viewAction: .accept) } label: {
|
||||
Text("Accept")
|
||||
.font(theme.fonts.bodySB)
|
||||
}
|
||||
.buttonStyle(PrimaryActionButtonStyle())
|
||||
|
||||
Button { viewModel.send(viewAction: .cancel) } label: {
|
||||
Text("Cancel")
|
||||
.font(theme.fonts.body)
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct TemplateSimpleScreen_Previews: PreviewProvider {
|
||||
static let stateRenderer = MockTemplateSimpleScreenScreenState.stateRenderer
|
||||
static var previews: some View {
|
||||
stateRenderer.screenGroup()
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
6
ElementX/Supporting Files/Assets.xcassets/Contents.json
Normal file
6
ElementX/Supporting Files/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
26
ElementX/Supporting Files/Assets.xcassets/app-logo.imageset/Contents.json
vendored
Normal file
26
ElementX/Supporting Files/Assets.xcassets/app-logo.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "launch_screen_logo.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "launch_screen_logo@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "launch_screen_logo@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
BIN
ElementX/Supporting Files/Assets.xcassets/app-logo.imageset/launch_screen_logo.png
vendored
Normal file
BIN
ElementX/Supporting Files/Assets.xcassets/app-logo.imageset/launch_screen_logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
BIN
ElementX/Supporting Files/Assets.xcassets/app-logo.imageset/launch_screen_logo@2x.png
vendored
Normal file
BIN
ElementX/Supporting Files/Assets.xcassets/app-logo.imageset/launch_screen_logo@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
ElementX/Supporting Files/Assets.xcassets/app-logo.imageset/launch_screen_logo@3x.png
vendored
Normal file
BIN
ElementX/Supporting Files/Assets.xcassets/app-logo.imageset/launch_screen_logo@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
32
ElementX/Supporting Files/Base.lproj/LaunchScreen.storyboard
Normal file
32
ElementX/Supporting Files/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
10
ElementX/Supporting Files/ElementX.entitlements
Normal file
10
ElementX/Supporting Files/ElementX.entitlements
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
36
ElementXTests/ElementXTests.swift
Normal file
36
ElementXTests/ElementXTests.swift
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// ElementXTests.swift
|
||||
// ElementXTests
|
||||
//
|
||||
// Created by Stefan Ceriu on 11.02.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import ElementX
|
||||
|
||||
class ElementXTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
// Any test you write for XCTest can be annotated as throws and async.
|
||||
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
||||
}
|
||||
|
||||
func testPerformanceExample() throws {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
42
ElementXUITests/ElementXUITests.swift
Normal file
42
ElementXUITests/ElementXUITests.swift
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// ElementXUITests.swift
|
||||
// ElementXUITests
|
||||
//
|
||||
// Created by Stefan Ceriu on 11.02.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
class ElementXUITests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
// In UI tests it is usually best to stop immediately when a failure occurs.
|
||||
continueAfterFailure = false
|
||||
|
||||
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// UI tests must launch the application that they test.
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Use recording to get started writing UI tests.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
func testLaunchPerformance() throws {
|
||||
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
|
||||
// This measures how long it takes to launch your application.
|
||||
measure(metrics: [XCTApplicationLaunchMetric()]) {
|
||||
XCUIApplication().launch()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
32
ElementXUITests/ElementXUITestsLaunchTests.swift
Normal file
32
ElementXUITests/ElementXUITestsLaunchTests.swift
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// ElementXUITestsLaunchTests.swift
|
||||
// ElementXUITests
|
||||
//
|
||||
// Created by Stefan Ceriu on 11.02.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
class ElementXUITestsLaunchTests: XCTestCase {
|
||||
|
||||
override class var runsForEachTargetApplicationUIConfiguration: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
continueAfterFailure = false
|
||||
}
|
||||
|
||||
func testLaunch() throws {
|
||||
let app = XCUIApplication()
|
||||
app.launch()
|
||||
|
||||
// Insert steps here to perform after app launch but before taking a screenshot,
|
||||
// such as logging into a test account or navigating somewhere in the app
|
||||
|
||||
let attachment = XCTAttachment(screenshot: app.screenshot())
|
||||
attachment.name = "Launch Screen"
|
||||
attachment.lifetime = .keepAlways
|
||||
add(attachment)
|
||||
}
|
||||
}
|
31
Scripts/createSwiftUISimpleScreen.sh
Executable file
31
Scripts/createSwiftUISimpleScreen.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! $# -eq 2 ]; then
|
||||
echo "Usage: ./createSwiftUISimpleScreen.sh Folder MyScreenName"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MODULE_DIR="../ElementX/Sources/Modules"
|
||||
OUTPUT_DIR=$MODULE_DIR/$1
|
||||
SCREEN_NAME=$2
|
||||
SCREEN_VAR_NAME=`echo $SCREEN_NAME | awk '{ print tolower(substr($0, 1, 1)) substr($0, 2) }'`
|
||||
TEMPLATE_DIR=$MODULE_DIR/Templates/SimpleScreenExample/
|
||||
if [ -e $OUTPUT_DIR ]; then
|
||||
echo "Error: Folder ${OUTPUT_DIR} already exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Create folder ${OUTPUT_DIR}"
|
||||
|
||||
mkdir -p $OUTPUT_DIR
|
||||
cp -R $TEMPLATE_DIR $OUTPUT_DIR/
|
||||
|
||||
cd $OUTPUT_DIR
|
||||
for file in $(find * -type f -print)
|
||||
do
|
||||
echo "Building ${file/TemplateSimpleScreen/$SCREEN_NAME}..."
|
||||
perl -p -i -e "s/TemplateSimpleScreen/"$SCREEN_NAME"/g" $file
|
||||
perl -p -i -e "s/templateSimpleScreen/"$SCREEN_VAR_NAME"/g" $file
|
||||
|
||||
mv ${file} ${file/TemplateSimpleScreen/$SCREEN_NAME}
|
||||
done
|
Loading…
x
Reference in New Issue
Block a user