diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 127c8405e2..7d72164ae9 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 1999ECC6777752A2616775CF /* MemberDetailsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A152791A2F56BD193BFE986 /* MemberDetailsProvider.swift */; }; 1A70A2199394B5EC660934A5 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = A678E40E917620059695F067 /* MatrixRustSDK */; }; 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; }; + 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05707BF550D770168A406DB /* LoginViewModelTests.swift */; }; 1F3232BD368DF430AB433907 /* DesignKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5A56C4F47C368EBE5C5E870 /* DesignKit */; }; 206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */; }; 224A55EEAEECF5336B14A4A5 /* EmoteRoomMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2DF459F1737A594667CC46 /* EmoteRoomMessage.swift */; }; @@ -108,11 +109,13 @@ 56F0A22972A3BB519DA2261C /* HomeScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */; }; 59C41313AED7566C3AC51163 /* RoomSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29A953B6C0C431DBF4DD00B4 /* RoomSummary.swift */; }; 5B2C4C17888FC095ED6880B2 /* SplashViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 48971F1FFD7FC5C466889FC7 /* SplashViewController.xib */; }; + 5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; }; 5CABC57F620FBB39F4EC127C /* TemplateSimpleScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9BA045DC4CA12D030ACF558 /* TemplateSimpleScreen.swift */; }; 5D430CDE11EAC3E8E6B80A66 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */; }; 5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */; }; 5E1FCC43B738941D5A5F1794 /* SplashScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B73A8C3118EAC7BF3F3EE7A /* SplashScreenViewModelProtocol.swift */; }; 5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; }; + 5FF799C79A7C127B043BADE9 /* ServerSelectionScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920F6C41B3BF1D35DB46EBE7 /* ServerSelectionScreen.swift */; }; 617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; }; 62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8210612D17A39369480FC183 /* MediaSource.swift */; }; 6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */; }; @@ -151,13 +154,16 @@ 872A6457DF573AF8CEAE927A /* LoginHomeserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9349F590E35CE514A71E6764 /* LoginHomeserver.swift */; }; 8775F46AE3234A5A5688C19D /* UserIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FAAA4A76CE4A1F3014D9 /* UserIndicator.swift */; }; 8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */; }; + 883FBD89B22FDFD8D7DF9FF5 /* ServerSelectionModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AFB7E9DFBCA13B4F6E84BFA /* ServerSelectionModels.swift */; }; 8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5E9C044BEB7C70B1378E91 /* UserSession.swift */; }; 8BBD3AA589DEE02A1B0923B2 /* NoticeRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F49CDE349C490D617332770 /* NoticeRoomTimelineItem.swift */; }; 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; 8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */; }; 90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; }; 90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; }; - 91CC102A286A0D9400B6E687 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CC1029286A0D9400B6E687 /* LoginScreenUITests.swift */; }; + 91EA1E0A286C5AD700EBCFB8 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A53298ACBE1242AEDA1D6A2 /* ServerSelectionViewModelTests.swift */; }; + 91EA1E0B286C5ADE00EBCFB8 /* ServerSelectionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59B0BEC17C0D794CB8423E /* ServerSelectionUITests.swift */; }; + 91EA1E0D286C600000EBCFB8 /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91EA1E0C286C600000EBCFB8 /* AuthenticationIconImage.swift */; }; 93BA4A81B6D893271101F9F0 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 5986E300FC849DEAB2EE7AEB /* Introspect */; }; 94E062D08E27B0387658E364 /* SplashScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5CF94E124616FD89424B73 /* SplashScreenViewModelTests.swift */; }; 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B902EA6CD3296B0E10EE432B /* HomeScreen.swift */; }; @@ -183,18 +189,19 @@ A636D4900E0D98ED91536482 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF3EDF23226895776553F04A /* AppCoordinator.swift */; }; A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; - A8177B197C2E3DB7ACB63088 /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88981CE56026FE761433BA56 /* LoginViewModelTests.swift */; }; A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; AB34401E4E1CAD5D2EC3072B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9760103CF316DF68698BCFE6 /* LaunchScreen.storyboard */; }; ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */; }; B0EDAF55877DE19B67837C22 /* TemplateSimpleScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C29670CEC77346F31EE94C /* TemplateSimpleScreenModels.swift */; }; B245583C63F8F90357B87FAE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; }; B3FDB1D9CF40777695DBBD1D /* AppCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */; }; + B49BD1C48AE49D86553E9872 /* ServerSelectionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A542F157990FB61E001158EB /* ServerSelectionCoordinator.swift */; }; B4AAB3257A83B73F53FB2689 /* StateStoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */; }; B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; }; B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; }; B80C4FABB5529DF12436FFDA /* AppIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 16DC8C5B2991724903F1FA6A /* AppIcon.pdf */; }; B94368839BDB69172E28E245 /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B698739E3410E2CDB7144 /* MXLog.swift */; }; + B9F270805FCAA35FCB561822 /* ServerSelectionViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A09104E096E3EF078243C55C /* ServerSelectionViewModelProtocol.swift */; }; BCC3EDB7AD0902797CB4BBC2 /* MXLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = EF188681D6B6068CFAEAFC3F /* MXLogger.m */; }; BCEC41FB1F2BB663183863E4 /* LoginServerInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D379E13DD9D987470A3C70C /* LoginServerInfoSection.swift */; }; BE3237142FA6E1A13C0E7D11 /* RoomSummaryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C21ECC295F4DE8DAA86D62AC /* RoomSummaryProtocol.swift */; }; @@ -227,6 +234,7 @@ DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; }; DFF7D6A6C26DDD40D00AE579 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = F012CB5EE3F2B67359F6CC52 /* target.yml */; }; E1DF24D085572A55C9758A2D /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; + E5A1C08F7099A4CC6D9DF5FB /* ServerSelectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F035F62329F35ED0DDAA404 /* ServerSelectionViewModel.swift */; }; E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.swift */; }; EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; }; EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; }; @@ -238,6 +246,7 @@ EF99A92701E401C4CD5ADC50 /* SplashScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */; }; F03E16ED043C62FED5A07AE0 /* MatrixEntitityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */; }; F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD51F9FDC91C231906D76C8 /* KeychainControllerProtocol.swift */; }; + F46AF85FE8919ACC102219D1 /* MockServerSelectionScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CC977884A5F433584DC26F /* MockServerSelectionScreenState.swift */; }; F4C3FEDB1B3A05376A1723A3 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */; }; F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; }; F56261126E368C831B3DE976 /* NavigationRouterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752DEC02D93AFF46BC13313A /* NavigationRouterType.swift */; }; @@ -271,6 +280,7 @@ /* Begin PBXFileReference section */ 01C4C7DB37597D7D8379511A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 02A07FF019724B6ACEA73076 /* szl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = szl; path = szl.lproj/Localizable.strings; sourceTree = ""; }; + 03CC977884A5F433584DC26F /* MockServerSelectionScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServerSelectionScreenState.swift; sourceTree = ""; }; 04BBC9E08250EF92ADE89CFD /* sr-Latn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sr-Latn"; path = "sr-Latn.lproj/Localizable.strings"; sourceTree = ""; }; 04E1273CC3BC3E471AF87BE5 /* UserIndicatorQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorQueueTests.swift; sourceTree = ""; }; 057B747CF045D3C6C30EAB2C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -282,6 +292,7 @@ 09747989908EC5E4AA29F844 /* MemberDetailsProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailsProviderProtocol.swift; sourceTree = ""; }; 0A191D3FDB995309C7E2DE7D /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; 0AB7A0C06CB527A1095DEB33 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = da; path = da.lproj/Localizable.stringsdict; sourceTree = ""; }; + 0AFB7E9DFBCA13B4F6E84BFA /* ServerSelectionModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionModels.swift; sourceTree = ""; }; 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSenderAvatarView.swift; sourceTree = ""; }; 0C13A92C1E9C79F055B8133D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = ""; }; 0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; @@ -308,6 +319,7 @@ 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Strings+Untranslated.swift"; sourceTree = ""; }; 1A63815AD6A5C306453342F2 /* ImageRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItem.swift; sourceTree = ""; }; 1C429043E986008B97736636 /* ab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ab; path = ab.lproj/Localizable.strings; sourceTree = ""; }; + 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = ""; }; 1E1FB768A24FDD2A5CA16E3C /* LoginViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelProtocol.swift; sourceTree = ""; }; 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DTHTMLElement+AttributedStringBuilder.swift"; sourceTree = ""; }; 2112A6CFEA46E672D90EBF54 /* kab */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kab; path = kab.lproj/Localizable.strings; sourceTree = ""; }; @@ -327,6 +339,7 @@ 2BEB3259B2208E5AE5BB3F65 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 2CF9FE7E0CF9F40D1509E63A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = bg; path = bg.lproj/Localizable.stringsdict; sourceTree = ""; }; 2EEB64CC6F3DF5B68736A6B4 /* AlertInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertInfo.swift; sourceTree = ""; }; + 2F035F62329F35ED0DDAA404 /* ServerSelectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModel.swift; sourceTree = ""; }; 31B01468022EC826CB2FD2C0 /* LoginModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginModels.swift; sourceTree = ""; }; 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = ""; }; 325A2B3278875554DDEB8A9B /* SplashScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenUITests.swift; sourceTree = ""; }; @@ -465,8 +478,8 @@ 878B7C1885486FB4BE41631D /* iw */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = iw; path = iw.lproj/Localizable.stringsdict; sourceTree = ""; }; 885D8C42DD17625B5261BEFF /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; 8888D13645C04AC9818F5778 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 88981CE56026FE761433BA56 /* LoginViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelTests.swift; sourceTree = ""; }; 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyler.swift; sourceTree = ""; }; + 8A53298ACBE1242AEDA1D6A2 /* ServerSelectionViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelTests.swift; sourceTree = ""; }; 8A9AE4967817E9608E22EB44 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 8BF686BA36D0C2FA3C63DFDF /* ImageRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomMessage.swift; sourceTree = ""; }; 8C0AA893D6F8A2F563E01BB9 /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = in; path = in.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -475,7 +488,8 @@ 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStyle.swift; sourceTree = ""; }; 90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = ""; }; - 91CC1029286A0D9400B6E687 /* LoginScreenUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginScreenUITests.swift; sourceTree = ""; }; + 91EA1E0C286C600000EBCFB8 /* AuthenticationIconImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationIconImage.swift; sourceTree = ""; }; + 920F6C41B3BF1D35DB46EBE7 /* ServerSelectionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreen.swift; sourceTree = ""; }; 92B61C243325DC76D3086494 /* EventBriefFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactoryProtocol.swift; sourceTree = ""; }; 9349F590E35CE514A71E6764 /* LoginHomeserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginHomeserver.swift; sourceTree = ""; }; 938BD1FCD9E6FF3FCFA7AB4C /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-CN"; path = "zh-CN.lproj/Localizable.stringsdict"; sourceTree = ""; }; @@ -496,11 +510,14 @@ 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = ""; }; 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = ""; }; A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = ""; }; + A05707BF550D770168A406DB /* LoginViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModelTests.swift; sourceTree = ""; }; + A09104E096E3EF078243C55C /* ServerSelectionViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionViewModelProtocol.swift; sourceTree = ""; }; A1C29670CEC77346F31EE94C /* TemplateSimpleScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateSimpleScreenModels.swift; sourceTree = ""; }; A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; A2B6433F516F1E6DFA0E2D89 /* vls */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vls; path = vls.lproj/Localizable.strings; sourceTree = ""; }; A436057DBEA1A23CA8CB1FD7 /* UIFont+AttributedStringBuilder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+AttributedStringBuilder.h"; sourceTree = ""; }; A443FAE2EE820A5790C35C8D /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; + A542F157990FB61E001158EB /* ServerSelectionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionCoordinator.swift; sourceTree = ""; }; A64F0DB78E0AC23C91AD89EF /* mk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mk; path = mk.lproj/Localizable.strings; sourceTree = ""; }; A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = ""; }; A72232816DCE2B76D48E1367 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = ""; }; @@ -619,6 +636,7 @@ FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerTests.swift; sourceTree = ""; }; FE2DF459F1737A594667CC46 /* EmoteRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomMessage.swift; sourceTree = ""; }; FF4EDB32B97910AAAFE632B2 /* TemplateSimpleScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateSimpleScreenViewModelProtocol.swift; sourceTree = ""; }; + FF59B0BEC17C0D794CB8423E /* ServerSelectionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionUITests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -782,6 +800,19 @@ path = Members; sourceTree = ""; }; + 3510020809E49EFA146296AD /* ServerSelection */ = { + isa = PBXGroup; + children = ( + A542F157990FB61E001158EB /* ServerSelectionCoordinator.swift */, + 0AFB7E9DFBCA13B4F6E84BFA /* ServerSelectionModels.swift */, + 2F035F62329F35ED0DDAA404 /* ServerSelectionViewModel.swift */, + A09104E096E3EF078243C55C /* ServerSelectionViewModelProtocol.swift */, + 03CC977884A5F433584DC26F /* MockServerSelectionScreenState.swift */, + 9D54059E4E42176B3ABB729F /* View */, + ); + path = ServerSelection; + sourceTree = ""; + }; 4009BE2E791C16AC6EE39A7E /* BugReport */ = { isa = PBXGroup; children = ( @@ -977,7 +1008,6 @@ isa = PBXGroup; children = ( AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */, - 88981CE56026FE761433BA56 /* LoginViewModelTests.swift */, EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */, 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */, DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */, @@ -986,6 +1016,8 @@ FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */, C070FD43DC6BF4E50217965A /* LocalizationTests.swift */, 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */, + 8A53298ACBE1242AEDA1D6A2 /* ServerSelectionViewModelTests.swift */, + A05707BF550D770168A406DB /* LoginViewModelTests.swift */, 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */, F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */, 3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */, @@ -1134,10 +1166,11 @@ children = ( 7D0CBC76C80E04345E11F2DB /* Application.swift */, C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */, + FF59B0BEC17C0D794CB8423E /* ServerSelectionUITests.swift */, 4D6E4C37E9F0E53D3DF951AC /* HomeScreenUITests.swift */, + 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */, 086B997409328F091EBA43CE /* RoomScreenUITests.swift */, E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */, - 91CC1029286A0D9400B6E687 /* LoginScreenUITests.swift */, 325A2B3278875554DDEB8A9B /* SplashScreenUITests.swift */, ); path = Sources; @@ -1188,6 +1221,14 @@ path = WeakDictionary; sourceTree = ""; }; + 9D54059E4E42176B3ABB729F /* View */ = { + isa = PBXGroup; + children = ( + 920F6C41B3BF1D35DB46EBE7 /* ServerSelectionScreen.swift */, + ); + path = View; + sourceTree = ""; + }; A0C06C0F6A8621B22BFAEB56 /* Localizations */ = { isa = PBXGroup; children = ( @@ -1371,8 +1412,10 @@ isa = PBXGroup; children = ( D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */, + 91EA1E0C286C600000EBCFB8 /* AuthenticationIconImage.swift */, 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */, 90F48FEF84016ED42A94BA24 /* LoginScreen */, + 3510020809E49EFA146296AD /* ServerSelection */, ); path = Authentication; sourceTree = ""; @@ -1711,16 +1754,17 @@ buildActionMask = 2147483647; files = ( 90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */, - A8177B197C2E3DB7ACB63088 /* LoginViewModelTests.swift in Sources */, 7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */, C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */, CA1E41AE5CDCB8D801DE0830 /* BuildSettings.swift in Sources */, + 91EA1E0A286C5AD700EBCFB8 /* ServerSelectionViewModelTests.swift in Sources */, 9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */, F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */, 0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */, EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */, 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */, 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */, + 1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */, 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */, EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */, 206F0DBAB6AF042CA1FF2C0D /* SettingsViewModelTests.swift in Sources */, @@ -1748,6 +1792,11 @@ 3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */, A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */, EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */, + B49BD1C48AE49D86553E9872 /* ServerSelectionCoordinator.swift in Sources */, + 883FBD89B22FDFD8D7DF9FF5 /* ServerSelectionModels.swift in Sources */, + 5FF799C79A7C127B043BADE9 /* ServerSelectionScreen.swift in Sources */, + E5A1C08F7099A4CC6D9DF5FB /* ServerSelectionViewModel.swift in Sources */, + B9F270805FCAA35FCB561822 /* ServerSelectionViewModelProtocol.swift in Sources */, CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */, 38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */, B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */, @@ -1806,12 +1855,14 @@ F03E16ED043C62FED5A07AE0 /* MatrixEntitityRegex.swift in Sources */, EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */, 7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */, + 91EA1E0D286C600000EBCFB8 /* AuthenticationIconImage.swift in Sources */, 62BBF5BE7B905222F0477FF2 /* MediaSource.swift in Sources */, 03B8FEA668A5B76A93113BB1 /* MemberDetailProviderManager.swift in Sources */, 1999ECC6777752A2616775CF /* MemberDetailsProvider.swift in Sources */, A5EC21A071F58FC1229C20D0 /* MemberDetailsProviderProtocol.swift in Sources */, 24906A1E82D0046655958536 /* MessageComposer.swift in Sources */, 072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */, + F46AF85FE8919ACC102219D1 /* MockServerSelectionScreenState.swift in Sources */, 28410F3DE89C2C44E4F75C92 /* MockBugReportService.swift in Sources */, 67E391A2E00709FB41903B36 /* MockMediaProvider.swift in Sources */, 51DB67C5B5BC68B0A6FF54D4 /* MockRoomProxy.swift in Sources */, @@ -1922,17 +1973,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 91CC102A286A0D9400B6E687 /* LoginScreenUITests.swift in Sources */, 7405B4824D45BA7C3D943E76 /* Application.swift in Sources */, 7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */, 499A26EB06C97E48C27A2DB9 /* BuildSettings.swift in Sources */, 9DC5FB22B8F86C3B51E907C1 /* HomeScreenUITests.swift in Sources */, + 5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */, 2F1CF90A3460C153154427F0 /* RoomScreenUITests.swift in Sources */, 490E606044B18985055FF690 /* SettingsUITests.swift in Sources */, A00DFC1DD3567B1EDC9F8D16 /* SplashScreenUITests.swift in Sources */, 2E68C57E7D644E94778743D5 /* TemplateSimpleScreenUITests.swift in Sources */, 0ED951768EC443A8728DE1D7 /* TimelineStyle.swift in Sources */, 75D98001C5AC38B6A5CA897C /* UITestScreenIdentifier.swift in Sources */, + 91EA1E0B286C5ADE00EBCFB8 /* ServerSelectionUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 383a264ada..14dcda9dc3 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -60,7 +60,7 @@ "location" : "https://github.com/matrix-org/matrix-rust-components-swift.git", "state" : { "branch" : "main", - "revision" : "43c88a4b0912a1589c2a28cc9bb2df45c70cdcad" + "revision" : "166966cb524f899bf0ac95e840c0fe01de48123e" } }, { diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Contents.json similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Contents.json diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/Contents.json new file mode 100644 index 0000000000..fec295dec4 --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "authentication_server_selection_icon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/authentication_server_selection_icon.svg b/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/authentication_server_selection_icon.svg new file mode 100644 index 0000000000..17b23458ed --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/authentication_server_selection_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 1.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 1.imageset/Contents.json similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 1.imageset/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 1.imageset/Contents.json diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 1.imageset/OnboardingSplashScreenPage1-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 1.imageset/OnboardingSplashScreenPage1-Dark.pdf similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 1.imageset/OnboardingSplashScreenPage1-Dark.pdf rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 1.imageset/OnboardingSplashScreenPage1-Dark.pdf diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 1.imageset/OnboardingSplashScreenPage1.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 1.imageset/OnboardingSplashScreenPage1.pdf similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 1.imageset/OnboardingSplashScreenPage1.pdf rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 1.imageset/OnboardingSplashScreenPage1.pdf diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 2.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 2.imageset/Contents.json similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 2.imageset/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 2.imageset/Contents.json diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 2.imageset/OnboardingSplashScreenPage2-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 2.imageset/OnboardingSplashScreenPage2-Dark.pdf similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 2.imageset/OnboardingSplashScreenPage2-Dark.pdf rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 2.imageset/OnboardingSplashScreenPage2-Dark.pdf diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 2.imageset/OnboardingSplashScreenPage2.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 2.imageset/OnboardingSplashScreenPage2.pdf similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 2.imageset/OnboardingSplashScreenPage2.pdf rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 2.imageset/OnboardingSplashScreenPage2.pdf diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 3.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 3.imageset/Contents.json similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 3.imageset/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 3.imageset/Contents.json diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 3.imageset/OnboardingSplashScreenPage3-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 3.imageset/OnboardingSplashScreenPage3-Dark.pdf similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 3.imageset/OnboardingSplashScreenPage3-Dark.pdf rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 3.imageset/OnboardingSplashScreenPage3-Dark.pdf diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 3.imageset/OnboardingSplashScreenPage3.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 3.imageset/OnboardingSplashScreenPage3.pdf similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 3.imageset/OnboardingSplashScreenPage3.pdf rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 3.imageset/OnboardingSplashScreenPage3.pdf diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 4.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 4.imageset/Contents.json similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 4.imageset/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 4.imageset/Contents.json diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 4.imageset/OnboardingSplashScreenPage4-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 4.imageset/OnboardingSplashScreenPage4-Dark.pdf similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 4.imageset/OnboardingSplashScreenPage4-Dark.pdf rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 4.imageset/OnboardingSplashScreenPage4-Dark.pdf diff --git a/ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 4.imageset/OnboardingSplashScreenPage4.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 4.imageset/OnboardingSplashScreenPage4.pdf similarity index 100% rename from ElementX/Resources/Assets.xcassets/Images/Splash Screen/Splash Screen Page 4.imageset/OnboardingSplashScreenPage4.pdf rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Splash Screen Page 4.imageset/OnboardingSplashScreenPage4.pdf diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index dbab7baab6..cd02c044cf 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -1,4 +1,9 @@ +/* Used for testing */ "untranslated" = "Untranslated"; + +"action_confirm" = "Confirm"; +"action_next" = "Next"; + "screenshot_detected_title" = "You took a screenshot"; "screenshot_detected_message" = "Would you like to submit a bug report?"; @@ -13,3 +18,9 @@ "authentication_server_info_title" = "Choose your server to store your data"; "authentication_server_info_matrix_description" = "Join millions for free on the largest public server"; + +"server_selection_title" = "Choose your server"; +"server_selection_message" = "What is the address of your server? A server is like a home for all your data."; +"server_selection_server_url" = "Server URL"; +"server_selection_server_footer" = "You can only connect to a server that has already been set up"; +"server_selection_generic_error" = "Cannot find a server at this URL, please check it is correct."; diff --git a/ElementX/Sources/Generated/Assets.swift b/ElementX/Sources/Generated/Assets.swift index fc57915994..7438510d7c 100644 --- a/ElementX/Sources/Generated/Assets.swift +++ b/ElementX/Sources/Generated/Assets.swift @@ -20,13 +20,14 @@ internal typealias AssetImageTypeAlias = ImageAsset.Image // swiftlint:disable identifier_name line_length nesting type_body_length type_name internal enum Asset { internal enum Images { - internal static let encryptionNormal = ImageAsset(name: "Images/encryption_normal") - internal static let encryptionTrusted = ImageAsset(name: "Images/encryption_trusted") - internal static let encryptionWarning = ImageAsset(name: "Images/encryption_warning") + internal static let serverSelectionIcon = ImageAsset(name: "Images/Server Selection Icon") internal static let splashScreenPage1 = ImageAsset(name: "Images/Splash Screen Page 1") internal static let splashScreenPage2 = ImageAsset(name: "Images/Splash Screen Page 2") internal static let splashScreenPage3 = ImageAsset(name: "Images/Splash Screen Page 3") internal static let splashScreenPage4 = ImageAsset(name: "Images/Splash Screen Page 4") + internal static let encryptionNormal = ImageAsset(name: "Images/encryption_normal") + internal static let encryptionTrusted = ImageAsset(name: "Images/encryption_trusted") + internal static let encryptionWarning = ImageAsset(name: "Images/encryption_warning") internal static let appLogo = ImageAsset(name: "Images/app-logo") internal static let closeCircle = ImageAsset(name: "Images/close_circle") internal static let timelineComposerSendMessage = ImageAsset(name: "Images/timelineComposerSendMessage") diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index dd5b63c302..e15e875ab9 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -10,6 +10,10 @@ import Foundation // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces extension ElementL10n { + /// Confirm + public static let actionConfirm = ElementL10n.tr("Untranslated", "action_confirm") + /// Next + public static let actionNext = ElementL10n.tr("Untranslated", "action_next") /// Forgot password public static let authenticationLoginForgotPassword = ElementL10n.tr("Untranslated", "authentication_login_forgot_password") /// Welcome back! @@ -26,6 +30,16 @@ extension ElementL10n { public static let screenshotDetectedMessage = ElementL10n.tr("Untranslated", "screenshot_detected_message") /// You took a screenshot public static let screenshotDetectedTitle = ElementL10n.tr("Untranslated", "screenshot_detected_title") + /// Cannot find a server at this URL, please check it is correct. + public static let serverSelectionGenericError = ElementL10n.tr("Untranslated", "server_selection_generic_error") + /// What is the address of your server? A server is like a home for all your data. + public static let serverSelectionMessage = ElementL10n.tr("Untranslated", "server_selection_message") + /// You can only connect to a server that has already been set up + public static let serverSelectionServerFooter = ElementL10n.tr("Untranslated", "server_selection_server_footer") + /// Server URL + public static let serverSelectionServerUrl = ElementL10n.tr("Untranslated", "server_selection_server_url") + /// Choose your server + public static let serverSelectionTitle = ElementL10n.tr("Untranslated", "server_selection_title") /// Timeline Style public static let settingsTimelineStyle = ElementL10n.tr("Untranslated", "settings_timeline_style") /// Untranslated diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationIconImage.swift b/ElementX/Sources/Screens/Authentication/AuthenticationIconImage.swift new file mode 100644 index 0000000000..a452471570 --- /dev/null +++ b/ElementX/Sources/Screens/Authentication/AuthenticationIconImage.swift @@ -0,0 +1,41 @@ +// +// Copyright 2022 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 + +/// An image that is styled for use as the screen icon in the onboarding flow. +struct AuthenticationIconImage: View { + + let image: ImageAsset + + var body: some View { + Image(image.name) + .resizable() + .renderingMode(.template) + .foregroundColor(.element.accent) + .frame(width: 90, height: 90) + .background(.white, in: Circle().inset(by: 2)) + .accessibilityHidden(true) + } +} + +// MARK: - Previews + +struct AuthenticationIconImage_Previews: PreviewProvider { + static var previews: some View { + AuthenticationIconImage(image: Asset.Images.serverSelectionIcon) + } +} diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift index 4c452c33a6..186d92673a 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift @@ -178,7 +178,34 @@ final class LoginCoordinator: Coordinator, Presentable { /// Presents the server selection screen as a modal. private func presentServerSelectionScreen() { - loginViewModel.displayError(.alert("Not implemented. Enter a full Matrix ID such as @user:server.com")) + MXLog.debug("[LoginCoordinator] presentServerSelectionScreen") + let parameters = ServerSelectionCoordinatorParameters(homeserver: loginViewModel.context.viewState.homeserver, + hasModalPresentation: true) + let coordinator = ServerSelectionCoordinator(parameters: parameters) + coordinator.callback = { [weak self, weak coordinator] result in + guard let self = self, let coordinator = coordinator else { return } + self.serverSelectionCoordinator(coordinator, didCompleteWith: result) + } + + coordinator.start() + add(childCoordinator: coordinator) + + let modalRouter = NavigationRouter(navigationController: UINavigationController()) + modalRouter.setRootModule(coordinator) + + navigationRouter.present(modalRouter, animated: true) + } + + /// Handles the result from the server selection modal, dismissing it after updating the view. + private func serverSelectionCoordinator(_ coordinator: ServerSelectionCoordinator, + didCompleteWith result: ServerSelectionCoordinatorResult) { + navigationRouter.dismissModule(animated: true) { [weak self] in + if case let .selected(homeserver) = result { + self?.updateViewModel(homeserver: homeserver) + } + + self?.remove(childCoordinator: coordinator) + } } /// Shows the forgot password screen. diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/MockServerSelectionScreenState.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/MockServerSelectionScreenState.swift new file mode 100644 index 0000000000..f4604f57fa --- /dev/null +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/MockServerSelectionScreenState.swift @@ -0,0 +1,47 @@ +// +// 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 + +enum MockServerSelectionScreenState: CaseIterable { + case matrix + case emptyAddress + case invalidAddress + case nonModal + + /// Generate the view struct for the screen state. + var viewModel: ServerSelectionViewModel { + // swiftlint:disable:next implicit_getter + @MainActor get { + switch self { + case .matrix: + return ServerSelectionViewModel(homeserverAddress: "https://matrix.org", + hasModalPresentation: true) + case .emptyAddress: + return ServerSelectionViewModel(homeserverAddress: "", + hasModalPresentation: true) + case .invalidAddress: + let viewModel = ServerSelectionViewModel(homeserverAddress: "thisisbad", + hasModalPresentation: true) + viewModel.displayError(.footerMessage(ElementL10n.unknownError)) + return viewModel + case .nonModal: + return ServerSelectionViewModel(homeserverAddress: "https://matrix.org", + hasModalPresentation: false) + } + } + } +} diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionCoordinator.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionCoordinator.swift new file mode 100644 index 0000000000..a367bfd59e --- /dev/null +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionCoordinator.swift @@ -0,0 +1,110 @@ +// +// 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 ServerSelectionCoordinatorParameters { + /// The homeserver to be shown initially. + let homeserver: LoginHomeserver + /// Whether the screen is presented modally or within a navigation stack. + let hasModalPresentation: Bool +} + +enum ServerSelectionCoordinatorResult { + case selected(LoginHomeserver) + case dismiss +} + +final class ServerSelectionCoordinator: Coordinator, Presentable { + + // MARK: - Properties + + // MARK: Private + + private let parameters: ServerSelectionCoordinatorParameters + private let serverSelectionHostingController: UIViewController + private var serverSelectionViewModel: ServerSelectionViewModelProtocol + + private var indicatorPresenter: UserIndicatorTypePresenterProtocol + private var loadingIndicator: UserIndicator? + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var callback: (@MainActor (ServerSelectionCoordinatorResult) -> Void)? + + // MARK: - Setup + + init(parameters: ServerSelectionCoordinatorParameters) { + self.parameters = parameters + + let viewModel = ServerSelectionViewModel(homeserverAddress: parameters.homeserver.address, + hasModalPresentation: parameters.hasModalPresentation) + let view = ServerSelectionScreen(viewModel: viewModel.context) + serverSelectionViewModel = viewModel + serverSelectionHostingController = UIHostingController(rootView: view) + + indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: serverSelectionHostingController) + } + + // MARK: - Public + + func start() { + MXLog.debug("[ServerSelectionCoordinator] did start.") + + serverSelectionViewModel.callback = { [weak self] result in + guard let self = self else { return } + MXLog.debug("[ServerSelectionCoordinator] ServerSelectionViewModel did complete with result: \(result).") + + switch result { + case .confirm(let homeserverAddress): + self.useHomeserver(homeserverAddress) + case .dismiss: + self.callback?(.dismiss) + } + } + } + + func toPresentable() -> UIViewController { + return self.serverSelectionHostingController + } + + // MARK: - Private + + /// Show an activity indicator whilst loading. + /// - Parameters: + /// - label: The label to show on the indicator. + /// - isInteractionBlocking: Whether the indicator should block any user interaction. + private func startLoading(label: String = ElementL10n.loading, isInteractionBlocking: Bool = true) { + loadingIndicator = indicatorPresenter.present(.loading(label: label, isInteractionBlocking: isInteractionBlocking)) + } + + /// Hide the currently displayed activity indicator. + private func stopLoading() { + loadingIndicator = nil + } + + /// Updates the login flow using the supplied homeserver address, or shows an error when this isn't possible. + private func useHomeserver(_ homeserverAddress: String) { + startLoading() + + let homeserverAddress = LoginHomeserver.sanitized(homeserverAddress) + callback?(.selected(LoginHomeserver(address: homeserverAddress))) + + stopLoading() + } +} diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionModels.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionModels.swift new file mode 100644 index 0000000000..e5e5792ef6 --- /dev/null +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionModels.swift @@ -0,0 +1,78 @@ +// +// 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: View model + +enum ServerSelectionViewModelResult { + /// The user would like to use the homeserver at the given address. + case confirm(homeserverAddress: String) + /// Dismiss the view without using the entered address. + case dismiss +} + +// MARK: View + +struct ServerSelectionViewState: BindableState { + /// View state that can be bound to from SwiftUI. + var bindings: ServerSelectionBindings + /// An error message to be shown in the text field footer. + var footerErrorMessage: String? + /// Whether the screen is presented modally or within a navigation stack. + var hasModalPresentation: Bool + + /// The message to show in the text field footer. + var footerMessage: String { + footerErrorMessage ?? ElementL10n.serverSelectionServerFooter + } + + /// The title shown on the confirm button. + var buttonTitle: String { + hasModalPresentation ? ElementL10n.actionConfirm : ElementL10n.actionNext + } + + /// The text field is showing an error. + var isShowingFooterError: Bool { + footerErrorMessage != nil + } + + /// Whether it is possible to continue when tapping the confirmation button. + var hasValidationError: Bool { + bindings.homeserverAddress.isEmpty || isShowingFooterError + } +} + +struct ServerSelectionBindings { + /// The homeserver address input by the user. + var homeserverAddress: String + /// Information describing the currently displayed alert. + var alertInfo: AlertInfo? +} + +enum ServerSelectionViewAction { + /// The user would like to use the homeserver at the input address. + case confirm + /// Dismiss the view without using the entered address. + case dismiss + /// Clear any errors shown in the text field footer. + case clearFooterError +} + +enum ServerSelectionErrorType: Hashable { + /// An error message to be shown in the text field footer. + case footerMessage(String) +} diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModel.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModel.swift new file mode 100644 index 0000000000..9d8e9107bf --- /dev/null +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModel.swift @@ -0,0 +1,68 @@ +// +// 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 + +typealias ServerSelectionViewModelType = StateStoreViewModel + +class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionViewModelProtocol { + + // MARK: - Properties + + // MARK: Private + + // MARK: Public + + var callback: (@MainActor (ServerSelectionViewModelResult) -> Void)? + + // MARK: - Setup + + init(homeserverAddress: String, hasModalPresentation: Bool) { + let bindings = ServerSelectionBindings(homeserverAddress: homeserverAddress) + super.init(initialViewState: ServerSelectionViewState(bindings: bindings, + hasModalPresentation: hasModalPresentation)) + } + + // MARK: - Public + + override func process(viewAction: ServerSelectionViewAction) async { + switch viewAction { + case .confirm: + callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress)) + case .dismiss: + callback?(.dismiss) + case .clearFooterError: + clearFooterError() + } + } + + func displayError(_ type: ServerSelectionErrorType) { + switch type { + case .footerMessage(let message): + withAnimation { + state.footerErrorMessage = message + } + } + } + + // MARK: - Private + + /// Clear any errors shown in the text field footer. + private func clearFooterError() { + guard state.footerErrorMessage != nil else { return } + withAnimation { state.footerErrorMessage = nil } + } +} diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModelProtocol.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModelProtocol.swift new file mode 100644 index 0000000000..e6094708b0 --- /dev/null +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionViewModelProtocol.swift @@ -0,0 +1,27 @@ +// +// 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 + +@MainActor +protocol ServerSelectionViewModelProtocol { + + var callback: (@MainActor (ServerSelectionViewModelResult) -> Void)? { get set } + var context: ServerSelectionViewModelType.Context { get } + + /// Displays an error to the user. + func displayError(_ type: ServerSelectionErrorType) +} diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift new file mode 100644 index 0000000000..53f6dcbeb6 --- /dev/null +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift @@ -0,0 +1,133 @@ +// +// 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 ServerSelectionScreen: View { + + // MARK: - Properties + + // MARK: Private + + @FocusState var isTextFieldFocused: Bool + + // MARK: Public + + @ObservedObject var viewModel: ServerSelectionViewModel.Context + + // MARK: Views + + var body: some View { + ScrollView { + VStack(spacing: 0) { + header + .padding(.top, UIConstants.topPaddingToNavigationBar) + .padding(.bottom, 36) + + serverForm + } + .readableFrame() + .padding(.horizontal, 16) + } + .background(Color.element.background, ignoresSafeAreaEdges: .all) + .toolbar { toolbar } + .alert(item: $viewModel.alertInfo) { $0.alert } + } + + /// The title, message and icon at the top of the screen. + var header: some View { + VStack(spacing: 8) { + AuthenticationIconImage(image: Asset.Images.serverSelectionIcon) + .padding(.bottom, 8) + + Text(ElementL10n.serverSelectionTitle) + .font(.element.title2B) + .multilineTextAlignment(.center) + .foregroundColor(.element.primaryContent) + + Text(ElementL10n.serverSelectionMessage) + .font(.element.body) + .multilineTextAlignment(.center) + .foregroundColor(.element.secondaryContent) + } + } + + /// The text field and confirm button where the user enters a server URL. + var serverForm: some View { + VStack(alignment: .leading, spacing: 12) { + VStack(spacing: 8) { + if #available(iOS 15.0, *) { + textField + .onSubmit(submit) + } else { + textField + } + } + + Button(action: submit) { + Text(viewModel.viewState.buttonTitle) + } + .buttonStyle(.elementAction(.xLarge)) + .disabled(viewModel.viewState.hasValidationError) + .accessibilityIdentifier("confirmButton") + } + } + + /// The text field, extracted for iOS 15 modifiers to be applied. + var textField: some View { + TextField(ElementL10n.serverSelectionServerUrl, text: $viewModel.homeserverAddress) + .focused($isTextFieldFocused) + .textFieldStyle(.elementInput(footerText: viewModel.viewState.footerMessage, + isError: viewModel.viewState.isShowingFooterError)) + .keyboardType(.URL) + .autocapitalization(.none) + .disableAutocorrection(true) + .onChange(of: viewModel.homeserverAddress) { _ in viewModel.send(viewAction: .clearFooterError) } + .accessibilityIdentifier("addressTextField") + } + + @ToolbarContentBuilder + var toolbar: some ToolbarContent { + ToolbarItem(placement: .cancellationAction) { + if viewModel.viewState.hasModalPresentation { + Button { viewModel.send(viewAction: .dismiss) } label: { + Text(ElementL10n.actionCancel) + } + .accessibilityIdentifier("dismissButton") + } + } + } + + /// Sends the `confirm` view action so long as the text field input is valid. + func submit() { + guard !viewModel.viewState.hasValidationError else { return } + viewModel.send(viewAction: .confirm) + } +} + +// MARK: - Previews + +struct ServerSelection_Previews: PreviewProvider { + static var previews: some View { + ForEach(MockServerSelectionScreenState.allCases, id: \.self) { state in + NavigationView { + ServerSelectionScreen(viewModel: state.viewModel.context) + .tint(.element.accent) + } + .navigationViewStyle(.stack) + } + } +} diff --git a/UITests/Sources/ServerSelectionUITests.swift b/UITests/Sources/ServerSelectionUITests.swift new file mode 100644 index 0000000000..499fc9b100 --- /dev/null +++ b/UITests/Sources/ServerSelectionUITests.swift @@ -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 XCTest +//import ElementX +// +//class ServerSelectionUITests: XCTestCase { +// +// var app: XCUIApplication! +// +// override func setUp() async throws { +// app = Application.launch() +// } +// +// override class var screenType: MockScreenState.Type { +// return MockServerSelectionScreenState.self +// } +// +// override class func createTest() -> MockScreenTest { +// return ServerSelectionUITests(selector: #selector(verifyServerSelectionScreen)) +// } +// +// func verifyServerSelectionScreen() throws { +// guard let screenState = screenState as? MockServerSelectionScreenState else { fatalError("no screen") } +// switch screenState { +// case .matrix: +// verifyNormalState() +// case .emptyAddress: +// verifyEmptyAddress() +// case .invalidAddress: +// verifyInvalidAddress() +// case .nonModal: +// verifyNonModalPresentation() +// } +// } +// +// func verifyNormalState() { +// let serverTextField = app.textFields.element +// XCTAssertEqual(serverTextField.value as? String, "matrix.org", "The server shown should be matrix.org with the https scheme hidden.") +// +// let confirmButton = app.buttons["confirmButton"] +// XCTAssertEqual(confirmButton.label, ElementL10n.actionConfirm, "The confirm button should say Confirm when in modal presentation.") +// XCTAssertTrue(confirmButton.exists, "The confirm button should always be shown.") +// XCTAssertTrue(confirmButton.isEnabled, "The confirm button should be enabled when there is an address.") +// +// let textFieldFooter = app.staticTexts["textFieldFooter"] +// XCTAssertTrue(textFieldFooter.exists) +// XCTAssertEqual(textFieldFooter.label, ElementL10n.serverSelectionServerFooter) +// +// let dismissButton = app.buttons["dismissButton"] +// XCTAssertTrue(dismissButton.exists, "The dismiss button should be shown during modal presentation.") +// } +// +// func verifyEmptyAddress() { +// let serverTextField = app.textFields.element +// XCTAssertEqual(serverTextField.value as? String, ElementL10n.serverSelectionServerUrl, "The text field should show placeholder text in this state.") +// +// let confirmButton = app.buttons["confirmButton"] +// XCTAssertTrue(confirmButton.exists, "The confirm button should always be shown.") +// XCTAssertFalse(confirmButton.isEnabled, "The confirm button should be disabled when the address is empty.") +// } +// +// func verifyInvalidAddress() { +// let serverTextField = app.textFields.element +// XCTAssertEqual(serverTextField.value as? String, "thisisbad", "The text field should show the entered server.") +// +// let confirmButton = app.buttons["confirmButton"] +// XCTAssertTrue(confirmButton.exists, "The confirm button should always be shown.") +// XCTAssertFalse(confirmButton.isEnabled, "The confirm button should be disabled when there is an error.") +// +// let textFieldFooter = app.staticTexts["textFieldFooter"] +// XCTAssertTrue(textFieldFooter.exists) +// XCTAssertEqual(textFieldFooter.label, ElementL10n.unknownError) +// } +// +// func verifyNonModalPresentation() { +// let dismissButton = app.buttons["dismissButton"] +// XCTAssertFalse(dismissButton.exists, "The dismiss button should be hidden when not in modal presentation.") +// +// let confirmButton = app.buttons["confirmButton"] +// XCTAssertEqual(confirmButton.label, ElementL10n.actionNext, "The confirm button should say Next when not in modal presentation.") +// } +//} diff --git a/UnitTests/Sources/ServerSelectionViewModelTests.swift b/UnitTests/Sources/ServerSelectionViewModelTests.swift new file mode 100644 index 0000000000..597a6915fd --- /dev/null +++ b/UnitTests/Sources/ServerSelectionViewModelTests.swift @@ -0,0 +1,58 @@ +// +// 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 + +@MainActor +class ServerSelectionViewModelTests: XCTestCase { + private enum Constants { + static let counterInitialValue = 0 + } + + var viewModel: ServerSelectionViewModelProtocol! + var context: ServerSelectionViewModelType.Context! + + @MainActor override func setUp() { + viewModel = ServerSelectionViewModel(homeserverAddress: "", hasModalPresentation: true) + context = viewModel.context + } + + func testErrorMessage() async throws { + // Given a new instance of the view model. + XCTAssertNil(context.viewState.footerErrorMessage, "There should not be an error message for a new view model.") + XCTAssertEqual(context.viewState.footerMessage, ElementL10n.serverSelectionServerFooter, "The standard footer message should be shown.") + + // When an error occurs. + let message = "Unable to contact server." + viewModel.displayError(.footerMessage(message)) + + // Then the footer should now be showing an error. + XCTAssertEqual(context.viewState.footerErrorMessage, message, "The error message should be stored.") + XCTAssertEqual(context.viewState.footerMessage, message, "The error message should be shown.") + + // And when clearing the error. + context.send(viewAction: .clearFooterError) + + // Wait for the action to spawn a Task. + await Task.yield() + + // Then the error message should now be removed. + XCTAssertNil(context.viewState.footerErrorMessage, "The error message should have been cleared.") + XCTAssertEqual(context.viewState.footerMessage, ElementL10n.serverSelectionServerFooter, "The standard footer message should be shown again.") + } +}