Merge pull request #3890 from vector-im/element_3846

Implement social login
This commit is contained in:
SBiOSoftWhare 2021-01-08 17:48:55 +01:00 committed by GitHub
commit 63da5b7b12
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 2102 additions and 39 deletions

View file

@ -2,10 +2,11 @@ Changes to be released in next version
=================================================
✨ Features
*
* AuthVC: Add social login (#3846).
🙌 Improvements
* Show user id in the room invite preview screen (#3839)
* AuthVC: SSO authentication now use redirect URL instead of fallback page (#3846).
🐛 Bugfix
* Crash report cannot be submitted (on small phones) #3819

View file

@ -52,6 +52,17 @@ final class BuildSettings: NSObject {
return keychainAccessGroup
}
static var applicationURLScheme: String? {
guard let urlTypes = Bundle.app.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [AnyObject],
let urlTypeDictionary = urlTypes.first as? [String: AnyObject],
let urlSchemes = urlTypeDictionary["CFBundleURLSchemes"] as? [AnyObject],
let externalURLScheme = urlSchemes.first as? String else {
return nil
}
return externalURLScheme
}
static var pushKitAppIdProd: String {
return baseBundleIdentifier + ".ios.voip.prod"
}

View file

@ -27,3 +27,5 @@ KEYCHAIN_ACCESS_GROUP = $(AppIdentifierPrefix)$(BASE_BUNDLE_IDENTIFIER).keychain
//Make Xcode 12 and fastlane(xcodebuild) happy while some pods are not updated
EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64
APPLICATION_SCHEME = element

View file

@ -219,6 +219,8 @@
B12676882523E4D100BE6B98 /* SecretsResetViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12676802523E4D100BE6B98 /* SecretsResetViewState.swift */; };
B12676892523E4D100BE6B98 /* SecretsResetViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12676812523E4D100BE6B98 /* SecretsResetViewAction.swift */; };
B126768A2523E4D100BE6B98 /* SecretsResetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12676822523E4D100BE6B98 /* SecretsResetViewModel.swift */; };
B12742BA258C46DD00731DA6 /* SSOAuthentificationSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12742B9258C46DC00731DA6 /* SSOAuthentificationSessionProtocol.swift */; };
B12742BC258C472800731DA6 /* LegacySSOAuthentificationSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12742BB258C472800731DA6 /* LegacySSOAuthentificationSession.swift */; };
B1284E3E2535FEA6003529D7 /* SecureBackupSetupIntroViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1284E3D2535FEA6003529D7 /* SecureBackupSetupIntroViewModelType.swift */; };
B1284E402535FEBA003529D7 /* SecureBackupSetupIntroViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1284E3F2535FEBA003529D7 /* SecureBackupSetupIntroViewModel.swift */; };
B12C56EF2396CB5E00FAC6DE /* RoomMessageURLParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */; };
@ -246,6 +248,9 @@
B140B4A821F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */; };
B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B142317822CCFA2000FFA96A /* EditHistoryCell.swift */; };
B142317B22CCFA2000FFA96A /* EditHistoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B142317922CCFA2000FFA96A /* EditHistoryCell.xib */; };
B14EED19257992D700448735 /* SocialLoginListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14EED18257992D600448735 /* SocialLoginListView.swift */; };
B14EED1B2579933100448735 /* SocialLoginListView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B14EED1A2579933100448735 /* SocialLoginListView.xib */; };
B14EED1D257D85E000448735 /* SocialLoginButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14EED1C257D85DF00448735 /* SocialLoginButton.swift */; };
B14F142E22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B14F142622144F6400FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.storyboard */; };
B14F142F22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142722144F6400FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift */; };
B14F143022144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142822144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift */; };
@ -313,6 +318,14 @@
B169331520F3CAFC00746532 /* PublicRoomTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B169330F20F3CAFC00746532 /* PublicRoomTableViewCell.xib */; };
B169331620F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = B169331220F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m */; };
B169331720F3CBE000746532 /* RecentCellData.m in Sources */ = {isa = PBXBuildFile; fileRef = B16932F920F3C51900746532 /* RecentCellData.m */; };
B16C028D25A71CA3008CA7B1 /* CustomSchemeURLConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16C028C25A71CA3008CA7B1 /* CustomSchemeURLConstants.swift */; };
B16D6354257D8FB0008BDC97 /* SocialLoginButtonFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16D6353257D8FB0008BDC97 /* SocialLoginButtonFactory.swift */; };
B16DC22B2587DC35004DAB1A /* SSOAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC22A2587DC35004DAB1A /* SSOAuthenticationService.swift */; };
B16DC235258A723B004DAB1A /* SourceImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC234258A723B004DAB1A /* SourceImage.swift */; };
B16DC238258A7595004DAB1A /* SocialLoginButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC237258A7595004DAB1A /* SocialLoginButtonStyle.swift */; };
B16DC23A258A75B7004DAB1A /* SocialLoginButtonViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC239258A75B7004DAB1A /* SocialLoginButtonViewData.swift */; };
B16DC23C258A7D13004DAB1A /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC23B258A7D13004DAB1A /* ThemeService.swift */; };
B16DC23E258A8025004DAB1A /* ThemeIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC23D258A8025004DAB1A /* ThemeIdentifier.swift */; };
B17982FF2119FED2001FD722 /* GDPRConsentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */; };
B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1798301211B13B3001FD722 /* OnBoardingManager.swift */; };
B183226623F55D6B0035B2E8 /* CameraAccessManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B183226523F55D6B0035B2E8 /* CameraAccessManager.swift */; };
@ -333,6 +346,10 @@
B190F55922CE356800AEB493 /* EditHistoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B190F55822CE356800AEB493 /* EditHistoryHeaderView.swift */; };
B190F55B22CE35FD00AEB493 /* EditHistoryHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B190F55A22CE35FD00AEB493 /* EditHistoryHeaderView.xib */; };
B190F55D22CE5A9700AEB493 /* EditHistorySection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B190F55C22CE5A9600AEB493 /* EditHistorySection.swift */; };
B1945CC4258A81FB0020D8D6 /* ThemeService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC23B258A7D13004DAB1A /* ThemeService.swift */; };
B1945CC5258A81FE0020D8D6 /* ThemeIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B16DC23D258A8025004DAB1A /* ThemeIdentifier.swift */; };
B1945CC9258C04F30020D8D6 /* SSOAuthenticationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1945CC8258C04F20020D8D6 /* SSOAuthenticationPresenter.swift */; };
B1945CCB258C24500020D8D6 /* SSOAuthentificationSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1945CCA258C244F0020D8D6 /* SSOAuthentificationSession.swift */; };
B1963B2B228F1C4900CBA17F /* BubbleReactionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B25228F1C4800CBA17F /* BubbleReactionsView.swift */; };
B1963B2C228F1C4900CBA17F /* BubbleReactionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1963B26228F1C4800CBA17F /* BubbleReactionViewCell.xib */; };
B1963B2D228F1C4900CBA17F /* BubbleReactionsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1963B27228F1C4800CBA17F /* BubbleReactionsViewModel.swift */; };
@ -344,6 +361,10 @@
B197B7C6243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */; };
B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */; };
B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */; };
B1A15BD425A4BAA800BDCA36 /* CustomSchemeURLParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A15BD325A4BAA800BDCA36 /* CustomSchemeURLParser.swift */; };
B1A15BD625A526A200BDCA36 /* URLComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A15BD525A526A200BDCA36 /* URLComponents.swift */; };
B1A15BD925A6652800BDCA36 /* DeepLinkOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A15BD825A6652800BDCA36 /* DeepLinkOption.swift */; };
B1A15BDB25A6680E00BDCA36 /* SSOURLConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A15BDA25A6680E00BDCA36 /* SSOURLConstants.swift */; };
B1A5B33E227ADF2A004CBA85 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A5B33D227ADF2A004CBA85 /* UIImage.swift */; };
B1A67946257559CF00BB0C69 /* RootTabEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B11F4D2025681500009F1586 /* RootTabEmptyView.swift */; };
B1A67947257559D500BB0C69 /* RootTabEmptyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B11F4D1E256814E5009F1586 /* RootTabEmptyView.xib */; };
@ -1262,6 +1283,8 @@
B12676802523E4D100BE6B98 /* SecretsResetViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsResetViewState.swift; sourceTree = "<group>"; };
B12676812523E4D100BE6B98 /* SecretsResetViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsResetViewAction.swift; sourceTree = "<group>"; };
B12676822523E4D100BE6B98 /* SecretsResetViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsResetViewModel.swift; sourceTree = "<group>"; };
B12742B9258C46DC00731DA6 /* SSOAuthentificationSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOAuthentificationSessionProtocol.swift; sourceTree = "<group>"; };
B12742BB258C472800731DA6 /* LegacySSOAuthentificationSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySSOAuthentificationSession.swift; sourceTree = "<group>"; };
B1284E3D2535FEA6003529D7 /* SecureBackupSetupIntroViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupSetupIntroViewModelType.swift; sourceTree = "<group>"; };
B1284E3F2535FEBA003529D7 /* SecureBackupSetupIntroViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupSetupIntroViewModel.swift; sourceTree = "<group>"; };
B12C56EE2396CB5E00FAC6DE /* RoomMessageURLParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageURLParser.swift; sourceTree = "<group>"; };
@ -1289,6 +1312,9 @@
B140B4A721F8AB4600E3F5FE /* KeyBackupRecoverCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
B142317822CCFA2000FFA96A /* EditHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistoryCell.swift; sourceTree = "<group>"; };
B142317922CCFA2000FFA96A /* EditHistoryCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditHistoryCell.xib; sourceTree = "<group>"; };
B14EED18257992D600448735 /* SocialLoginListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginListView.swift; sourceTree = "<group>"; };
B14EED1A2579933100448735 /* SocialLoginListView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SocialLoginListView.xib; sourceTree = "<group>"; };
B14EED1C257D85DF00448735 /* SocialLoginButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginButton.swift; sourceTree = "<group>"; };
B14F142622144F6400FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = KeyBackupRecoverFromRecoveryKeyViewController.storyboard; sourceTree = "<group>"; };
B14F142722144F6400FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyViewModelType.swift; sourceTree = "<group>"; };
B14F142822144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift; sourceTree = "<group>"; };
@ -1406,6 +1432,14 @@
B169331020F3CAFC00746532 /* PublicRoomTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PublicRoomTableViewCell.h; sourceTree = "<group>"; };
B169331220F3CAFC00746532 /* PublicRoomsDirectoryDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PublicRoomsDirectoryDataSource.m; sourceTree = "<group>"; };
B169331320F3CAFC00746532 /* PublicRoomsDirectoryDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PublicRoomsDirectoryDataSource.h; sourceTree = "<group>"; };
B16C028C25A71CA3008CA7B1 /* CustomSchemeURLConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSchemeURLConstants.swift; sourceTree = "<group>"; };
B16D6353257D8FB0008BDC97 /* SocialLoginButtonFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginButtonFactory.swift; sourceTree = "<group>"; };
B16DC22A2587DC35004DAB1A /* SSOAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOAuthenticationService.swift; sourceTree = "<group>"; };
B16DC234258A723B004DAB1A /* SourceImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceImage.swift; sourceTree = "<group>"; };
B16DC237258A7595004DAB1A /* SocialLoginButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginButtonStyle.swift; sourceTree = "<group>"; };
B16DC239258A75B7004DAB1A /* SocialLoginButtonViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginButtonViewData.swift; sourceTree = "<group>"; };
B16DC23B258A7D13004DAB1A /* ThemeService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeService.swift; sourceTree = "<group>"; };
B16DC23D258A8025004DAB1A /* ThemeIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeIdentifier.swift; sourceTree = "<group>"; };
B17982FE2119FED2001FD722 /* GDPRConsentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GDPRConsentViewController.swift; sourceTree = "<group>"; };
B1798301211B13B3001FD722 /* OnBoardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingManager.swift; sourceTree = "<group>"; };
B183226523F55D6B0035B2E8 /* CameraAccessManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraAccessManager.swift; sourceTree = "<group>"; };
@ -1426,6 +1460,9 @@
B190F55822CE356800AEB493 /* EditHistoryHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistoryHeaderView.swift; sourceTree = "<group>"; };
B190F55A22CE35FD00AEB493 /* EditHistoryHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EditHistoryHeaderView.xib; sourceTree = "<group>"; };
B190F55C22CE5A9600AEB493 /* EditHistorySection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditHistorySection.swift; sourceTree = "<group>"; };
B1945CC6258B77520020D8D6 /* SocialLoginDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialLoginDataSource.swift; sourceTree = "<group>"; };
B1945CC8258C04F20020D8D6 /* SSOAuthenticationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOAuthenticationPresenter.swift; sourceTree = "<group>"; };
B1945CCA258C244F0020D8D6 /* SSOAuthentificationSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOAuthentificationSession.swift; sourceTree = "<group>"; };
B1963B25228F1C4800CBA17F /* BubbleReactionsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionsView.swift; sourceTree = "<group>"; };
B1963B26228F1C4800CBA17F /* BubbleReactionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BubbleReactionViewCell.xib; sourceTree = "<group>"; };
B1963B27228F1C4800CBA17F /* BubbleReactionsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleReactionsViewModel.swift; sourceTree = "<group>"; };
@ -1437,6 +1474,10 @@
B197B7C5243DE947005ABBF3 /* EncryptionTrustLevelBadgeImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionTrustLevelBadgeImageHelper.swift; sourceTree = "<group>"; };
B19EFA3821F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinatorType.swift; sourceTree = "<group>"; };
B19EFA3A21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverCoordinator.swift; sourceTree = "<group>"; };
B1A15BD325A4BAA800BDCA36 /* CustomSchemeURLParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSchemeURLParser.swift; sourceTree = "<group>"; };
B1A15BD525A526A200BDCA36 /* URLComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponents.swift; sourceTree = "<group>"; };
B1A15BD825A6652800BDCA36 /* DeepLinkOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkOption.swift; sourceTree = "<group>"; };
B1A15BDA25A6680E00BDCA36 /* SSOURLConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSOURLConstants.swift; sourceTree = "<group>"; };
B1A5B33D227ADF2A004CBA85 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
B1A6805324B7C65200E312CC /* MajorUpdateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MajorUpdateManager.swift; sourceTree = "<group>"; };
B1A68592229E807800D6C09A /* RoomBubbleCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomBubbleCellLayout.swift; sourceTree = "<group>"; };
@ -2307,9 +2348,11 @@
32242F0B21E8FBA900725742 /* Theme */ = {
isa = PBXGroup;
children = (
32242F0C21E8FBA900725742 /* ThemeService.m */,
32242F0D21E8FBA900725742 /* Theme.swift */,
32242F1121E8FBA900725742 /* ThemeService.h */,
32242F0C21E8FBA900725742 /* ThemeService.m */,
B16DC23B258A7D13004DAB1A /* ThemeService.swift */,
B16DC23D258A8025004DAB1A /* ThemeIdentifier.swift */,
32242F0D21E8FBA900725742 /* Theme.swift */,
32242F0E21E8FBA900725742 /* Themes */,
3232ABC1225B996100AD6A5C /* Themable.swift */,
);
@ -3222,6 +3265,33 @@
path = DataSources;
sourceTree = "<group>";
};
B16DC2192587AED2004DAB1A /* SSO */ = {
isa = PBXGroup;
children = (
B1945CC8258C04F20020D8D6 /* SSOAuthenticationPresenter.swift */,
B1A15BDA25A6680E00BDCA36 /* SSOURLConstants.swift */,
B16DC22A2587DC35004DAB1A /* SSOAuthenticationService.swift */,
B12742B9258C46DC00731DA6 /* SSOAuthentificationSessionProtocol.swift */,
B1945CCA258C244F0020D8D6 /* SSOAuthentificationSession.swift */,
B12742BB258C472800731DA6 /* LegacySSOAuthentificationSession.swift */,
);
path = SSO;
sourceTree = "<group>";
};
B16DC236258A7516004DAB1A /* SocialLogin */ = {
isa = PBXGroup;
children = (
B16DC237258A7595004DAB1A /* SocialLoginButtonStyle.swift */,
B16DC239258A75B7004DAB1A /* SocialLoginButtonViewData.swift */,
B14EED1C257D85DF00448735 /* SocialLoginButton.swift */,
B16D6353257D8FB0008BDC97 /* SocialLoginButtonFactory.swift */,
B1945CC6258B77520020D8D6 /* SocialLoginDataSource.swift */,
B14EED18257992D600448735 /* SocialLoginListView.swift */,
B14EED1A2579933100448735 /* SocialLoginListView.xib */,
);
path = SocialLogin;
sourceTree = "<group>";
};
B17982FD2119FEA7001FD722 /* GDPR */ = {
isa = PBXGroup;
children = (
@ -3340,6 +3410,16 @@
path = KeyVerification;
sourceTree = "<group>";
};
B1A15BD725A664F400BDCA36 /* DeepLink */ = {
isa = PBXGroup;
children = (
B1A15BD825A6652800BDCA36 /* DeepLinkOption.swift */,
B16C028C25A71CA3008CA7B1 /* CustomSchemeURLConstants.swift */,
B1A15BD325A4BAA800BDCA36 /* CustomSchemeURLParser.swift */,
);
path = DeepLink;
sourceTree = "<group>";
};
B1A6805224B7C60900E312CC /* MajorUpdate */ = {
isa = PBXGroup;
children = (
@ -3458,6 +3538,7 @@
B1A6C10523881ECB002882FD /* SlidingModal */,
32DB556722FDADE50016329E /* ServiceTerms */,
B1550FC52420E8F400CE097B /* QRCode */,
B1A15BD725A664F400BDCA36 /* DeepLink */,
B1B556CD20EE6C4C00210D55 /* Common */,
);
path = Modules;
@ -3875,6 +3956,8 @@
B1B556FA20EE6C4C00210D55 /* AuthenticationViewController.h */,
B1B556FC20EE6C4C00210D55 /* AuthenticationViewController.m */,
B1B556FB20EE6C4C00210D55 /* AuthenticationViewController.xib */,
B16DC236258A7516004DAB1A /* SocialLogin */,
B16DC2192587AED2004DAB1A /* SSO */,
B1B5579220EF575A00210D55 /* Views */,
);
path = Authentication;
@ -5199,6 +5282,7 @@
ECAE7AEA24EC223D002FA813 /* Models */ = {
isa = PBXGroup;
children = (
B16DC234258A723B004DAB1A /* SourceImage.swift */,
ECAE7AE424EC0E01002FA813 /* TableViewSections.swift */,
ECAE7AE624EC15F7002FA813 /* Section.swift */,
ECAE7AE824EC1888002FA813 /* Row.swift */,
@ -5460,6 +5544,7 @@
ECAE7AED24EFDD1F002FA813 /* MXSessionState.swift */,
ECF57A4F250913E4004BBF9D /* MXKTableViewCellWithLabelAndSwitch.swift */,
ECF57A5625091ECC004BBF9D /* MXKTableViewCellWithTextView.swift */,
B1A15BD525A526A200BDCA36 /* URLComponents.swift */,
);
path = Categories;
sourceTree = "<group>";
@ -5976,6 +6061,7 @@
B1B558DB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.xib in Resources */,
B185145D24B8C9A400EE19EA /* MajorUpdateViewController.storyboard in Resources */,
B1CE83E42422817200D07506 /* KeyVerificationVerifyBySASViewController.storyboard in Resources */,
B14EED1B2579933100448735 /* SocialLoginListView.xib in Resources */,
B1B5572B20EE6C4D00210D55 /* RoomMemberDetailsViewController.xib in Resources */,
B1D4752C21EE52C30067973F /* KeyBackupSetupIntroViewController.storyboard in Resources */,
B1B558C620EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.xib in Resources */,
@ -6166,6 +6252,8 @@
B169328420F38BE300746532 /* SegmentedViewController.m in Sources */,
32FD757824D2C9BA00BA7B37 /* Bundle.swift in Sources */,
32242F1821E8FBF800725742 /* DefaultTheme.swift in Sources */,
B1945CC4258A81FB0020D8D6 /* ThemeService.swift in Sources */,
B1945CC5258A81FE0020D8D6 /* ThemeIdentifier.swift in Sources */,
B1664BCA20F4E67600808783 /* ShareViewController.m in Sources */,
B1664BC620F4E67600808783 /* FallbackViewController.m in Sources */,
32242F1621E8FBCC00725742 /* ThemeService.m in Sources */,
@ -6249,6 +6337,7 @@
B1CE83DE2422817200D07506 /* KeyVerificationVerifyBySASViewModelType.swift in Sources */,
32FD757024D2BEF700BA7B37 /* InfoPlist.swift in Sources */,
F083BE021E7009ED00A9B29C /* AvatarGenerator.m in Sources */,
B16DC23E258A8025004DAB1A /* ThemeIdentifier.swift in Sources */,
B157FAA023264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewModelType.swift in Sources */,
B1B5573A20EE6C4D00210D55 /* GroupRoomsViewController.m in Sources */,
B1B558F920EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.m in Sources */,
@ -6260,6 +6349,7 @@
B1B4E9BF24D4703E004D5C33 /* BaseBubbleCell.swift in Sources */,
ECFBD5FD250FA59B00DD5F5A /* RoomCreationEventsModalViewController.swift in Sources */,
B16932A520F3A21C00746532 /* empty.mm in Sources */,
B1945CCB258C24500020D8D6 /* SSOAuthentificationSession.swift in Sources */,
3232AB4A2256558300AD6A5C /* FlowTemplateCoordinator.swift in Sources */,
ECF57A4825090C23004BBF9D /* EnterNewRoomDetailsCoordinatorType.swift in Sources */,
ECFBD5D7250A7AAF00DD5F5A /* RoomsDirectoryCoordinatorBridgePresenter.swift in Sources */,
@ -6341,6 +6431,7 @@
3232AB4D2256558300AD6A5C /* TemplateScreenCoordinatorType.swift in Sources */,
B1B5581720EF625800210D55 /* PreviewRoomTitleView.m in Sources */,
B1DCC63F22E9A3AE00625807 /* EmojiItem+EmojiMart.swift in Sources */,
B16DC235258A723B004DAB1A /* SourceImage.swift in Sources */,
B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */,
B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */,
EC3B066924AC6ADE000DF9BF /* CrossSigningService.swift in Sources */,
@ -6371,10 +6462,13 @@
B1B558C320EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */,
B10A3E9D24FE88CB007C380F /* RootRouterType.swift in Sources */,
B1B9DEDC22E9B7440065E677 /* SerializationServiceType.swift in Sources */,
B16DC23C258A7D13004DAB1A /* ThemeService.swift in Sources */,
B110872521F098F0003554A5 /* ActivityIndicatorPresenter.swift in Sources */,
32242F1521E8FBA900725742 /* DarkTheme.swift in Sources */,
B1A15BD625A526A200BDCA36 /* URLComponents.swift in Sources */,
B1D211E222BD193C00D939BD /* ReactionsMenuViewModel.swift in Sources */,
EC2B4EF124A1EEBD005EB739 /* DataProtectionHelper.swift in Sources */,
B16DC23A258A75B7004DAB1A /* SocialLoginButtonViewData.swift in Sources */,
B140B4A621F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift in Sources */,
B1B5577420EE702900210D55 /* WidgetViewController.m in Sources */,
B1DCC63122E7026F00625807 /* EmojiPickerHeaderView.swift in Sources */,
@ -6434,6 +6528,7 @@
B1B9DEE822EB34EF0065E677 /* ReactionHistoryCoordinatorType.swift in Sources */,
EC711B7424A63B37008F830C /* SecretsSetupRecoveryKeyViewModelType.swift in Sources */,
B14F143122144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewState.swift in Sources */,
B16C028D25A71CA3008CA7B1 /* CustomSchemeURLConstants.swift in Sources */,
32DB557F22FDADE50016329E /* ServiceTermsModalScreenCoordinator.swift in Sources */,
EC31F014251B53AD00D407DA /* RoomInfoBasicView.swift in Sources */,
EC1CA85F24C1DEC400DE9EBF /* EnterPinCodeViewModelType.swift in Sources */,
@ -6531,6 +6626,7 @@
B12C56EF2396CB5E00FAC6DE /* RoomMessageURLParser.swift in Sources */,
EC711B9924A63B37008F830C /* SecretsRecoveryCoordinator.swift in Sources */,
B1BEE73623DF44A60003A4CB /* UserVerificationSessionsStatusCoordinatorType.swift in Sources */,
B16DC238258A7595004DAB1A /* SocialLoginButtonStyle.swift in Sources */,
B1C45A86232A8C2600165425 /* SettingsIdentityServerViewModelType.swift in Sources */,
F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */,
B157FAA623264AE900EBFBD4 /* SettingsDiscoveryThreePidDetailsViewController.swift in Sources */,
@ -6543,6 +6639,7 @@
B1DCC63B22E85EF800625807 /* EmojiMartCategory.swift in Sources */,
3232AB4F2256558300AD6A5C /* TemplateScreenViewController.swift in Sources */,
B1B558FC20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */,
B14EED1D257D85E000448735 /* SocialLoginButton.swift in Sources */,
B1B5572920EE6C4D00210D55 /* RoomFilesViewController.m in Sources */,
B1BEE74B23E093260003A4CB /* UserVerificationSessionStatusViewAction.swift in Sources */,
B1098C1021ED07E4000DDA48 /* Presentable.swift in Sources */,
@ -6601,6 +6698,7 @@
B1DCC63722E8541700625807 /* EmojiStore.swift in Sources */,
3232ABA6225730E100AD6A5C /* DeviceVerificationStartViewController.swift in Sources */,
B16932EA20F3C39000746532 /* UnifiedSearchRecentsDataSource.m in Sources */,
B16D6354257D8FB0008BDC97 /* SocialLoginButtonFactory.swift in Sources */,
B10A3E9924FE86AF007C380F /* SplitViewCoordinator.swift in Sources */,
B1BEE72A23DF38B20003A4CB /* UserVerificationSessionStatusCell.swift in Sources */,
B1C45A8A232A8C2600165425 /* SettingsIdentityServerCoordinatorBridgePresenter.swift in Sources */,
@ -6692,6 +6790,7 @@
B12D7A0223E2462200FACEDC /* UserVerificationStartViewAction.swift in Sources */,
EC85D7372477DD97002C44C9 /* LocalContactsSectionHeaderContainerView.m in Sources */,
B1DCC61A22E5E17100625807 /* EmojiPickerViewController.swift in Sources */,
B1A15BD425A4BAA800BDCA36 /* CustomSchemeURLParser.swift in Sources */,
ECFBD5C9250A7AAF00DD5F5A /* RoomsDirectoryCoordinator.swift in Sources */,
B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */,
EC1CA89A24C9C9A200DE9EBF /* SetupBiometricsCoordinatorType.swift in Sources */,
@ -6701,12 +6800,14 @@
EC1CA87524C8259700DE9EBF /* KeychainStore.swift in Sources */,
B1B5575220EE6C4D00210D55 /* RoomKeyRequestViewController.m in Sources */,
32A6001A22C661100042C1D9 /* EditHistoryCoordinator.swift in Sources */,
B1A15BDB25A6680E00BDCA36 /* SSOURLConstants.swift in Sources */,
F083BD1E1E7009ED00A9B29C /* LegacyAppDelegate.m in Sources */,
B1B558E620EF768F00210D55 /* RoomIncomingAttachmentWithoutSenderInfoBubbleCell.m in Sources */,
B1B4E9BD24D4701F004D5C33 /* BubbleCellReactionsDisplayable.swift in Sources */,
329E746722CD02EA006F9797 /* BubbleReactionActionViewCell.swift in Sources */,
B1098BFB21ECFE65000DDA48 /* KeyBackupSetupCoordinatorType.swift in Sources */,
B1098BF721ECFE65000DDA48 /* PasswordStrength.swift in Sources */,
B14EED19257992D700448735 /* SocialLoginListView.swift in Sources */,
EC711B8424A63B37008F830C /* SecretsSetupRecoveryPassphraseInputMode.swift in Sources */,
B1B336BE242B933700F95EC4 /* KeyVerificationSelfVerifyStartViewState.swift in Sources */,
B1BEE73423DF44A60003A4CB /* UserVerificationSessionsStatusViewModelType.swift in Sources */,
@ -6738,6 +6839,7 @@
EC711B7F24A63B37008F830C /* SecretsSetupRecoveryPassphraseViewController.swift in Sources */,
B1B5575120EE6C4D00210D55 /* AuthenticationViewController.m in Sources */,
B1CE83BA2422815C00D07506 /* KeyVerificationService.swift in Sources */,
B16DC22B2587DC35004DAB1A /* SSOAuthenticationService.swift in Sources */,
B1B5571820EE6C4D00210D55 /* CountryPickerViewController.m in Sources */,
B1D211E622C194A200D939BD /* ReactionsMenuViewState.swift in Sources */,
ECF57A4925090C23004BBF9D /* EnterNewRoomDetailsViewState.swift in Sources */,
@ -6746,6 +6848,7 @@
ECF57A85250A64F0004BBF9D /* PlaceholderedTextView.swift in Sources */,
B1B4E9C424D47207004D5C33 /* BubbleReactionsViewModelBuilder.swift in Sources */,
EC85D72A2477DCF2002C44C9 /* KeyVerificationManuallyVerifyViewModel.swift in Sources */,
B12742BC258C472800731DA6 /* LegacySSOAuthentificationSession.swift in Sources */,
B1BEE74A23E093260003A4CB /* UserVerificationSessionStatusCoordinatorType.swift in Sources */,
3232ABA4225730E100AD6A5C /* DeviceVerificationStartViewAction.swift in Sources */,
B1550FCC2420E8F500CE097B /* QRCodeGenerator.swift in Sources */,
@ -6836,6 +6939,7 @@
ECB101322477CFDB00CF8C11 /* UIDevice.swift in Sources */,
B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */,
EC1CA86224C1DEC400DE9EBF /* EnterPinCodeViewState.swift in Sources */,
B12742BA258C46DD00731DA6 /* SSOAuthentificationSessionProtocol.swift in Sources */,
EC711B8A24A63B37008F830C /* SecretsRecoveryWithPassphraseViewController.swift in Sources */,
B1B336C3242B933700F95EC4 /* KeyVerificationSelfVerifyStartCoordinator.swift in Sources */,
B1B557CC20EF5D8000210D55 /* DirectoryServerTableViewCell.m in Sources */,
@ -6856,7 +6960,9 @@
EC85D73E2477DDD0002C44C9 /* PushNotificationService.m in Sources */,
32BF995121FA29DC00698084 /* SettingsKeyBackupViewModelType.swift in Sources */,
B190F55922CE356800AEB493 /* EditHistoryHeaderView.swift in Sources */,
B1A15BD925A6652800BDCA36 /* DeepLinkOption.swift in Sources */,
32F6B96A2270623100BBA352 /* KeyVerificationDataLoadingViewState.swift in Sources */,
B1945CC9258C04F30020D8D6 /* SSOAuthenticationPresenter.swift in Sources */,
32BF995321FA2A1300698084 /* SettingsKeyBackupViewState.swift in Sources */,
ECFBD5FB250FA59B00DD5F5A /* RoomCreationEventsModalViewAction.swift in Sources */,
B18DEDDB243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewAction.swift in Sources */,

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "social_login_button_apple.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "social_login_button_apple@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "social_login_button_apple@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "social_login_button_facebook.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "social_login_button_facebook@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "social_login_button_facebook@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "social_login_button_github.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "social_login_button_github@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "social_login_button_github@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,015 B

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "social_login_button_google.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "social_login_button_google@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "social_login_button_google@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "social_login_button_twitter.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "social_login_button_twitter@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "social_login_button_twitter@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

View file

@ -139,6 +139,16 @@
"auth_softlogout_clear_data_sign_out_msg" = "Are you sure you want to clear all data currently stored on this device? Sign in again to access your account data and messages.";
"auth_softlogout_clear_data_sign_out" = "Sign out";
// Social login
"social_login_list_title_continue" = "Continue with";
"social_login_list_title_sign_in" = "Or login with";
"social_login_list_title_sign_up" = "Or register with";
"social_login_button_title_continue" = "Continue with %@";
"social_login_button_title_sign_in" = "Sign In with %@";
"social_login_button_title_sign_up" = "Sign Up with %@";
// Errors
"error_user_already_logged_in" = "It looks like youre trying to connect to another homeserver. Do you want to sign out?";

View file

@ -57,6 +57,31 @@ extension UIImage {
return newImage
}
// Based on https://stackoverflow.com/a/31314494
@objc func vc_resized(with targetSize: CGSize) -> UIImage? {
let size = self.size
let widthRatio = targetSize.width/size.width
let heightRatio = targetSize.height/size.height
// Figure out what our orientation is, and use that to form the rectangle
let newSize: CGSize
if widthRatio > heightRatio {
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
} else {
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
let rect = CGRect(origin: .zero, size: newSize)
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
@objc func vc_notRenderedImage() -> UIImage {
if let cgImage = cgImage {
return NotRenderedImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)

View file

@ -0,0 +1,28 @@
//
// 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 Foundation
extension URLComponents {
func vc_getQueryItem(with name: String) -> URLQueryItem? {
return self.queryItems?.first(where: { $0.name == name })
}
func vc_getQueryItemValue(for name: String) -> String? {
return self.vc_getQueryItem(with: name)?.value
}
}

View file

@ -20,6 +20,11 @@ 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 socialLoginButtonApple = ImageAsset(name: "social_login_button_apple")
internal static let socialLoginButtonFacebook = ImageAsset(name: "social_login_button_facebook")
internal static let socialLoginButtonGithub = ImageAsset(name: "social_login_button_github")
internal static let socialLoginButtonGoogle = ImageAsset(name: "social_login_button_google")
internal static let socialLoginButtonTwitter = ImageAsset(name: "social_login_button_twitter")
internal static let callAudioMuteOffIcon = ImageAsset(name: "call_audio_mute_off_icon")
internal static let callAudioMuteOnIcon = ImageAsset(name: "call_audio_mute_on_icon")
internal static let callChatIcon = ImageAsset(name: "call_chat_icon")

View file

@ -4266,6 +4266,30 @@ internal enum VectorL10n {
internal static var skip: String {
return VectorL10n.tr("Vector", "skip")
}
/// Continue with %@
internal static func socialLoginButtonTitleContinue(_ p1: String) -> String {
return VectorL10n.tr("Vector", "social_login_button_title_continue", p1)
}
/// Sign In with %@
internal static func socialLoginButtonTitleSignIn(_ p1: String) -> String {
return VectorL10n.tr("Vector", "social_login_button_title_sign_in", p1)
}
/// Sign Up with %@
internal static func socialLoginButtonTitleSignUp(_ p1: String) -> String {
return VectorL10n.tr("Vector", "social_login_button_title_sign_up", p1)
}
/// Continue with
internal static var socialLoginListTitleContinue: String {
return VectorL10n.tr("Vector", "social_login_list_title_continue")
}
/// Or login with
internal static var socialLoginListTitleSignIn: String {
return VectorL10n.tr("Vector", "social_login_list_title_sign_in")
}
/// Or register with
internal static var socialLoginListTitleSignUp: String {
return VectorL10n.tr("Vector", "social_login_list_title_sign_up")
}
/// Start
internal static var start: String {
return VectorL10n.tr("Vector", "start")

View file

@ -0,0 +1,36 @@
//
// 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 Foundation
enum ThemeIdentifier: String, RawRepresentable {
case light = "default"
case dark = "dark"
case black = "black"
init?(rawValue: String) {
switch rawValue {
case "default":
self = .light
case "dark":
self = .dark
case "black":
self = .black
default:
return nil
}
}
}

View file

@ -0,0 +1,27 @@
//
// 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 Foundation
extension ThemeService {
var themeIdentifier: ThemeIdentifier? {
guard let themeId = self.themeId else {
return nil
}
return ThemeIdentifier(rawValue: themeId)
}
}

View file

@ -20,7 +20,7 @@ class BlackTheme: DarkTheme {
override init() {
super.init()
self.identifier = "black"
self.identifier = ThemeIdentifier.black.rawValue
self.backgroundColor = UIColor(rgb: 0x000000)
self.baseColor = UIColor(rgb: 0x000000)
self.headerBackgroundColor = UIColor(rgb: 0x000000)

View file

@ -21,7 +21,7 @@ import UIKit
@objcMembers
class DarkTheme: NSObject, Theme {
var identifier: String = "dark"
var identifier: String = ThemeIdentifier.dark.rawValue
var backgroundColor: UIColor = UIColor(rgb: 0x15191E)

View file

@ -21,7 +21,7 @@ import UIKit
@objcMembers
class DefaultTheme: NSObject, Theme {
var identifier: String = "default"
var identifier: String = ThemeIdentifier.light.rawValue
var backgroundColor: UIColor = UIColor(rgb: 0xFFFFFF)

View file

@ -24,6 +24,8 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
// MARK: - Constants
// MARK: - Properties
private let customSchemeURLParser: CustomSchemeURLParser
// MARK: Private
@ -48,6 +50,7 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
init(router: RootRouterType) {
self.rootRouter = router
self.customSchemeURLParser = CustomSchemeURLParser()
}
// MARK: - Public methods
@ -55,6 +58,18 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
func start() {
self.showSplitView(session: self.mainSession)
}
func open(url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// NOTE: As said in the Apple documentation be careful on security issues with Custom Scheme URL (see https://developer.apple.com/documentation/xcode/allowing_apps_and_websites_to_link_to_your_content/defining_a_custom_url_scheme_for_your_app)
do {
let deepLinkOption = try self.customSchemeURLParser.parse(url: url, options: options)
return self.handleDeepLinkOption(deepLinkOption)
} catch {
NSLog("[AppCoordinator] Custom scheme URL parsing failed with error: \(error)")
return false
}
}
// MARK: - Private methods
@ -86,6 +101,18 @@ final class AppCoordinator: NSObject, AppCoordinatorType {
// FIXME: Present an error on coordinator.toPresentable()
self.legacyAppDelegate.showError(asAlert: error)
}
private func handleDeepLinkOption(_ deepLinkOption: DeepLinkOption) -> Bool {
let canOpenLink: Bool
switch deepLinkOption {
case .connect(let loginToken, let transactionId):
canOpenLink = self.legacyAppDelegate.continueSSOLogin(withToken: loginToken, txnId: transactionId)
}
return canOpenLink
}
}
// MARK: - LegacyAppDelegateDelegate

View file

@ -18,4 +18,6 @@ import Foundation
/// `AppCoordinatorType` is a protocol describing a Coordinator that handles application navigation flow.
protocol AppCoordinatorType: Coordinator {
func open(url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool
}

View file

@ -101,6 +101,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
self.legacyAppDelegate.applicationDidReceiveMemoryWarning(application)
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
return self.appCoordinator.open(url: url, options: options)
}
// MARK: User Activity Continuation
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

View file

@ -243,6 +243,14 @@ extern NSString *const AppDelegateUniversalLinkDidChangeNotification;
*/
- (void)checkAppVersion;
#pragma mark - Authentication
/// When SSO login succeeded, when SFSafariViewController is used, continue login with success parameters.
/// @param loginToken The login token provided when SSO succeeded.
/// @param txnId transaction id generated during SSO page presentation.
/// returns YES if the SSO login can be continued.
- (BOOL)continueSSOLoginWithToken:(NSString*)loginToken txnId:(NSString*)txnId;
@end
@protocol LegacyAppDelegateDelegate <NSObject>

View file

@ -4649,4 +4649,19 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
}
}
#pragma mark - Authentication
- (BOOL)continueSSOLoginWithToken:(NSString*)loginToken txnId:(NSString*)txnId
{
AuthenticationViewController *authVC = self.masterTabBarController.authViewController;
if (!authVC)
{
NSLog(@"[AppDelegate] Fail to continue SSO login");
return NO;
}
return [authVC continueSSOLoginWithToken:loginToken txnId:txnId];
}
@end

View file

@ -55,6 +55,12 @@
- (void)showCustomHomeserver:(NSString*)homeserver andIdentityServer:(NSString*)identityServer;
/// When SSO login succeeded, when SFSafariViewController is used, continue login with success parameters.
/// @param loginToken The login token provided when SSO succeeded.
/// @param txnId transaction id generated during SSO page presentation.
/// returns YES if the SSO login can be continued.
- (BOOL)continueSSOLoginWithToken:(NSString*)loginToken txnId:(NSString*)txnId;
@end

View file

@ -27,7 +27,12 @@
#import "Riot-Swift.h"
@interface AuthenticationViewController () <AuthFallBackViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, SetPinCoordinatorBridgePresenterDelegate>
static const CGFloat kAuthInputContainerViewMinHeightConstraintConstant = 150.0;
@interface AuthenticationViewController () <AuthFallBackViewControllerDelegate, KeyVerificationCoordinatorBridgePresenterDelegate, SetPinCoordinatorBridgePresenterDelegate,
SocialLoginListViewDelegate,
SSOAuthenticationPresenterDelegate
>
{
/**
The default country code used to initialize the mobile phone number input.
@ -63,6 +68,17 @@
@property (nonatomic, strong) SetPinCoordinatorBridgePresenter *setPinCoordinatorBridgePresenter;
@property (nonatomic, strong) KeyboardAvoider *keyboardAvoider;
@property (weak, nonatomic) IBOutlet UIView *socialLoginContainerView;
@property (nonatomic, weak) SocialLoginListView *socialLoginListView;
@property (nonatomic, strong) SSOAuthenticationPresenter *ssoAuthenticationPresenter;
// Current SSO flow containing Identity Providers. Used for `socialLoginListView`
@property (nonatomic, strong) MXLoginSSOFlow *currentLoginSSOFlow;
// Current SSO transaction id used to identify and validate the SSO authentication callback
@property (nonatomic, strong) NSString *ssoCallbackTxnId;
@end
@implementation AuthenticationViewController
@ -377,8 +393,10 @@
}
}
[self updateAuthInputViewVisibility];
[self updateForgotPwdButtonVisibility];
[self updateSoftLogoutClearDataContainerVisibility];
[self updateSocialLoginViewVisibility];
}
- (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView
@ -422,6 +440,21 @@
[self refreshContentViewHeightConstraint];
}
- (void)updateAuthInputViewVisibility
{
BOOL hideAuthInputView = NO;
// Hide input view when there is only social login actions to present
if ((self.authType == MXKAuthenticationTypeLogin || self.authType == MXKAuthenticationTypeRegister)
&& self.currentLoginSSOFlow
&& !self.isAuthSessionContainsPasswordFlow)
{
hideAuthInputView = YES;
}
self.authInputsView.hidden = hideAuthInputView;
}
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled
{
super.userInteractionEnabled = userInteractionEnabled;
@ -528,6 +561,21 @@
}
}
- (BOOL)continueSSOLoginWithToken:(NSString*)loginToken txnId:(NSString*)txnId
{
// Check if transaction id is the same as expected
if (loginToken &&
txnId && self.ssoCallbackTxnId
&& [txnId isEqualToString:self.ssoCallbackTxnId])
{
[self loginWithToken:loginToken];
return YES;
}
NSLog(@"[AuthenticationVC] Fail to continue SSO login");
return NO;
}
#pragma mark - Fallback URL display
- (void)showAuthenticationFallBackView:(NSString*)fallbackPage
@ -692,7 +740,8 @@
*/
- (MXAuthenticationSession*)handleSupportedFlowsInAuthenticationSession:(MXAuthenticationSession *)authSession
{
MXLoginFlow *ssoFlow;
MXLoginSSOFlow *ssoFlow;
MXLoginFlow *passwordFlow;
NSMutableArray *supportedFlows = [NSMutableArray array];
for (MXLoginFlow *flow in authSession.flows)
@ -704,20 +753,30 @@
[supportedFlows addObject:flow];
}
// Prioritise SSO over other flows
if ([flow.type isEqualToString:kMXLoginFlowTypeSSO]
|| [flow.type isEqualToString:kMXLoginFlowTypeCAS])
if ([flow.type isEqualToString:kMXLoginFlowTypePassword])
{
passwordFlow = flow;
}
if ([flow isKindOfClass:MXLoginSSOFlow.class])
{
NSLog(@"[AuthenticationVC] handleSupportedFlowsInAuthenticationSession: Prioritise flow %@", flow.type);
ssoFlow = flow;
break;
ssoFlow = (MXLoginSSOFlow *)flow;
}
}
// Prioritise SSO over other flows
if (ssoFlow)
{
[supportedFlows removeAllObjects];
[supportedFlows addObject:ssoFlow];
// If the SSO contains Identity Providers list and password
// Display both social login and password input
if (ssoFlow.identityProviders.count && passwordFlow)
{
[supportedFlows addObject:passwordFlow];
}
}
if (supportedFlows.count != authSession.flows.count)
@ -740,7 +799,12 @@
authSession = [self handleSupportedFlowsInAuthenticationSession:authSession];
[super handleAuthenticationSession:authSession];
self.currentLoginSSOFlow = [self logginSSOFlowWithProvidersFromFlows:authSession.flows];
[self updateAuthInputViewVisibility];
[self updateSocialLoginViewVisibility];
AuthInputsView *authInputsview;
if ([self.authInputsView isKindOfClass:AuthInputsView.class])
{
@ -752,7 +816,7 @@
[self updateForgotPwdButtonVisibility];
[self updateSoftLogoutClearDataContainerVisibility];
self.submitButton.hidden = authInputsview.isSingleSignOnRequired;
self.submitButton.hidden = authInputsview.isSingleSignOnRequired || authInputsview.isHidden;
// Bind ssoButton again if self.authInputsView has changed
[authInputsview.ssoButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
@ -763,6 +827,54 @@
// That makes softLogoutClearDataContainer appear upper in the screen
[self.submitButton removeFromSuperview];
}
[self refreshContentViewHeightConstraint];
}
- (BOOL)isAuthSessionContainsPasswordFlow
{
BOOL containsPassword = NO;
if (self.authInputsView.authSession)
{
containsPassword = [self containsPasswordFlowInFlows:self.authInputsView.authSession.flows];
}
return containsPassword;
}
- (BOOL)containsPasswordFlowInFlows:(NSArray<MXLoginFlow*>*)loginFlows
{
for (MXLoginFlow *loginFlow in loginFlows)
{
if ([loginFlow.type isEqualToString:kMXLoginFlowTypePassword])
{
return YES;
}
}
return NO;
}
- (MXLoginSSOFlow*)logginSSOFlowWithProvidersFromFlows:(NSArray<MXLoginFlow*>*)loginFlows
{
MXLoginSSOFlow *ssoFlowWithProviders;
for (MXLoginFlow *loginFlow in loginFlows)
{
if ([loginFlow isKindOfClass:MXLoginSSOFlow.class])
{
MXLoginSSOFlow *ssoFlow = (MXLoginSSOFlow *)loginFlow;
if (ssoFlow.identityProviders.count)
{
ssoFlowWithProviders = ssoFlow;
break;
}
}
}
return ssoFlowWithProviders;
}
- (IBAction)onButtonPressed:(id)sender
@ -890,9 +1002,8 @@
[super onButtonPressed:self.submitButton];
}
else if (sender == ((AuthInputsView*)self.authInputsView).ssoButton)
{
// Do SSO using the fallback URL
[self showAuthenticationFallBackView];
{
[self presentDefaultSSOAuthentication];
}
else if (sender == self.softLogoutClearDataButton)
{
@ -958,7 +1069,7 @@
BOOL showForgotPasswordButton = NO;
if (BuildSettings.authScreenShowForgotPassword)
if (BuildSettings.authScreenShowForgotPassword && authInputsview.isHidden == NO)
{
showForgotPasswordButton = (self.authType == MXKAuthenticationTypeLogin) && !authInputsview.isSingleSignOnRequired;
}
@ -1054,6 +1165,17 @@
// Refresh content view height by considering the options container display.
CGFloat constant = self.optionsContainer.frame.origin.y + 10;
if (self.authInputsView.isHidden == NO)
{
self.authInputContainerViewMinHeightConstraint.constant = kAuthInputContainerViewMinHeightConstraintConstant;
self.authInputContainerViewHeightConstraint.constant = self.authInputsView.viewHeightConstraint.constant;
}
else
{
self.authInputContainerViewMinHeightConstraint.constant = 0;
self.authInputContainerViewHeightConstraint.constant = 0;
}
if (!self.optionsContainer.isHidden)
{
constant += self.serverOptionsContainer.frame.origin.y;
@ -1079,8 +1201,15 @@
// The soft logout clear data section adds more height
constant += self.softLogoutClearDataContainer.frame.size.height;
}
if (self.isSocialLoginViewShown)
{
constant += [self socialLoginViewHeightFittingWidth:self.contentView.frame.size.width];
}
self.contentViewHeightConstraint.constant = constant;
[self.view layoutIfNeeded];
}
- (void)hideCustomServers:(BOOL)hidden
@ -1148,7 +1277,7 @@
self.customServersContainer.hidden = NO;
// Refresh content view height
self.contentViewHeightConstraint.constant += self.customServersContainer.frame.size.height;
[self refreshContentViewHeightConstraint];
// Scroll to display server options
CGPoint offset = self.authenticationScrollView.contentOffset;
@ -1186,11 +1315,6 @@
// Override here the handling of the authInputsView height change.
if ([@"viewHeightConstraint.constant" isEqualToString:keyPath])
{
self.authInputContainerViewHeightConstraint.constant = self.authInputsView.viewHeightConstraint.constant;
// Force to render the view
[self.view layoutIfNeeded];
// Refresh content view height by considering the updated frame of the options container.
[self refreshContentViewHeightConstraint];
}
@ -1496,4 +1620,159 @@
self.setPinCoordinatorBridgePresenter = nil;
}
#pragma mark - Social login view management
- (BOOL)isSocialLoginViewShown
{
return self.socialLoginListView.superview
&& !self.socialLoginListView.isHidden
&& self.currentLoginSSOFlow.identityProviders.count;
}
- (CGFloat)socialLoginViewHeightFittingWidth:(CGFloat)width
{
NSArray<MXLoginSSOIdentityProvider*> *identityProviders = self.currentLoginSSOFlow.identityProviders;
if (!identityProviders.count && self.socialLoginListView)
{
return 0.0;
}
return [SocialLoginListView contentViewHeightWithIdentityProviders:identityProviders mode:self.socialLoginListView.mode fitting:self.contentView.frame.size.width];
}
- (void)showSocialLoginViewWithLoginSSOFlow:(MXLoginSSOFlow*)loginSSOFlow andMode:(SocialLoginButtonMode)mode
{
SocialLoginListView *listView = self.socialLoginListView;
if (!listView)
{
listView = [SocialLoginListView instantiate];
[self.socialLoginContainerView vc_addSubViewMatchingParent:listView];
self.socialLoginListView = listView;
listView.delegate = self;
}
[listView updateWith:loginSSOFlow.identityProviders mode:mode];
[self refreshContentViewHeightConstraint];
}
- (void)hideSocialLoginView
{
[self.socialLoginListView removeFromSuperview];
[self refreshContentViewHeightConstraint];
}
- (void)updateSocialLoginViewVisibility
{
SocialLoginButtonMode socialLoginButtonMode = SocialLoginButtonModeContinue;
BOOL showSocialLoginView = self.currentLoginSSOFlow ? YES : NO;
switch (self.authType)
{
case MXKAuthenticationTypeForgotPassword:
showSocialLoginView = NO;
break;
case MXKAuthenticationTypeRegister:
socialLoginButtonMode = SocialLoginButtonModeSignUp;
break;
case MXKAuthenticationTypeLogin:
if (((AuthInputsView*)self.authInputsView).isSingleSignOnRequired)
{
socialLoginButtonMode = SocialLoginButtonModeContinue;
}
else
{
socialLoginButtonMode = SocialLoginButtonModeSignIn;
}
break;
default:
break;
}
if (showSocialLoginView)
{
[self showSocialLoginViewWithLoginSSOFlow:self.currentLoginSSOFlow andMode:socialLoginButtonMode];
}
else
{
[self hideSocialLoginView];
}
}
#pragma mark - SocialLoginListViewDelegate
- (void)socialLoginListView:(SocialLoginListView *)socialLoginListView didTapSocialButtonWithIdentifier:(NSString *)identifier
{
[self presentSSOAuthenticationForIdentityProviderIdentifier:identifier];
}
#pragma mark - SSOIdentityProviderAuthenticationPresenter
- (void)presentSSOAuthenticationForIdentityProviderIdentifier:(NSString*)identityProviderIdentifier
{
NSString *homeServerStringURL = self.homeServerTextField.text;
if (!homeServerStringURL)
{
return;
}
SSOAuthenticationService *ssoAuthenticationService = [[SSOAuthenticationService alloc] initWithHomeserverStringURL:homeServerStringURL];
SSOAuthenticationPresenter *presenter = [[SSOAuthenticationPresenter alloc] initWithSsoAuthenticationService:ssoAuthenticationService];
presenter.delegate = self;
// Generate a unique identifier that will identify the success callback URL
NSString *transactionId = [MXTools generateTransactionId];
[presenter presentForIdentityProviderIdentifier:identityProviderIdentifier with: transactionId from:self animated:YES];
self.ssoCallbackTxnId = transactionId;
self.ssoAuthenticationPresenter = presenter;
}
- (void)presentDefaultSSOAuthentication
{
[self presentSSOAuthenticationForIdentityProviderIdentifier:nil];
}
- (void)dismissSSOAuthenticationPresenter
{
[self.ssoAuthenticationPresenter dismissWithAnimated:YES completion:nil];
self.ssoAuthenticationPresenter = nil;
}
// TODO: Move to SDK
- (void)loginWithToken:(NSString*)loginToken
{
NSDictionary *parameters = @{
@"type" : kMXLoginFlowTypeToken,
@"token": loginToken
};
[self loginWithParameters:parameters];
}
#pragma mark - SSOAuthenticationPresenterDelegate
- (void)ssoAuthenticationPresenterDidCancel:(SSOAuthenticationPresenter *)presenter
{
[self dismissSSOAuthenticationPresenter];
}
- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationDidFailWithError:(NSError *)error
{
[self dismissSSOAuthenticationPresenter];
}
- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationSucceededWithToken:(NSString *)token
{
[self dismissSSOAuthenticationPresenter];
[self loginWithToken:token];
}
@end

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina5_9" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17126"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -42,6 +42,7 @@
<outlet property="rightBarButtonItem" destination="Kwt-KN-aVL" id="Y3F-wA-tf8"/>
<outlet property="serverOptionsContainer" destination="FIn-2w-e6H" id="Z0e-NN-3LY"/>
<outlet property="skipButton" destination="wEJ-AF-rdH" id="smu-MS-2IY"/>
<outlet property="socialLoginContainerView" destination="TjK-XL-dQS" id="Rte-h1-blp"/>
<outlet property="softLogoutClearDataButton" destination="ZRO-C2-hH1" id="PDa-aM-Hso"/>
<outlet property="softLogoutClearDataContainer" destination="vX2-5Y-rQc" id="mgK-41-cnX"/>
<outlet property="softLogoutClearDataLabel" destination="QYL-Lo-tmH" id="ks9-5X-xfs"/>
@ -111,7 +112,7 @@
<color key="textColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wIH-Kd-r7q" userLabel="retryButton">
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wIH-Kd-r7q" userLabel="retryButton">
<rect key="frame" x="165" y="46.666666666666686" width="45" height="30"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCRetryButton"/>
<constraints>
@ -144,7 +145,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gg0-TE-OGb">
<rect key="frame" x="0.0" y="360" width="375" height="300"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AJ2-lJ-NUq">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AJ2-lJ-NUq">
<rect key="frame" x="19" y="33" width="122" height="30"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCForgotPasswordButton"/>
<constraints>
@ -164,7 +165,7 @@
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="UVJ-Re-xe2"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wEJ-AF-rdH">
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wEJ-AF-rdH">
<rect key="frame" x="11" y="33" width="106" height="30"/>
<color key="backgroundColor" red="0.028153735480000001" green="0.82494870580000002" blue="0.051896891280000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCSkipButton"/>
@ -181,7 +182,7 @@
<action selector="onButtonPressed:" destination="-1" eventType="touchUpInside" id="iEr-Vf-f6P"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="k3J-Eg-itz" userLabel="SubmitBtn">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="k3J-Eg-itz" userLabel="SubmitBtn">
<rect key="frame" x="258" y="33" width="106" height="30"/>
<color key="backgroundColor" red="0.028153735480000001" green="0.82494870580000002" blue="0.051896891280000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCLoginButton"/>
@ -366,7 +367,7 @@ Clear it if you're finished using this device, or want to sign in to another acc
<nil key="highlightedColor"/>
<string key="userLabel">Clear personal data Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.</string>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZRO-C2-hH1">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZRO-C2-hH1">
<rect key="frame" x="245" y="129" width="119" height="30"/>
<color key="backgroundColor" red="0.4624713659286499" green="0.81734329462051392" blue="0.47220504283905029" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
@ -416,20 +417,32 @@ Clear it if you're finished using this device, or want to sign in to another acc
<constraint firstItem="AJ2-lJ-NUq" firstAttribute="leading" secondItem="Gg0-TE-OGb" secondAttribute="leading" constant="19" id="xFm-Bs-yzw"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TjK-XL-dQS">
<rect key="frame" x="0.0" y="660" width="375" height="0.0"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" priority="750" id="mIX-QF-Tmn"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<accessibility key="accessibilityConfiguration" identifier="AuthContentView"/>
<constraints>
<constraint firstAttribute="height" constant="485" id="6v6-fz-e8o"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="width" secondItem="rhx-dD-4EJ" secondAttribute="width" id="EBX-KN-pRT"/>
<constraint firstItem="TjK-XL-dQS" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="MId-fr-A7b"/>
<constraint firstAttribute="bottom" secondItem="TjK-XL-dQS" secondAttribute="bottom" id="PKr-aT-3Qe"/>
<constraint firstItem="TjK-XL-dQS" firstAttribute="top" secondItem="Gg0-TE-OGb" secondAttribute="bottom" id="Sfy-Zn-JkS"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" priority="250" constant="384" id="UEM-Mh-0H9"/>
<constraint firstItem="xWb-IJ-v7F" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="YnP-Nk-QxR"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="top" secondItem="xWb-IJ-v7F" secondAttribute="bottom" id="aGH-m3-xL3"/>
<constraint firstAttribute="trailing" secondItem="TjK-XL-dQS" secondAttribute="trailing" id="d1j-OA-Wwm"/>
<constraint firstAttribute="trailing" secondItem="xWb-IJ-v7F" secondAttribute="trailing" id="hko-ol-XDd"/>
<constraint firstItem="xWb-IJ-v7F" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" constant="160" id="khR-Uj-OTH"/>
<constraint firstItem="d8r-TX-pwX" firstAttribute="top" secondItem="rhx-dD-4EJ" secondAttribute="top" constant="35" id="l68-Ta-YKg"/>
<constraint firstAttribute="centerX" secondItem="d8r-TX-pwX" secondAttribute="centerX" id="l6k-EH-Yb8"/>
<constraint firstItem="Gg0-TE-OGb" firstAttribute="leading" secondItem="rhx-dD-4EJ" secondAttribute="leading" id="rS3-go-zbf"/>
<constraint firstItem="TjK-XL-dQS" firstAttribute="width" secondItem="rhx-dD-4EJ" secondAttribute="width" id="zIZ-fl-i3i"/>
</constraints>
</view>
</subviews>
@ -449,7 +462,7 @@ Clear it if you're finished using this device, or want to sign in to another acc
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="q1e-Wg-6t7" userLabel="Authentication Fallback ContentView">
<rect key="frame" x="0.0" y="44" width="375" height="734"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9qj-5c-Sfb">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9qj-5c-Sfb">
<rect key="frame" x="317" y="5" width="50" height="35"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCCancelAuthFallbackButton"/>
<constraints>
@ -480,6 +493,7 @@ Clear it if you're finished using this device, or want to sign in to another acc
</constraints>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="3h8-75-kcp"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" identifier="AuthenticationVCView"/>
<constraints>
@ -502,7 +516,6 @@ Clear it if you're finished using this device, or want to sign in to another acc
<constraint firstItem="izX-ya-hXh" firstAttribute="top" secondItem="k7D-Gy-yBR" secondAttribute="bottom" id="y2h-1T-TZ4"/>
<constraint firstItem="k7D-Gy-yBR" firstAttribute="leading" secondItem="5rn-KE-plm" secondAttribute="leading" id="z6D-6L-5Ud"/>
</constraints>
<viewLayoutGuide key="safeArea" id="3h8-75-kcp"/>
<point key="canvasLocation" x="138.40000000000001" y="153.69458128078819"/>
</view>
</objects>

View file

@ -0,0 +1,59 @@
//
// 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 Foundation
import SafariServices
/// LegacySSOAuthentificationSession is session used to authenticate a user through a web service on iOS 11 and earlier. It uses SFAuthenticationSession.
final class LegacySSOAuthentificationSession: SSOAuthentificationSessionProtocol {
// MARK: - Constants
// MARK: - Properties
private var authentificationSession: SFAuthenticationSession?
// MARK: - Public
func setContextProvider(_ contextProvider: SSOAuthenticationSessionContextProviding) {
}
func authenticate(with url: URL, callbackURLScheme: String?, completionHandler: @escaping SSOAuthenticationSessionCompletionHandler) {
let authentificationSession = SFAuthenticationSession(url: url, callbackURLScheme: callbackURLScheme) { (callbackURL, error) in
var finalError: Error?
if let error = error as? SFAuthenticationError {
switch error.code {
case .canceledLogin:
finalError = SSOAuthentificationSessionError.userCanceled
default:
finalError = error
}
}
completionHandler(callbackURL, finalError)
}
self.authentificationSession = authentificationSession
authentificationSession.start()
}
func cancel() {
self.authentificationSession?.cancel()
}
}

View file

@ -0,0 +1,160 @@
//
// 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 Foundation
import SafariServices
@objc protocol SSOAuthenticationPresenterDelegate {
func ssoAuthenticationPresenterDidCancel(_ presenter: SSOAuthenticationPresenter)
func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter, authenticationDidFailWithError error: Error)
func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter, authenticationSucceededWithToken token: String)
}
enum SSOAuthenticationPresenterError: Error {
case failToLoadAuthenticationURL
}
/// SSOAuthenticationPresenter enables to present single sign-on authentication
@objcMembers
final class SSOAuthenticationPresenter: NSObject {
// MARK: - Constants
// MARK: - Properties
private let ssoAuthenticationService: SSOAuthenticationService
// MARK: Private
private weak var presentingViewController: UIViewController?
private var authenticationSession: SSOAuthentificationSessionProtocol?
private weak var safariViewController: SFSafariViewController?
// MARK: Public
weak var delegate: SSOAuthenticationPresenterDelegate?
// MARK: - Setup
init(ssoAuthenticationService: SSOAuthenticationService) {
self.ssoAuthenticationService = ssoAuthenticationService
super.init()
}
// MARK: - Public
func present(forIdentityProviderIdentifier identityProviderIdentifier: String?,
with transactionId: String,
from presentingViewController: UIViewController,
animated: Bool) {
guard let authenticationURL = self.ssoAuthenticationService.authenticationURL(for: identityProviderIdentifier, transactionId: transactionId) else {
self.delegate?.ssoAuthenticationPresenter(self, authenticationDidFailWithError: SSOAuthenticationPresenterError.failToLoadAuthenticationURL)
return
}
self.presentingViewController = presentingViewController
// SFAuthenticationSession and ASWebAuthenticationSession doesn't work with guided access (rdar://48376122)
if UIAccessibility.isGuidedAccessEnabled {
self.presentSafariViewController(with: authenticationURL, animated: animated)
} else {
self.startAuthenticationSession(with: authenticationURL)
}
}
func dismiss(animated: Bool, completion: (() -> Void)?) {
if let safariViewController = self.safariViewController {
safariViewController.dismiss(animated: animated, completion: completion)
}
self.authenticationSession?.cancel()
}
// MARK: - Private
private func presentSafariViewController(with authenticationURL: URL, animated: Bool) {
guard let presentingViewController = self.presentingViewController else {
return
}
let safariViewController = SFSafariViewController(url: authenticationURL)
safariViewController.dismissButtonStyle = .cancel
safariViewController.delegate = self
presentingViewController.present(safariViewController, animated: animated, completion: nil)
self.safariViewController = safariViewController
}
private func startAuthenticationSession(with authenticationURL: URL) {
guard let presentingViewController = self.presentingViewController else {
return
}
let authenticationSession: SSOAuthentificationSessionProtocol
if #available(iOS 12.0, *) {
authenticationSession = SSOAuthentificationSession()
} else {
authenticationSession = LegacySSOAuthentificationSession()
}
if #available(iOS 12.0, *) {
if let presentingWindow = presentingViewController.view.window {
let contextProvider = SSOAuthenticationSessionContextProvider(window: presentingWindow)
authenticationSession.setContextProvider(contextProvider)
}
}
authenticationSession.authenticate(with: authenticationURL, callbackURLScheme: self.ssoAuthenticationService.callBackURLScheme) { [weak self] (callBackURL, error) in
guard let self = self else {
return
}
if let error = error {
if case SSOAuthentificationSessionError.userCanceled = error {
self.delegate?.ssoAuthenticationPresenterDidCancel(self)
} else {
self.delegate?.ssoAuthenticationPresenter(self, authenticationDidFailWithError: error)
}
} else if let successURL = callBackURL {
if let loginToken = self.ssoAuthenticationService.loginToken(from: successURL) {
self.delegate?.ssoAuthenticationPresenter(self, authenticationSucceededWithToken: loginToken)
} else {
NSLog("SSOAuthenticationPresenter: Login token not found")
self.delegate?.ssoAuthenticationPresenter(self, authenticationDidFailWithError: SSOAuthenticationServiceError.tokenNotFound)
}
}
}
self.authenticationSession = authenticationSession
}
}
// MARK: - SFSafariViewControllerDelegate
extension SSOAuthenticationPresenter: SFSafariViewControllerDelegate {
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
self.delegate?.ssoAuthenticationPresenterDidCancel(self)
}
func safariViewController(_ controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
if !didLoadSuccessfully {
self.delegate?.ssoAuthenticationPresenter(self, authenticationDidFailWithError: SSOAuthenticationPresenterError.failToLoadAuthenticationURL)
}
}
}

View file

@ -0,0 +1,93 @@
//
// 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 Foundation
enum SSOAuthenticationServiceError: Error {
case tokenNotFound
case userCanceled
case unknown
}
@objcMembers
final class SSOAuthenticationService: NSObject {
// MARK: - Constants
// MARK: - Properties
private let homeserverStringURL: String
let callBackURLScheme: String?
// MARK: - Setup
init(homeserverStringURL: String) {
self.homeserverStringURL = homeserverStringURL
self.callBackURLScheme = BuildSettings.applicationURLScheme
super.init()
}
// MARK: - Public
func authenticationURL(for identityProvider: String?, transactionId: String) -> URL? {
guard var authenticationComponent = URLComponents(string: self.homeserverStringURL) else {
return nil
}
let ssoRedirectPath: String
if let identityProvider = identityProvider {
ssoRedirectPath = SSOURLConstants.Paths.unstableRedirect + identityProvider
} else {
ssoRedirectPath = SSOURLConstants.Paths.redirect
}
authenticationComponent.path = ssoRedirectPath
var queryItems: [URLQueryItem] = []
if let callBackURLScheme = self.buildCallBackURL(with: transactionId) {
queryItems.append(URLQueryItem(name: SSOURLConstants.Parameters.redirectURL, value: callBackURLScheme))
}
authenticationComponent.queryItems = queryItems
return authenticationComponent.url
}
func loginToken(from url: URL) -> String? {
guard let components = URLComponents(string: url.absoluteString) else {
return nil
}
return components.vc_getQueryItemValue(for: SSOURLConstants.Parameters.callbackLoginToken)
}
// MARK: - Private
private func buildCallBackURL(with transactionId: String) -> String? {
guard let callBackURLScheme = self.callBackURLScheme else {
return nil
}
var urlComponents = URLComponents()
urlComponents.scheme = callBackURLScheme
urlComponents.host = CustomSchemeURLConstants.Hosts.connect
// Transaction id is used to indentify the request
urlComponents.queryItems = [URLQueryItem(name: CustomSchemeURLConstants.Parameters.transactionId, value: transactionId)]
return urlComponents.string
}
}

View file

@ -0,0 +1,89 @@
//
// 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 Foundation
import AuthenticationServices
/// Provides context to target where in an application's UI the authorization view should be shown.
@available(iOS 12.0, *)
class SSOAuthenticationSessionContextProvider: NSObject, SSOAuthenticationSessionContextProviding, ASWebAuthenticationPresentationContextProviding {
let window: UIWindow
init(window: UIWindow) {
self.window = window
}
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return window
}
}
/// SSOAuthentificationSession is session used to authenticate a user through a web service on iOS 12+. It uses ASWebAuthenticationSession.
/// More information: https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service
@available(iOS 12.0, *)
final class SSOAuthentificationSession: SSOAuthentificationSessionProtocol {
// MARK: - Constants
// MARK: - Properties
private var authentificationSession: ASWebAuthenticationSession?
private var contextProvider: SSOAuthenticationSessionContextProviding?
// MARK: - Public
func setContextProvider(_ contextProvider: SSOAuthenticationSessionContextProviding) {
self.contextProvider = contextProvider
}
func authenticate(with url: URL, callbackURLScheme: String?, completionHandler: @escaping SSOAuthenticationSessionCompletionHandler) {
let authentificationSession = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURLScheme) { (callbackURL, error) in
var finalError: Error?
if let error = error as? ASWebAuthenticationSessionError {
switch error.code {
case .canceledLogin:
finalError = SSOAuthentificationSessionError.userCanceled
default:
finalError = error
}
}
completionHandler(callbackURL, finalError)
}
// Ask the browser for a private authentication session
if #available(iOS 13.0, *) {
authentificationSession.prefersEphemeralWebBrowserSession = true
}
self.authentificationSession = authentificationSession
if #available(iOS 13.0, *) {
if let asWebContextProvider = contextProvider as? ASWebAuthenticationPresentationContextProviding {
authentificationSession.presentationContextProvider = asWebContextProvider
}
}
authentificationSession.start()
}
func cancel() {
self.authentificationSession?.cancel()
}
}

View file

@ -0,0 +1,46 @@
//
// 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 Foundation
enum SSOAuthentificationSessionError: Error {
case userCanceled
}
/// A completion handler the session calls when it completes successfully, or when the user cancels the session.
public typealias SSOAuthenticationSessionCompletionHandler = (URL?, Error?) -> Void
/// An interface the session uses to ask a delegate for a presentation context.
protocol SSOAuthenticationSessionContextProviding {
var window: UIWindow { get }
}
/// SSOAuthentificationSessionProtocol abstract a session that an app uses to authenticate a user through a web service (SFAuthenticationSession or ASWebAuthenticationSession).
protocol SSOAuthentificationSessionProtocol {
/// Cancels the authentication session. Dismiss displayed authentication screen.
func cancel()
/// Provides context to target where in an application's UI the authorization view should be shown.
func setContextProvider(_ contextProvider: SSOAuthenticationSessionContextProviding)
/// Starts a web authentication session.
/// - Parameters:
/// - url: A URL with the http or https scheme pointing to the authentication webpage.
/// - callbackURLScheme: The custom URL scheme that the app expects in the callback URL.
/// - completionHandler: A completion handler the session calls when it completes successfully, or when the user cancels the session.
func authenticate(with url: URL, callbackURLScheme: String?, completionHandler: @escaping SSOAuthenticationSessionCompletionHandler)
}

View file

@ -0,0 +1,30 @@
//
// 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 Foundation
enum SSOURLConstants {
enum Parameters {
static let callbackLoginToken = "loginToken"
static let redirectURL = "redirectUrl"
}
enum Paths {
static let redirect = "/_matrix/client/r0/login/sso/redirect"
static let unstableRedirect = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect/"
}
}

View file

@ -0,0 +1,143 @@
//
// 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
import AFNetworking
/// SocialLoginButton represents a button associated to a social login provider.
final class SocialLoginButton: UIButton, Themable {
// MARK: - Constants
private enum Constants {
static let backgroundColorAlpha: CGFloat = 0.2
static let cornerRadius: CGFloat = 8.0
static let fontSize: CGFloat = 17.0
static let borderWidth: CGFloat = 1.0
static let imageEdgeInsetRight: CGFloat = 20.0
static let imageTargetSize = CGSize(width: 24, height: 24)
static let highlightedAlpha: CGFloat = 0.5
}
// MARK: - Properties
// MARK: Private
private var theme: Theme?
private var viewData: SocialLoginButtonViewData?
// MARK: Public
var identifier: String? {
return self.viewData?.identifier
}
// MARK: Setup
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
private func commonInit() {
self.clipsToBounds = true
self.layer.masksToBounds = true
self.layer.cornerRadius = Constants.cornerRadius
self.titleLabel?.font = UIFont.systemFont(ofSize: Constants.fontSize)
self.imageEdgeInsets.right = Constants.imageEdgeInsetRight
self.update(theme: ThemeService.shared().theme)
}
// MARK: - Public
func fill(with viewData: SocialLoginButtonViewData) {
self.viewData = viewData
self.setTitle(viewData.title, for: .normal)
self.updateWithCurrentTheme()
}
// MARK: - Private
private func updateButtonStyle(with theme: Theme) {
guard let viewData = self.viewData else {
return
}
let buttonStyle: SocialLoginButtonStyle
if let themeStyle = viewData.themeStyles[theme.identifier] {
buttonStyle = themeStyle
} else {
buttonStyle = viewData.defaultStyle
}
self.update(with: buttonStyle)
}
private func update(with buttonStyle: SocialLoginButtonStyle) {
// Image
if let sourceImage = buttonStyle.logo {
switch sourceImage {
case .local(let image):
self.setImage(image, for: .normal)
case .remote(let imageURL):
let urlRequest = URLRequest(url: imageURL)
self.setImageFor(.normal, with: urlRequest, placeholderImage: nil) { (urlRequest, httpURLResponse, image) in
let resizedImage = image.vc_resized(with: Constants.imageTargetSize)
self.setImage(resizedImage, for: .normal)
} failure: { (urlRequest, httpURLResponse, error) in
self.setImage(nil, for: .normal)
}
}
} else {
self.setImage(nil, for: .normal)
}
// Background
self.vc_setBackgroundColor(buttonStyle.backgroundColor, for: .normal)
self.layer.borderWidth = buttonStyle.borderColor != nil ? Constants.borderWidth : 0.0
self.layer.borderColor = buttonStyle.borderColor?.cgColor
// Title
self.setTitleColor(buttonStyle.titleColor, for: .normal)
self.setTitleColor(buttonStyle.titleColor.withAlphaComponent(Constants.highlightedAlpha), for: .highlighted)
}
private func updateWithCurrentTheme() {
guard let theme = self.theme else {
return
}
self.update(theme: theme)
}
// MARK: - Themable
func update(theme: Theme) {
self.theme = theme
self.updateButtonStyle(with: theme)
}
}

View file

@ -0,0 +1,236 @@
//
// 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 Foundation
@objc
enum SocialLoginButtonMode: Int {
case `continue`
case signIn
case signUp
}
/// `SocialLoginButtonFactory` builds SocialLoginButton and apply dedicated theme if needed.
class SocialLoginButtonFactory {
// MARK - Public
func build(with identityProvider: MXLoginSSOIdentityProvider, mode: SocialLoginButtonMode) -> SocialLoginButton {
let button = SocialLoginButton()
let defaultStyle: SocialLoginButtonStyle
var styles: [String: SocialLoginButtonStyle] = [:]
switch identityProvider.identifier {
case "google":
(defaultStyle, styles) = self.buildGoogleButtonStyles()
case "facebook":
(defaultStyle, styles) = self.buildFacebookButtonStyles()
case "github":
(defaultStyle, styles) = self.buildGitHubButtonStyles()
case "apple":
(defaultStyle, styles) = self.buildAppleButtonStyles()
case "twitter":
(defaultStyle, styles) = self.buildTwitterButtonStyles()
default:
let image: SourceImage?
if let imageStringURL = identityProvider.icon, let imageURL = URL(string: imageStringURL) {
image = .remote(imageURL)
} else {
image = nil
}
(defaultStyle, styles) = self.buildDefaultButtonStyles(with: image)
}
let title = self.buildButtonTitle(with: identityProvider.name, mode: mode)
let viewData = SocialLoginButtonViewData(identifier: identityProvider.identifier,
title: title,
defaultStyle: defaultStyle,
themeStyles: styles)
button.fill(with: viewData)
return button
}
// MARK - Private
private func buildButtonTitle(with providerTitle: String, mode: SocialLoginButtonMode) -> String {
let buttonTitle: String
switch mode {
case .signIn:
buttonTitle = VectorL10n.socialLoginButtonTitleSignIn(providerTitle)
case .signUp:
buttonTitle = VectorL10n.socialLoginButtonTitleSignUp(providerTitle)
case .continue:
buttonTitle = VectorL10n.socialLoginButtonTitleContinue(providerTitle)
}
return buttonTitle
}
private func buildAppleButtonStyles() -> (SocialLoginButtonStyle, [String: SocialLoginButtonStyle]) {
var lightImage: SourceImage?
let appleLogo = Asset.Images.socialLoginButtonApple.image
if let appleLogoLightStyle = appleLogo.vc_tintedImage(usingColor: .white) {
lightImage = .local(appleLogoLightStyle)
}
let lightStyle = SocialLoginButtonStyle(logo: lightImage,
titleColor: .white,
backgroundColor: .black,
borderColor: nil)
var darkImage: SourceImage?
if let appleLogoDarkStyle = appleLogo.vc_tintedImage(usingColor: .black) {
darkImage = .local(appleLogoDarkStyle)
}
let darkStyle = SocialLoginButtonStyle(logo: darkImage,
titleColor: .black,
backgroundColor: .white,
borderColor: nil)
let defaultStyle: SocialLoginButtonStyle = lightStyle
let styles: [String: SocialLoginButtonStyle] = [
ThemeIdentifier.light.rawValue: lightStyle,
ThemeIdentifier.dark.rawValue: darkStyle,
ThemeIdentifier.black.rawValue: darkStyle
]
return (defaultStyle, styles)
}
private func buildGoogleButtonStyles() -> (SocialLoginButtonStyle, [String: SocialLoginButtonStyle]) {
let logo = Asset.Images.socialLoginButtonGoogle.image
let lightImage: SourceImage = .local(logo)
let lightStyle = SocialLoginButtonStyle(logo: lightImage,
titleColor: UIColor(white: 0, alpha: 0.54),
backgroundColor: .white,
borderColor: .black)
var darkImage: SourceImage?
if let logoDarkStyle = logo.vc_tintedImage(usingColor: .white) {
darkImage = .local(logoDarkStyle)
}
let darkStyle = SocialLoginButtonStyle(logo: darkImage,
titleColor: .white,
backgroundColor: UIColor(rgb: 0x4285F4),
borderColor: nil)
let defaultStyle: SocialLoginButtonStyle = lightStyle
let styles: [String: SocialLoginButtonStyle] = [
ThemeIdentifier.light.rawValue: lightStyle,
ThemeIdentifier.dark.rawValue: darkStyle,
ThemeIdentifier.black.rawValue: darkStyle
]
return (defaultStyle, styles)
}
private func buildTwitterButtonStyles() -> (SocialLoginButtonStyle, [String: SocialLoginButtonStyle]) {
let defaultStyle = SocialLoginButtonStyle(logo: .local(Asset.Images.socialLoginButtonTwitter.image),
titleColor: .white,
backgroundColor: UIColor(rgb: 0x47ACDF),
borderColor: nil)
return (defaultStyle, [:])
}
private func buildFacebookButtonStyles() -> (SocialLoginButtonStyle, [String: SocialLoginButtonStyle]) {
let defaultStyle = SocialLoginButtonStyle(logo: .local(Asset.Images.socialLoginButtonFacebook.image),
titleColor: .white,
backgroundColor: UIColor(rgb: 0x3C5A99),
borderColor: nil)
return (defaultStyle, [:])
}
private func buildGitHubButtonStyles() -> (SocialLoginButtonStyle, [String: SocialLoginButtonStyle]) {
var lightImage: SourceImage?
let githubLogo = Asset.Images.socialLoginButtonGithub.image
if let githubLogoLightStyle = githubLogo.vc_tintedImage(usingColor: .black) {
lightImage = .local(githubLogoLightStyle)
}
let lightStyle = SocialLoginButtonStyle(logo: lightImage,
titleColor: .black,
backgroundColor: .white,
borderColor: .black)
var darkImage: SourceImage?
if let githubLogoDarkStyle = githubLogo.vc_tintedImage(usingColor: .white) {
darkImage = .local(githubLogoDarkStyle)
}
let darkStyle = SocialLoginButtonStyle(logo: darkImage,
titleColor: .white,
backgroundColor: .black,
borderColor: .white)
let defaultStyle: SocialLoginButtonStyle = lightStyle
let styles: [String: SocialLoginButtonStyle] = [
ThemeIdentifier.light.rawValue: lightStyle,
ThemeIdentifier.dark.rawValue: darkStyle,
ThemeIdentifier.black.rawValue: darkStyle
]
return (defaultStyle, styles)
}
private func buildDefaultButtonStyles(with image: SourceImage?) -> (SocialLoginButtonStyle, [String: SocialLoginButtonStyle]) {
let lightStyle = SocialLoginButtonStyle(logo: image,
titleColor: .black,
backgroundColor: .white,
borderColor: .black)
let darkStyle = SocialLoginButtonStyle(logo: image,
titleColor: .white,
backgroundColor: .black,
borderColor: .white)
let defaultStyle: SocialLoginButtonStyle = lightStyle
let styles: [String: SocialLoginButtonStyle] = [
ThemeIdentifier.light.rawValue: lightStyle,
ThemeIdentifier.dark.rawValue: darkStyle,
ThemeIdentifier.black.rawValue: darkStyle
]
return (defaultStyle, styles)
}
}

View file

@ -0,0 +1,25 @@
//
// 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 Foundation
/// SocialLoginButton style
struct SocialLoginButtonStyle {
let logo: SourceImage?
let titleColor: UIColor
let backgroundColor: UIColor
let borderColor: UIColor?
}

View file

@ -0,0 +1,33 @@
//
// 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 Foundation
/// SocialLoginButton view data
struct SocialLoginButtonViewData {
/// Identify provider identifier
let identifier: String
/// Button title
let title: String
/// Default button style
let defaultStyle: SocialLoginButtonStyle
/// Button style per theme identifier
let themeStyles: [String: SocialLoginButtonStyle]
}

View file

@ -0,0 +1,156 @@
//
// 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
import Reusable
@objc protocol SocialLoginListViewDelegate: class {
func socialLoginListView(_ socialLoginListView: SocialLoginListView, didTapSocialButtonWithIdentifier identifier: String)
}
/// SocialLoginListView displays a list of social login buttons according to a given array of SSO Identity Providers.
@objcMembers
final class SocialLoginListView: UIView, NibLoadable {
// MARK: - Constants
private static let sizingView = SocialLoginListView.instantiate()
private enum Constants {
static let buttonHeight: CGFloat = 44.0
}
// MARK: - Properties
// MARK: Outlets
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var buttonsStackView: UIStackView!
// MARK: Private
private let socialButtonFactory: SocialLoginButtonFactory = SocialLoginButtonFactory()
private var theme: Theme!
private var buttons: [SocialLoginButton] = []
private(set) var mode: SocialLoginButtonMode = .continue
// MARK: Public
weak var delegate: SocialLoginListViewDelegate?
// MARK: - Setup
static func instantiate() -> SocialLoginListView {
let view = SocialLoginListView.loadFromNib()
view.theme = ThemeService.shared().theme
return view
}
// MARK: - Public
func update(with identityProviders: [MXLoginSSOIdentityProvider], mode: SocialLoginButtonMode) {
self.mode = mode
let title: String
switch mode {
case .continue:
title = VectorL10n.socialLoginListTitleContinue
case .signIn:
title = VectorL10n.socialLoginListTitleSignIn
case .signUp:
title = VectorL10n.socialLoginListTitleSignUp
}
self.titleLabel.text = title
self.removeButtons()
let buttons = self.socialLoginButtons(for: identityProviders, mode: mode)
for button in buttons {
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: Constants.buttonHeight).isActive = true
self.buttonsStackView.addArrangedSubview(button)
}
self.buttons = buttons
}
static func contentViewHeight(identityProviders: [MXLoginSSOIdentityProvider],
mode: SocialLoginButtonMode,
fitting width: CGFloat) -> CGFloat {
let sizingView = self.sizingView
sizingView.frame = CGRect(x: 0, y: 0, width: width, height: 1)
sizingView.update(with: identityProviders, mode: mode)
sizingView.setNeedsLayout()
sizingView.layoutIfNeeded()
let fittingSize = CGSize(width: width, height: UIView.layoutFittingCompressedSize.height)
return sizingView.systemLayoutSizeFitting(fittingSize).height
}
// MARK: - Private
private func removeButtons() {
self.buttonsStackView.vc_removeAllSubviews()
self.buttons = []
}
private func socialLoginButtons(for identityProviders: [MXLoginSSOIdentityProvider], mode: SocialLoginButtonMode) -> [SocialLoginButton] {
var buttons: [SocialLoginButton] = []
// Order alphabeticaly by Identity Provider identifier
let sortedIdentityProviders = identityProviders.sorted { (firstIdentityProvider, secondIdentityProvider) -> Bool in
firstIdentityProvider.identifier < secondIdentityProvider.identifier
}
for identityProvider in sortedIdentityProviders {
let socialLoginButton = self.socialButtonFactory.build(with: identityProvider, mode: mode)
socialLoginButton.update(theme: self.theme)
socialLoginButton.addTarget(self, action: #selector(socialButtonAction(_:)), for: .touchUpInside)
buttons.append(socialLoginButton)
}
return buttons
}
// MARK: - Action
@objc private func socialButtonAction(_ socialLoginButton: SocialLoginButton) {
guard let identifier = socialLoginButton.identifier else {
return
}
self.delegate?.socialLoginListView(self, didTapSocialButtonWithIdentifier: identifier)
}
}
// MARK: - Themable
extension SocialLoginListView: Themable {
func update(theme: Theme) {
self.theme = theme
self.titleLabel.textColor = theme.textSecondaryColor
for button in self.buttons {
button.update(theme: theme)
}
}
}

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17126"/>
<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"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="SocialLoginListView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="197"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" text="Or login with" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aq0-cP-kyj">
<rect key="frame" x="20" y="20" width="374" height="39"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="39" id="3N8-38-YLI"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="S94-Mr-AzH">
<rect key="frame" x="47" y="79" width="320" height="98"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ebn-i5-Ixd">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<color key="backgroundColor" systemColor="linkColor"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="c7I-Vf-smw"/>
</constraints>
<state key="normal" title="Button" image="social_login_button_facebook"/>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7pW-XK-gE7">
<rect key="frame" x="0.0" y="54" width="320" height="44"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="LRM-mj-KV9"/>
</constraints>
<state key="normal" title="Button" image="social_login_button_github">
<color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
</button>
</subviews>
<constraints>
<constraint firstAttribute="width" priority="750" constant="320" id="oP5-q2-9cK"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="aq0-cP-kyj" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="20" id="2Vc-s3-2c3"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="S94-Mr-AzH" secondAttribute="trailing" constant="20" id="C0z-3g-kxr"/>
<constraint firstAttribute="trailing" secondItem="aq0-cP-kyj" secondAttribute="trailing" constant="20" id="EPh-iw-rBR"/>
<constraint firstAttribute="bottom" secondItem="S94-Mr-AzH" secondAttribute="bottom" constant="20" id="UlQ-fd-NAJ"/>
<constraint firstItem="aq0-cP-kyj" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" id="VOb-tL-VFc"/>
<constraint firstItem="S94-Mr-AzH" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="gtL-SP-6lp"/>
<constraint firstItem="S94-Mr-AzH" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" id="rb6-Z8-Std"/>
<constraint firstItem="S94-Mr-AzH" firstAttribute="top" secondItem="aq0-cP-kyj" secondAttribute="bottom" constant="20" id="sM8-OR-Vnq"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="buttonsStackView" destination="S94-Mr-AzH" id="1Tu-hR-HhZ"/>
<outlet property="titleLabel" destination="aq0-cP-kyj" id="tlN-w9-VKt"/>
</connections>
<point key="canvasLocation" x="137.68115942028987" y="-142.29910714285714"/>
</view>
</objects>
<resources>
<image name="social_login_button_facebook" width="24" height="24"/>
<image name="social_login_button_github" width="24" height="24"/>
<systemColor name="linkColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,23 @@
//
// 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 Foundation
/// SourceImage represents a local or remote image.
enum SourceImage {
case local(_ image: UIImage)
case remote(_ url: URL)
}

View file

@ -0,0 +1,28 @@
//
// 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 Foundation
enum CustomSchemeURLConstants {
enum Parameters {
static let transactionId = "transaction_id"
}
enum Hosts {
static let connect = "connect"
}
}

View file

@ -0,0 +1,68 @@
//
// 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 Foundation
enum CustomSchemeURLParserError: Error {
case unknownHost
case invalidParameters
case unknown
}
/// CustomSchemeURLParser enables to parse custom scheme URL
class CustomSchemeURLParser {
func parse(url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) throws -> DeepLinkOption {
guard let host = url.host else {
throw CustomSchemeURLParserError.unknownHost
}
guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
throw CustomSchemeURLParserError.unknown
}
let deepLinkOption: DeepLinkOption
switch host {
case CustomSchemeURLConstants.Hosts.connect:
if let deepLinkOpt = self.buildDeepLinkConnect(from: urlComponents) {
deepLinkOption = deepLinkOpt
} else {
throw CustomSchemeURLParserError.invalidParameters
}
default:
throw CustomSchemeURLParserError.unknownHost
}
return deepLinkOption
}
private func buildDeepLinkConnect(from urlComponents: URLComponents) -> DeepLinkOption? {
guard let queryItems = urlComponents.queryItems, queryItems.isEmpty == false else {
return nil
}
guard let loginToken = urlComponents.vc_getQueryItemValue(for: SSOURLConstants.Parameters.callbackLoginToken) else {
return nil
}
guard let txnId = urlComponents.vc_getQueryItemValue(for: CustomSchemeURLConstants.Parameters.transactionId) else {
return nil
}
return DeepLinkOption.connect(loginToken, txnId)
}
}

View file

@ -0,0 +1,24 @@
//
// 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 Foundation
/// DeepLinkOption represents deep link paths with their respective parameters
enum DeepLinkOption {
/// Used for SSO callback only when VoiceOver is enabled
case connect(_ loginToken: String, _ transactionId: String)
}

View file

@ -20,12 +20,30 @@
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>$(BASE_BUNDLE_IDENTIFIER)</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(APPLICATION_SCHEME)</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<true/>
<key>ITSEncryptionExportComplianceCode</key>
<string>d1dd539c-d21c-43e2-92e2-212c5269565c</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>http</string>
<string>https</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
@ -94,10 +112,5 @@
<string>$(BASE_BUNDLE_IDENTIFIER)</string>
<key>keychainAccessGroup</key>
<string>$(KEYCHAIN_ACCESS_GROUP)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>http</string>
<string>https</string>
</array>
</dict>
</plist>

View file

@ -2,3 +2,4 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ThemeService.h"