diff --git a/Btchap/Config/BuildSettings.swift b/Btchap/Config/BuildSettings.swift index e75c3b93d2..be8ca52768 100644 --- a/Btchap/Config/BuildSettings.swift +++ b/Btchap/Config/BuildSettings.swift @@ -123,7 +123,8 @@ final class BuildSettings: NSObject { static let applicationHelpUrlString = "https://www.beta.tchap.gouv.fr/faq" static let applicationServicesStatusUrlString = "https://status.tchap.numerique.gouv.fr/" static let applicationAcceptableUsePolicyUrlString = "" - + static let proConnectInfoUrlString = "https://proconnect.gouv.fr/" + // MARK: - Matrix permalinks // Hosts/Paths for URLs that will considered as valid permalinks. Those permalinks are opened within the app. static let permalinkSupportedHosts: [String: [String]] = [ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index c100b8d9c4..0ace1d242d 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index f8313b5403..7fc1d38e63 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index e84f9b420a..19dfa4eef5 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 3e70abf952..ca1f56baf2 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index 259a9f2c35..f9e8738014 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 0f22418f3c..ee80e4c1ea 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index f8313b5403..fb9c7c10a8 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 4ed8d1c787..7ab2061c2d 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index f196b7a48e..f8f0974215 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png index c5d7d1dcae..990ef31cf2 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png index ea9b8abdd2..bad9646cac 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index f196b7a48e..c643436c1e 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index b6714dca95..36c640caab 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png index 78f8e0f9ba..2d639a46a7 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png index 4ef08bc3ef..842999f68d 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 08a0e10cb0..28b4953416 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index c8a54a002b..26a0e8b9cf 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 8169a5880b..094cf22d12 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png index 1b4fe5bd94..4b3d0be92a 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png index 0f8081781e..93af77f660 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png index 19ea05e2f4..ae326bb563 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo.png index 258ce54114..bbe4ea952f 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo@2x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo@2x.png index 1d26c83d7c..1779a7ed21 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo@2x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo@2x.png differ diff --git a/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo@3x.png b/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo@3x.png index 0b01f38e68..11dc73da54 100644 Binary files a/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo@3x.png and b/DevTchap/Assets/DevTchapSharedImages.xcassets/TchapLogo.imageset/tchap_logo@3x.png differ diff --git a/DevTchap/Config/BuildSettings.swift b/DevTchap/Config/BuildSettings.swift index 9798d09178..5df485d5af 100644 --- a/DevTchap/Config/BuildSettings.swift +++ b/DevTchap/Config/BuildSettings.swift @@ -97,7 +97,7 @@ final class BuildSettings: NSObject { static let serverUrlPrefix = "https://matrix." static let preferredIdentityServerNames = [ "dev01.tchap.incubateur.net", - "dev02.tchap.incubateur.net" +// "dev02.tchap.incubateur.net" ] static let otherIdentityServerNames: [String] = [ "ext01.tchap.incubateur.net" @@ -123,7 +123,7 @@ final class BuildSettings: NSObject { static let applicationHelpUrlString = "https://www.tchap.incubateur.net/faq" static let applicationServicesStatusUrlString = "https://status.tchap.numerique.gouv.fr/" static let applicationAcceptableUsePolicyUrlString = "" - + static let proConnectInfoUrlString = "https://proconnect.gouv.fr/" // MARK: - Matrix permalinks // Hosts/Paths for URLs that will considered as valid permalinks. Those permalinks are opened within the app. diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index c90d47d46f..0000000000 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,104 +0,0 @@ -{ - "pins" : [ - { - "identity" : "devicekit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/devicekit/DeviceKit", - "state" : { - "revision" : "d37e70cb2646666dcf276d7d3d4a9760a41ff8a6", - "version" : "4.9.0" - } - }, - { - "identity" : "dtcoretext", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Cocoanetics/DTCoreText", - "state" : { - "revision" : "b664664825da565b4c2b7a17dbe2369f68ae43d9", - "version" : "1.6.26" - } - }, - { - "identity" : "dtfoundation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Cocoanetics/DTFoundation.git", - "state" : { - "revision" : "76062513434421cb6c8a1ae1d4f8368a7ebc2da3", - "version" : "1.7.18" - } - }, - { - "identity" : "maplibre-gl-native-distribution", - "kind" : "remoteSourceControl", - "location" : "https://github.com/maplibre/maplibre-gl-native-distribution", - "state" : { - "revision" : "d761956e81e74d8bdbfba31e0ec3a75616190658", - "version" : "5.12.2" - } - }, - { - "identity" : "matrix-analytics-events", - "kind" : "remoteSourceControl", - "location" : "https://github.com/matrix-org/matrix-analytics-events", - "state" : { - "revision" : "de0cac487e5e7f607ee17045882204c91585461f", - "version" : "0.23.1" - } - }, - { - "identity" : "matrix-rich-text-editor-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/matrix-org/matrix-rich-text-editor-swift", - "state" : { - "revision" : "21c0dd6e9c0b38d19d97af8e3e99fe01df56825d", - "version" : "2.37.3" - } - }, - { - "identity" : "ogg-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vector-im/ogg-swift.git", - "state" : { - "revision" : "9d82ed838404f10b607a1a1689f404563e9115c3", - "version" : "0.8.3" - } - }, - { - "identity" : "opus-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/vector-im/opus-swift", - "state" : { - "revision" : "11f1887767cbc87c4b64b789ee830b779cc744cb", - "version" : "0.8.4" - } - }, - { - "identity" : "posthog-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/PostHog/posthog-ios", - "state" : { - "revision" : "8b2508444962d67aa5f8770074f32d493383dafd", - "version" : "3.2.5" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", - "version" : "1.1.0" - } - }, - { - "identity" : "swift-ogg", - "kind" : "remoteSourceControl", - "location" : "https://github.com/element-hq/swift-ogg", - "state" : { - "branch" : "0.0.1", - "revision" : "e9a9e7601da662fd8b97d93781ff5c60b4becf88" - } - } - ], - "version" : 2 -} diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index b7a06b448d..dee684fb43 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -2299,7 +2299,7 @@ "authentication_verify_email_waiting_hint" = "Vous n’avez pas reçu l’e-mail ?"; /* The placeholder will show the email address that was entered. */ "authentication_verify_email_waiting_message" = "Suivez les instructions envoyées à %@"; -"authentication_verify_email_text_field_placeholder" = "Adresse mail"; // Tchap +"authentication_verify_email_text_field_placeholder" = "Adresse mail professionnelle"; // Tchap /* The placeholder will show the homeserver's domain */ "authentication_verify_email_input_message" = "%@ doit vérifier votre compte"; "authentication_verify_email_input_title" = "Entrez votre e-mail"; diff --git a/Riot/Modules/Authentication/AuthenticationCoordinator.swift b/Riot/Modules/Authentication/AuthenticationCoordinator.swift index b47c20b3bc..09277ab3ff 100644 --- a/Riot/Modules/Authentication/AuthenticationCoordinator.swift +++ b/Riot/Modules/Authentication/AuthenticationCoordinator.swift @@ -32,7 +32,9 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc enum EntryPoint { case registration - case login + // Tchap: allow override home server's preferred login mode +// case login + case login(LoginMode? = nil) } // MARK: - Properties @@ -88,9 +90,17 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc // MARK: - Public + // Tchap: allow override home server's preferred login mode func start() { + start(forcedAuthenticationMode: nil) + } + + // Tchap: allow override home server's preferred login mode + func start(forcedAuthenticationMode: LoginMode? = nil) { Task { @MainActor in - await startAuthenticationFlow() + // Tchap: allow override home server's preferred login mode +// await startAuthenticationFlow() + await startAuthenticationFlow(forcedAuthenticationMode: forcedAuthenticationMode) callback?(.didStart) authenticationService.delegate = self } @@ -114,7 +124,9 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc // MARK: - Private /// Starts the authentication flow. - @MainActor private func startAuthenticationFlow() async { + // Tchap: allow override home server's preferred login mode +// @MainActor private func startAuthenticationFlow() async { + @MainActor private func startAuthenticationFlow(forcedAuthenticationMode: LoginMode? = nil) async { if let softLogoutCredentials = authenticationService.softLogoutCredentials, let homeserverAddress = softLogoutCredentials.homeServer { do { @@ -129,7 +141,15 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc return } - let flow: AuthenticationFlow = initialScreen == .login ? .login : .register + // Tchap: allow override home server's preferred login mode + // let flow: AuthenticationFlow = initialScreen == .login ? .login : .register + let flow: AuthenticationFlow = { + if case .login(_) = initialScreen { + return .login + } else { + return .register + } + }() // Check if the user must select a server if BuildSettings.forceHomeserverSelection, authenticationService.provisioningLink?.homeserverUrl == nil { @@ -137,14 +157,15 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc return } - do { - // Start the flow (if homeserverAddress is nil, the default server will be used). - try await authenticationService.startFlow(flow) - } catch { - MXLog.error("[AuthenticationCoordinator] start: Failed to start, showing server selection.") - showServerSelectionScreen(for: flow) - return - } + // Tchap: Don't use default home server +// do { +// // Start the flow (if homeserverAddress is nil, the default server will be used). +// try await authenticationService.startFlow(flow) +// } catch { +// MXLog.error("[AuthenticationCoordinator] start: Failed to start, showing server selection.") +// showServerSelectionScreen(for: flow) +// return +// } switch initialScreen { case .registration: @@ -153,13 +174,15 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc } else { // Tchap: force email registration mode // showRegistrationScreen() - TchapShowVerifyEmailScreen() + await TchapShowVerifyEmailScreen() } case .login: if authenticationService.state.homeserver.needsLoginFallback { showFallback(for: flow) } else { - showLoginScreen() + // Tchap: allow override home server's preferred login mode +// showLoginScreen() + showLoginScreen(forcedAuthenticationMode: forcedAuthenticationMode) } } } @@ -262,13 +285,17 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc // MARK: - Login /// Shows the login screen. - @MainActor private func showLoginScreen() { + // Tchap: allow override home server's preferred login mode +// @MainActor private func showLoginScreen() { + @MainActor private func showLoginScreen(forcedAuthenticationMode: LoginMode? = nil) { MXLog.debug("[AuthenticationCoordinator] showLoginScreen") let homeserver = authenticationService.state.homeserver let parameters = AuthenticationLoginCoordinatorParameters(navigationRouter: navigationRouter, authenticationService: authenticationService, - loginMode: homeserver.preferredLoginMode) + // Tchap: allow override home server's preferred login mode + // loginMode: homeserver.preferredLoginMode) + loginMode: forcedAuthenticationMode ?? homeserver.preferredLoginMode) let coordinator = AuthenticationLoginCoordinator(parameters: parameters) coordinator.callback = { [weak self, weak coordinator] result in guard let self = self, let coordinator = coordinator else { return } @@ -383,9 +410,12 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc // Tchap: start Registration with VerifyEmail screen /// Shows the login screen. - @MainActor private func TchapShowVerifyEmailScreen() { + @MainActor private func TchapShowVerifyEmailScreen() async { MXLog.debug("[AuthenticationCoordinator] TchapShowVerifyEmailScreen") + // Call `startFlow` here to get `registrationWizard` initialized. + try? await authenticationService.startFlow(.register) + guard let registrationWizard = authenticationService.registrationWizard else { MXLog.failure("[AuthenticationCoordinator] showStage: Missing the RegistrationWizard needed to complete the stage.") displayError(message: VectorL10n.errorCommonMessage) diff --git a/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinator.swift b/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinator.swift index 2877de09d7..54f9329a0d 100644 --- a/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinator.swift +++ b/Riot/Modules/CrossSigning/Setup/CrossSigningSetupCoordinator.swift @@ -45,7 +45,11 @@ final class CrossSigningSetupCoordinator: CrossSigningSetupCoordinatorType { // MARK: - Public methods func start() { - self.showReauthentication() + // Tchap: launch classic crossiging without authentication parameters + // to trigger real requets to backend, with real keys. + // This will trigger a 401 reponse that will launch the SSO reauthentication. +// self.showReauthentication() + self.setupCrossSigning(with: [:]) } func toPresentable() -> UIViewController { @@ -71,6 +75,24 @@ final class CrossSigningSetupCoordinator: CrossSigningSetupCoordinatorType { coordinator.start() } + // Tchap: reauthenticate with session information (used by SSO reauthentication) + private func showReauthentication(with session: MXAuthenticationSession) { + + let setupCrossSigningRequest = self.crossSigningService.setupCrossSigningRequest() + + let reauthenticationParameters = ReauthenticationCoordinatorParameters(session: parameters.session, + presenter: parameters.presenter, + title: parameters.title, + message: parameters.message, + authenticationSession: session) + + let coordinator = ReauthenticationCoordinator(parameters: reauthenticationParameters) + coordinator.delegate = self + self.add(childCoordinator: coordinator) + + coordinator.start() + } + private func setupCrossSigning(with authenticationParameters: [String: Any]) { guard let crossSigning = self.parameters.session.crypto?.crossSigning else { return @@ -85,7 +107,18 @@ final class CrossSigningSetupCoordinator: CrossSigningSetupCoordinatorType { guard let self = self else { return } - self.delegate?.crossSigningSetupCoordinator(self, didFailWithError: error) + + // Tchap: handle 'authentication requested' error (401) from backend +// self.delegate?.crossSigningSetupCoordinator(self, didFailWithError: error) + let nsError = error as NSError + if let jsonResponse = nsError.userInfo[MXHTTPClientErrorResponseDataKey] as? [AnyHashable: Any], + let authenticationSession = MXAuthenticationSession(fromJSON: jsonResponse) { + self.showReauthentication(with: authenticationSession) + } + else { + self.delegate?.crossSigningSetupCoordinator(self, didFailWithError: error) + } + } } } diff --git a/Riot/Modules/LaunchLoading/LaunchLoadingView.swift b/Riot/Modules/LaunchLoading/LaunchLoadingView.swift index c4cdee4224..fe880f573c 100644 --- a/Riot/Modules/LaunchLoading/LaunchLoadingView.swift +++ b/Riot/Modules/LaunchLoading/LaunchLoadingView.swift @@ -56,14 +56,33 @@ final class LaunchLoadingView: UIView, NibLoadable, Themable { animationTimeline.play() self.animationTimeline = animationTimeline + // Tchap: setup custom loading view. + tchapSetupLoadingView() + progressContainer.isHidden = true } + // Tchap: replace Element logo loadng animation by a simple UIActivityIndicatorView. + private func tchapSetupLoadingView() { + let tchapAnimationView = UIActivityIndicatorView(style: .large) + self.addSubview(tchapAnimationView) + tchapAnimationView.translatesAutoresizingMaskIntoConstraints = false + tchapAnimationView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true + tchapAnimationView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true + tchapAnimationView.startAnimating() + + // Hide Element aninmated logo. + animationView.isHidden = true + } + // MARK: - Public func update(theme: Theme) { self.backgroundColor = theme.backgroundColor self.animationView.backgroundColor = theme.backgroundColor + + // Tchap: Set progress view to Tchap color. + progressView.progressTintColor = theme.tintColor } } diff --git a/Riot/Modules/MatrixKit/Controllers/MXKWebViewViewController.h b/Riot/Modules/MatrixKit/Controllers/MXKWebViewViewController.h index 30306e0247..8812c04b58 100644 --- a/Riot/Modules/MatrixKit/Controllers/MXKWebViewViewController.h +++ b/Riot/Modules/MatrixKit/Controllers/MXKWebViewViewController.h @@ -66,4 +66,7 @@ */ @property (nonatomic) NSString *localHTMLFile; +// Tchap: give access to backButton to allow a 'Cancel' functionnality. +- (void)setBackButton:(UIBarButtonItem *)button; + @end diff --git a/Riot/Modules/MatrixKit/Controllers/MXKWebViewViewController.m b/Riot/Modules/MatrixKit/Controllers/MXKWebViewViewController.m index 17e09796b0..9d1ed05ff1 100644 --- a/Riot/Modules/MatrixKit/Controllers/MXKWebViewViewController.m +++ b/Riot/Modules/MatrixKit/Controllers/MXKWebViewViewController.m @@ -230,6 +230,11 @@ - (void)goBack } } +// Tchap: give access to backButton to allow a 'Cancel' functionnality. +- (void)setBackButton:(UIBarButtonItem *)button { + backButton = button; +} + #pragma mark - WKNavigationDelegate - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation diff --git a/Riot/Modules/Onboarding/OnboardingCoordinator.swift b/Riot/Modules/Onboarding/OnboardingCoordinator.swift index 3fb1d711a6..0faead4112 100644 --- a/Riot/Modules/Onboarding/OnboardingCoordinator.swift +++ b/Riot/Modules/Onboarding/OnboardingCoordinator.swift @@ -107,7 +107,9 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol { } startLoading() if BuildSettings.onboardingEnableNewAuthenticationFlow { - beginAuthentication(with: .login) { [weak self] in + // Tchap: allow override home server's preferred login mode +// beginAuthentication(with: .login) { [weak self] in + beginAuthentication(with: .login()) { [weak self] in self?.stopLoading() } } else { @@ -167,9 +169,12 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol { // showUseCaseSelectionScreen() beginAuthentication(with: .registration, onStart: coordinator.stop) - case .login: + // Tchap: allow override home server's preferred login mode +// case .login: + case let .login(mode): if BuildSettings.onboardingEnableNewAuthenticationFlow { - beginAuthentication(with: .login, onStart: coordinator.stop) +// beginAuthentication(with: .login, onStart: coordinator.stop) + beginAuthentication(with: .login(mode), onStart: coordinator.stop) } else { coordinator.stop() showLegacyAuthenticationScreen() @@ -243,7 +248,15 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol { authenticationCoordinator = coordinator add(childCoordinator: coordinator) - coordinator.start() + + // Tchap: allow override home server's preferred login mode +// coordinator.start() + if case let .login(forcedAuthenticationMode) = initialScreen { + coordinator.start(forcedAuthenticationMode: forcedAuthenticationMode) + } + else { + coordinator.start() + } } /// Show the legacy authentication screen. Any parameters that have been set in previous screens are be applied. diff --git a/Riot/Modules/Reauthentication/ReauthFallBackViewController.swift b/Riot/Modules/Reauthentication/ReauthFallBackViewController.swift index 130e5a8625..808787050c 100644 --- a/Riot/Modules/Reauthentication/ReauthFallBackViewController.swift +++ b/Riot/Modules/Reauthentication/ReauthFallBackViewController.swift @@ -39,6 +39,12 @@ final class ReauthFallBackViewController: AuthFallBackViewController, Themable { self.setupNavigationBar() self.registerThemeServiceDidChangeThemeNotification() self.update(theme: self.theme) + + // Tchap: block dismissal of this Reauthentication sheet by dragging down. + // The user must use the close button. + if #available(iOS 13.0, *) { + self.isModalInPresentation = true + } } // MARK: - Public @@ -62,10 +68,19 @@ final class ReauthFallBackViewController: AuthFallBackViewController, Themable { } private func setupNavigationBar() { + // Tchap: Add 'Cancel' button the cancel the authentication process from the beginning. + // (because the 'Done' button will try to launch the authentication process with current session token + // which will retrigger the display of the Authentication window). + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.didCancel?() + } + let doneBarButtonItem = MXKBarButtonItem(title: VectorL10n.close, style: .plain) { [weak self] in self?.didValidate?() - } - self.navigationItem.leftBarButtonItem = doneBarButtonItem + } + self.navigationItem.leftBarButtonItem = cancelBarButtonItem + + self.setBackButton(doneBarButtonItem) } } diff --git a/Riot/Modules/Reauthentication/ReauthenticationCoordinator.swift b/Riot/Modules/Reauthentication/ReauthenticationCoordinator.swift index 14c7887c30..08f4449592 100644 --- a/Riot/Modules/Reauthentication/ReauthenticationCoordinator.swift +++ b/Riot/Modules/Reauthentication/ReauthenticationCoordinator.swift @@ -92,7 +92,12 @@ final class ReauthenticationCoordinator: ReauthenticationCoordinatorType { } private func start(with authenticationSession: MXAuthenticationSession) { - if self.userInteractiveAuthenticationService.hasPasswordFlow(inFlows: authenticationSession.flows) { + // Tchap: give priority to SSO reauthentication if a SSO flow is available and not completed + if self.userInteractiveAuthenticationService.tchapHasSsoFlowAvailable(authenticationSession: authenticationSession), + let authenticationFallbackURL = self.userInteractiveAuthenticationService.firstUncompletedStageAuthenticationFallbackURL(for: authenticationSession) { + self.showFallbackAuthentication(with: authenticationFallbackURL, authenticationSession: authenticationSession) + } + else if self.userInteractiveAuthenticationService.hasPasswordFlow(inFlows: authenticationSession.flows) { self.showPasswordAuthentication(with: authenticationSession) } else if let authenticationFallbackURL = self.userInteractiveAuthenticationService.firstUncompletedStageAuthenticationFallbackURL(for: authenticationSession) { @@ -142,10 +147,15 @@ final class ReauthenticationCoordinator: ReauthenticationCoordinatorType { let reauthFallbackViewController: ReauthFallBackViewController = ReauthFallBackViewController(url: authenticationURL.absoluteString) reauthFallbackViewController.title = self.parameters.title + // Tchap: move navigationController init before actions closures for the closures to capture the controller to dismiss it. + let navigationController = RiotNavigationController(rootViewController: reauthFallbackViewController) + reauthFallbackViewController.didCancel = { [weak self] in guard let self = self else { return } + // Tchap: dismiss controller + navigationController.dismiss(animated: true) self.delegate?.reauthenticationCoordinatorDidCancel(self) } @@ -160,11 +170,11 @@ final class ReauthenticationCoordinator: ReauthenticationCoordinatorType { } let authenticationParameters = self.authenticationParametersBuilder.buildOAuthParameters(with: sessionId) + // Tchap: dismiss controller + navigationController.dismiss(animated: true) self.delegate?.reauthenticationCoordinatorDidComplete(self, withAuthenticationParameters: authenticationParameters) } - let navigationController = RiotNavigationController(rootViewController: reauthFallbackViewController) - self.presentingViewController.present(navigationController, animated: true) } } diff --git a/Riot/Modules/Secrets/Reset/SecretsResetCoordinator.swift b/Riot/Modules/Secrets/Reset/SecretsResetCoordinator.swift index acf31af0df..3f1bb0eaf4 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetCoordinator.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetCoordinator.swift @@ -71,6 +71,20 @@ final class SecretsResetCoordinator: SecretsResetCoordinatorType { coordinator.start() self.add(childCoordinator: coordinator) } + + private func showAuthentication(with authenticationSession: MXAuthenticationSession) { + + let reauthenticationCoordinatorParameters = ReauthenticationCoordinatorParameters(session: self.session, + presenter: self.toPresentable(), + title: nil, + message: VectorL10n.secretsResetAuthenticationMessage, + authenticationSession: authenticationSession) + + let coordinator = ReauthenticationCoordinator(parameters: reauthenticationCoordinatorParameters) + coordinator.delegate = self + coordinator.start() + self.add(childCoordinator: coordinator) + } } // MARK: - SecretsResetViewModelCoordinatorDelegate @@ -80,6 +94,10 @@ extension SecretsResetCoordinator: SecretsResetViewModelCoordinatorDelegate { self.showAuthentication(with: request) } + func secretsResetViewModel(_ viewModel: any SecretsResetViewModelType, needsToAuthenticateWith session: MXAuthenticationSession) { + self.showAuthentication(with: session) + } + func secretsResetViewModelDidResetSecrets(_ viewModel: SecretsResetViewModelType) { self.delegate?.secretsResetCoordinatorDidResetSecrets(self) } diff --git a/Riot/Modules/Secrets/Reset/SecretsResetViewModel.swift b/Riot/Modules/Secrets/Reset/SecretsResetViewModel.swift index 92c5db8f92..52970c34cf 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetViewModel.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetViewModel.swift @@ -48,7 +48,9 @@ final class SecretsResetViewModel: SecretsResetViewModelType { case .loadData: break case .reset: - self.askAuthentication() + // Tchap: try `resetScrets` immediately to post request with the keys to get correct response from backend. +// self.askAuthentication() + self.resetSecrets(with: [:]) case .authenticationCancelled: self.authenticationCancelled() case .authenticationInfoEntered(let authParameters): @@ -92,7 +94,17 @@ final class SecretsResetViewModel: SecretsResetViewModelType { guard let self = self else { return } - self.update(viewState: .error(error)) + + // Tchap: handle 'authentication requested' error (401) from backend + let nsError = error as NSError + if let jsonResponse = nsError.userInfo[MXHTTPClientErrorResponseDataKey] as? [AnyHashable: Any], + let authenticationSession = MXAuthenticationSession(fromJSON: jsonResponse) { + // Begin authentication flow using authentication session informations returned by backend. + self.coordinatorDelegate?.secretsResetViewModel(self, needsToAuthenticateWith: authenticationSession) + } + else { + self.update(viewState: .error(error)) + } }) } diff --git a/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift b/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift index 5a5c880775..1bfdf00f84 100644 --- a/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift +++ b/Riot/Modules/Secrets/Reset/SecretsResetViewModelType.swift @@ -24,6 +24,7 @@ protocol SecretsResetViewModelViewDelegate: AnyObject { protocol SecretsResetViewModelCoordinatorDelegate: AnyObject { func secretsResetViewModel(_ viewModel: SecretsResetViewModelType, needsToAuthenticateWith request: AuthenticatedEndpointRequest) + func secretsResetViewModel(_ viewModel: SecretsResetViewModelType, needsToAuthenticateWith session: MXAuthenticationSession) func secretsResetViewModelDidResetSecrets(_ viewModel: SecretsResetViewModelType) func secretsResetViewModelDidCancel(_ viewModel: SecretsResetViewModelType) } diff --git a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountService.swift b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountService.swift index db91179c60..680d9717e7 100644 --- a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountService.swift +++ b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountService.swift @@ -93,7 +93,9 @@ enum DeactivateAccountServiceError: Error { /// - Parameter authenticationSession: The authentication session to be used. /// - Returns: A tuple containing the required authentication method along with a URL for fallback if necessary. private func handleAuthenticationSession(_ authenticationSession: MXAuthenticationSession) throws -> (DeactivateAccountAuthentication, URL?) { - guard let nextStage = uiaService.firstUncompletedFlowIdentifier(in: authenticationSession) else { + // Tchap: give priority to SSO authentication + // guard let nextStage = uiaService.firstUncompletedFlowIdentifier(in: authenticationSession) else { + guard let nextStage = uiaService.firstUncompletedFlowIdentifier(in: authenticationSession, priorityToSso: true) else { MXLog.error("[DeactivateAccountService] handleAuthenticationSession: Failed to determine the next stage.") throw DeactivateAccountServiceError.missingStage } diff --git a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m index beeff99a87..aecbbf6f7f 100644 --- a/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/Modules/Settings/DeactivateAccount/DeactivateAccountViewController.m @@ -351,9 +351,19 @@ - (void)presentFallbackForURL:(NSURL *)url { MXLogDebug(@"[DeactivateAccountViewController] Show fallback for url: %@", url) SFSafariViewController *safariViewController = [[SFSafariViewController alloc] initWithURL:url]; - safariViewController.modalPresentationStyle = UIModalPresentationFormSheet; + // Tchap: set modalPresentationStyle to 'currentContext' to enable safariViewController to be presented modaly. +// safariViewController.modalPresentationStyle = UIModalPresentationFormSheet; + safariViewController.modalPresentationStyle = UIModalPresentationCurrentContext; + safariViewController.delegate = self; + // Tchap: block dismissal of this SFSafariViewController sheet by dragging down. + // Else, the underlying activity indicator is not removed. + // The user must use the close button. + if (@available(iOS 13.0, *)) { + safariViewController.modalInPresentation = true; + } + [self presentViewController:safariViewController animated:YES completion:nil]; } diff --git a/Riot/Modules/UserInteractiveAuthentication/UserInteractiveAuthenticationService.swift b/Riot/Modules/UserInteractiveAuthentication/UserInteractiveAuthenticationService.swift index 7d7c709c54..bc86be8c14 100644 --- a/Riot/Modules/UserInteractiveAuthentication/UserInteractiveAuthenticationService.swift +++ b/Riot/Modules/UserInteractiveAuthentication/UserInteractiveAuthenticationService.swift @@ -211,7 +211,10 @@ final class UserInteractiveAuthenticationService: NSObject { /// - Parameter authenticationSession: An authentication session for a given request. /// - Returns: The fallback URL for the first uncompleted stage found. func firstUncompletedStageAuthenticationFallbackURL(for authenticationSession: MXAuthenticationSession) -> URL? { - guard let sessiondId = authenticationSession.session, let firstUncompletedStageIdentifier = self.firstUncompletedFlowIdentifier(in: authenticationSession) else { + guard let sessiondId = authenticationSession.session, + // Tchap: give priority to SSO authentication +// let firstUncompletedStageIdentifier = self.firstUncompletedFlowIdentifier(in: authenticationSession) else { + let firstUncompletedStageIdentifier = self.firstUncompletedFlowIdentifier(in: authenticationSession, priorityToSso: true) else { return nil } return self.authenticationFallbackURL(for: firstUncompletedStageIdentifier, sessionId: sessiondId) @@ -235,8 +238,10 @@ final class UserInteractiveAuthenticationService: NSObject { /// Find the first uncompleted login flow stage in a MXauthenticationSession. /// - Parameter authenticationSession: An authentication session for a given request. /// - Returns: Uncompleted login flow stage identifier. - func firstUncompletedFlowIdentifier(in authenticationSession: MXAuthenticationSession) -> String? { - + // Tchap: Add `priorityToSso` parameter +// func firstUncompletedFlowIdentifier(in authenticationSession: MXAuthenticationSession) -> String? { + func firstUncompletedFlowIdentifier(in authenticationSession: MXAuthenticationSession, priorityToSso: Bool = false) -> String? { + let completedStages = authenticationSession.completed ?? [] guard let flows = authenticationSession.flows else { @@ -256,6 +261,11 @@ final class UserInteractiveAuthenticationService: NSObject { let completedStagesSet = NSOrderedSet(array: completedStages) uncompletedStages.minus(completedStagesSet) + // Tchap + if uncompletedStages.contains(kMXLoginFlowTypeSSO) { + return kMXLoginFlowTypeSSO + } + let firstUncompletedFlowIdentifier = uncompletedStages.firstObject as? String return firstUncompletedFlowIdentifier } @@ -271,6 +281,12 @@ final class UserInteractiveAuthenticationService: NSObject { return false } + // Tchap + /// Check if an array of login flows contains "m.login.sso" flow. + func tchapHasSsoFlowAvailable(authenticationSession: MXAuthenticationSession) -> Bool { + return self.firstUncompletedFlowIdentifier(in: authenticationSession, priorityToSso: true) == kMXLoginFlowTypeSSO + } + // MARK: - Private private func isThereAKnownFlow(inFlows flows: [MXLoginFlow]) -> Bool { diff --git a/RiotNSE/BuildSettings.swift b/RiotNSE/BuildSettings.swift index 392f5b92dc..4b07a26113 100644 --- a/RiotNSE/BuildSettings.swift +++ b/RiotNSE/BuildSettings.swift @@ -137,7 +137,7 @@ final class BuildSettings: NSObject { static let applicationHelpUrlString = "https://www.tchap.gouv.fr/faq" static let applicationServicesStatusUrlString = "https://status.tchap.numerique.gouv.fr/" static let applicationAcceptableUsePolicyUrlString = "" - + static let proConnectInfoUrlString = "https://proconnect.gouv.fr/" // MARK: - Matrix permalinks // Hosts/Paths for URLs that will considered as valid permalinks. Those permalinks are opened within the app. diff --git a/RiotShareExtension/BuildSettings.swift b/RiotShareExtension/BuildSettings.swift index 392f5b92dc..4b07a26113 100644 --- a/RiotShareExtension/BuildSettings.swift +++ b/RiotShareExtension/BuildSettings.swift @@ -137,7 +137,7 @@ final class BuildSettings: NSObject { static let applicationHelpUrlString = "https://www.tchap.gouv.fr/faq" static let applicationServicesStatusUrlString = "https://status.tchap.numerique.gouv.fr/" static let applicationAcceptableUsePolicyUrlString = "" - + static let proConnectInfoUrlString = "https://proconnect.gouv.fr/" // MARK: - Matrix permalinks // Hosts/Paths for URLs that will considered as valid permalinks. Those permalinks are opened within the app. diff --git a/RiotShareExtension/target.yml b/RiotShareExtension/target.yml index 3d8224d84f..dc41105979 100644 --- a/RiotShareExtension/target.yml +++ b/RiotShareExtension/target.yml @@ -105,6 +105,8 @@ targets: # Tchap - path: ../RiotShareExtension/BuildSettings.swift - path: ../Tchap/Extensions + excludes: + - WebLinks+Tchap.swift - path: ../Tchap/Generated/Images_Riot.swift - path: ../Tchap/Generated/InfoPlist.swift - path: ../Tchap/Generated/Strings.swift diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift index 38f1939f4b..ef196346be 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationState.swift @@ -51,7 +51,10 @@ struct AuthenticationState { } /// The preferred login mode for the server - var preferredLoginMode: LoginMode = .unknown + // Tchap: force preferredLoginMode to `password` to present `username` input field. + // (can't set it to `SSO` because we don't know yet if the user's homeServer supports SSO). +// var preferredLoginMode: LoginMode = .unknown + var preferredLoginMode: LoginMode = .password /// Flag indicating whether the homeserver supports logging in via a QR code. var supportsQRLogin = false diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index 61884f9e3f..aedc325353 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -65,9 +65,22 @@ struct AuthenticationLoginViewState: BindableState { /// View state that can be bound to from SwiftUI. var bindings: AuthenticationLoginBindings + // Tchap: add loginMode (only password or sso modes are handled) + var tchapAuthenticationMode: LoginMode + /// Whether to show any SSO buttons. var showSSOButtons: Bool { - !homeserver.ssoIdentityProviders.isEmpty + // Tchap: only show sso buttons if tchapAuthenticationMode == .sso OR .ssoAndPassword +// !homeserver.ssoIdentityProviders.isEmpty + if case .sso = tchapAuthenticationMode, + !homeserver.ssoIdentityProviders.isEmpty { + return true + } + if case .ssoAndPassword = tchapAuthenticationMode, + !homeserver.ssoIdentityProviders.isEmpty { + return true + } + return false } /// `true` if the username and password are ready to be submitted. @@ -77,7 +90,21 @@ struct AuthenticationLoginViewState: BindableState { /// `true` if valid credentials have been entered and the homeserver is loaded. var canSubmit: Bool { - hasValidCredentials && !isLoading + // Tchap: handle `canSubmit` by checking email validity for concerned cases +// return hasValidCredentials && !isLoading + switch tchapAuthenticationMode { + case .password: + return !isLoading && tchapEmailIsValid + case .sso: + return !isLoading && tchapEmailIsValid + default: + return hasValidCredentials && !isLoading + } + } + + // Tchap: username is email + var tchapEmailIsValid: Bool { + MXTools.isEmailAddress(bindings.username) } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift index 29064d9582..4deed3352d 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift @@ -27,9 +27,16 @@ class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, Authentica // MARK: - Setup - init(homeserver: AuthenticationHomeserverViewData) { + // Tchap: pass `loginMode` to ViewState to correctly display login UI. +// init(homeserver: AuthenticationHomeserverViewData) { +// let bindings = AuthenticationLoginBindings() +// let viewState = AuthenticationLoginViewState(tchapLoginState: .onlyLogin, homeserver: homeserver, bindings: bindings) +// +// super.init(initialViewState: viewState) +// } + init(homeserver: AuthenticationHomeserverViewData, authenticationMode: LoginMode = .unknown) { let bindings = AuthenticationLoginBindings() - let viewState = AuthenticationLoginViewState(homeserver: homeserver, bindings: bindings) + let viewState = AuthenticationLoginViewState(homeserver: homeserver, bindings: bindings, tchapAuthenticationMode: authenticationMode) super.init(initialViewState: viewState) } diff --git a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift index 150790d46c..b1d0778411 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift @@ -90,10 +90,13 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { self.parameters = parameters let homeserver = parameters.authenticationService.state.homeserver - let viewModel = AuthenticationLoginViewModel(homeserver: homeserver.viewData) + // Tchap: pass `loginMode` to viewModel to be able to adapt the working of the login display view. + let viewModel = AuthenticationLoginViewModel(homeserver: homeserver.viewData, authenticationMode: parameters.loginMode) authenticationLoginViewModel = viewModel - let view = AuthenticationLoginScreen(viewModel: viewModel.context) + // Tchap: Use heavily customized AuthenticationLoginScreen +// let view = AuthenticationLoginScreen(viewModel: viewModel.context) + let view = TchapAuthenticationLoginScreen(viewModel: viewModel.context) authenticationLoginHostingController = VectorHostingController(rootView: view) authenticationLoginHostingController.vc_removeBackTitle() authenticationLoginHostingController.enableNavigationBarScrollEdgeAppearance = true @@ -287,35 +290,40 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { @MainActor private func showForgotPasswordScreen() { MXLog.debug("[AuthenticationLoginCoordinator] showForgotPasswordScreen") - guard let loginWizard = loginWizard else { - MXLog.failure("[AuthenticationLoginCoordinator] The login wizard was requested before getting the login flow.") - return - } - - let modalRouter = NavigationRouter() - - let parameters = AuthenticationForgotPasswordCoordinatorParameters(navigationRouter: modalRouter, - loginWizard: loginWizard, - homeserver: parameters.authenticationService.state.homeserver) - let coordinator = AuthenticationForgotPasswordCoordinator(parameters: parameters) - coordinator.callback = { [weak self, weak coordinator] result in - guard let self = self, let coordinator = coordinator else { return } - switch result { - case .success: - self.navigationRouter.dismissModule(animated: true, completion: nil) - self.successIndicator = self.indicatorPresenter.present(.success(label: VectorL10n.done)) - case .cancel: - self.navigationRouter.dismissModule(animated: true, completion: nil) + // Tchap: Call `startFlow` here to get `loginWizard` initialized. + Task { + try? await authenticationService.startFlow(.login) + + guard let loginWizard = loginWizard else { + MXLog.failure("[AuthenticationLoginCoordinator] The login wizard was requested before getting the login flow.") + return } - self.remove(childCoordinator: coordinator) + + let modalRouter = NavigationRouter() + + let parameters = AuthenticationForgotPasswordCoordinatorParameters(navigationRouter: modalRouter, + loginWizard: loginWizard, + homeserver: parameters.authenticationService.state.homeserver) + let coordinator = AuthenticationForgotPasswordCoordinator(parameters: parameters) + coordinator.callback = { [weak self, weak coordinator] result in + guard let self = self, let coordinator = coordinator else { return } + switch result { + case .success: + self.navigationRouter.dismissModule(animated: true, completion: nil) + self.successIndicator = self.indicatorPresenter.present(.success(label: VectorL10n.done)) + case .cancel: + self.navigationRouter.dismissModule(animated: true, completion: nil) + } + self.remove(childCoordinator: coordinator) + } + + coordinator.start() + add(childCoordinator: coordinator) + + modalRouter.setRootModule(coordinator) + + navigationRouter.present(modalRouter, animated: true) } - - coordinator.start() - add(childCoordinator: coordinator) - - modalRouter.setRootModule(coordinator) - - navigationRouter.present(modalRouter, animated: true) } /// Shows the QR login screen. diff --git a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift index 79b534a6a1..03798ce497 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift @@ -38,15 +38,14 @@ struct AuthenticationLoginScreen: View { .padding(.top, OnboardingMetrics.topPaddingToNavigationBar) .padding(.bottom, 28) - // Tchap: Hide server selection -// serverInfo -// .padding(.leading, 12) -// .padding(.bottom, 16) -// -// Rectangle() -// .fill(theme.colors.quinaryContent) -// .frame(height: 1) -// .padding(.bottom, 22) + serverInfo + .padding(.leading, 12) + .padding(.bottom, 16) + + Rectangle() + .fill(theme.colors.quinaryContent) + .frame(height: 1) + .padding(.bottom, 22) if viewModel.viewState.homeserver.showLoginForm { loginForm @@ -99,12 +98,10 @@ struct AuthenticationLoginScreen: View { /// The form with text fields for username and password, along with a submit button. var loginForm: some View { VStack(spacing: 14) { - // Tchap: Update placeholder and set keyboard type to email address - RoundedBorderTextField(placeHolder: VectorL10n.authenticationVerifyEmailTextFieldPlaceholder, + RoundedBorderTextField(placeHolder: VectorL10n.authenticationLoginUsername, text: $viewModel.username, isFirstResponder: false, - configuration: UIKitTextInputConfiguration(keyboardType: .emailAddress, - returnKeyType: .next, + configuration: UIKitTextInputConfiguration(returnKeyType: .next, autocapitalizationType: .none, autocorrectionType: .no), onEditingChanged: usernameEditingChanged, diff --git a/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift b/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift index ed10aa7a6d..71832dce9b 100644 --- a/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift +++ b/RiotSwiftUI/Modules/Common/ViewModel/StateStoreViewModel.swift @@ -43,7 +43,9 @@ final class ViewModelContext: ObservableOb // MARK: Public /// Get-able/Observable `Published` property for the `ViewState` - @Published fileprivate(set) var viewState: ViewState + // Tchap: set viewState settable from outside (needed for SSO Login) + // @Published fileprivate(set) var viewState: ViewState + @Published var viewState: ViewState /// Set-able/Bindable access to the bindable state. subscript(dynamicMember keyPath: WritableKeyPath) -> T { diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift index 669ac19cbc..b40a96178d 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/Coordinator/OnboardingSplashScreenCoordinator.swift @@ -42,7 +42,9 @@ final class OnboardingSplashScreenCoordinator: OnboardingSplashScreenCoordinator init() { let viewModel = OnboardingSplashScreenViewModel() - let view = OnboardingSplashScreen(viewModel: viewModel.context) + // Tchap: use Tchap heavy customized Onboarding splash screen +// let view = OnboardingSplashScreen(viewModel: viewModel.context) + let view = TchapOnboardingSplashScreen(viewModel: viewModel.context) onboardingSplashScreenViewModel = viewModel onboardingSplashScreenHostingController = VectorHostingController(rootView: view) onboardingSplashScreenHostingController.vc_removeBackTitle() diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift index 3b81de78eb..d766caf03d 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenModels.swift @@ -30,7 +30,9 @@ struct OnboardingSplashScreenPageContent { enum OnboardingSplashScreenViewModelResult { case register - case login + // Tchap: allow override home server's preferred login mode +// case login + case login(forcedAuthenticationMode: LoginMode?) } // MARK: View @@ -94,7 +96,8 @@ struct OnboardingSplashScreenBindings { enum OnboardingSplashScreenViewAction { case register - case login + // Tchap: allow override home server's preferred login mode + case login(LoginMode?) case nextPage case previousPage case hiddenPage diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift index 0d9c9c5636..3eb6ba3503 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/OnboardingSplashScreenViewModel.swift @@ -45,8 +45,11 @@ class OnboardingSplashScreenViewModel: OnboardingSplashScreenViewModelType, Onbo switch viewAction { case .register: register() - case .login: - login() + // Tchap: allow override home server's preferred login mode +// case let .login: +// login() + case let .login(forcedAuthenticationMode): + login(forcedAuthenticationMode: forcedAuthenticationMode) case .nextPage: // Wrap back round to the first page index when reaching the end. state.bindings.pageIndex = (state.bindings.pageIndex + 1) % state.content.count @@ -63,7 +66,11 @@ class OnboardingSplashScreenViewModel: OnboardingSplashScreenViewModelType, Onbo completion?(.register) } - private func login() { - completion?(.login) + // Tchap: allow override home server's preferred login mode +// private func login() { +// completion?(.login) +// } + private func login(forcedAuthenticationMode: LoginMode?) { + completion?(.login(forcedAuthenticationMode: forcedAuthenticationMode)) } } diff --git a/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift b/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift index 7bb7b80987..a26516aa24 100644 --- a/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift +++ b/RiotSwiftUI/Modules/Onboarding/SplashScreen/View/OnboardingSplashScreen.swift @@ -25,14 +25,13 @@ struct OnboardingSplashScreen: View { @Environment(\.theme) private var theme @Environment(\.layoutDirection) private var layoutDirection - // Tchap: remove carousel -// private var isLeftToRight: Bool { layoutDirection == .leftToRight } -// private var pageCount: Int { viewModel.viewState.content.count } -// -// /// A timer to automatically animate the pages. -// @State private var pageTimer: Timer? -// /// The amount of offset to apply when a drag gesture is in progress. -// @State private var dragOffset: CGFloat = .zero + private var isLeftToRight: Bool { layoutDirection == .leftToRight } + private var pageCount: Int { viewModel.viewState.content.count } + + /// A timer to automatically animate the pages. + @State private var pageTimer: Timer? + /// The amount of offset to apply when a drag gesture is in progress. + @State private var dragOffset: CGFloat = .zero // MARK: Public @@ -44,41 +43,28 @@ struct OnboardingSplashScreen: View { Spacer() .frame(height: OnboardingMetrics.spacerHeight(in: geometry)) - // Tchap: remove carousel -// // The main content of the carousel -// HStack(alignment: .top, spacing: 0) { -// // Add a hidden page at the start of the carousel duplicating the content of the last page -// OnboardingSplashScreenPage(content: viewModel.viewState.content[pageCount - 1]) -// .frame(width: geometry.size.width) -// -// ForEach(0..