From 513720ec8df491ee6b51f72d86537162c8552b79 Mon Sep 17 00:00:00 2001 From: Frank Rotermund Date: Wed, 30 Nov 2022 18:10:20 +0100 Subject: [PATCH] MESSENGER-3707 new login flow and welcome integration --- Config/BWIBuildSettings.swift | 5 +- .../BuM-Beta/BWIBuildSettings+BuM-Beta.swift | 1 - Riot/Assets/de.lproj/Bwi.strings | 3 + Riot/Assets/en.lproj/Bwi.strings | 22 ++++++++ Riot/Generated/BWIStrings.swift | 56 +++++++++++++++++++ Riot/Generated/Images.swift | 5 ++ .../Onboarding/OnboardingCoordinator.swift | 23 ++++++-- .../AuthenticationServerInfoSection.swift | 26 +++++---- .../MatrixSDK/AuthenticationService.swift | 6 ++ .../View/AuthenticationLoginScreen.swift | 16 +++--- .../AuthenticationServerSelectionModels.swift | 2 +- ...enticationServerSelectionCoordinator.swift | 11 ++++ .../AuthenticationServerSelectionScreen.swift | 2 +- .../Onboarding/Common/OnboardingIcon.swift | 4 +- .../WelcomeExperienceView.swift | 31 +++++----- 15 files changed, 172 insertions(+), 41 deletions(-) diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index 8e5958b59..f95788173 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -495,7 +495,7 @@ class BWIBuildSettings: NSObject { // MARK: - Message Bubbles bwi show in our menu and only for beta builds - var bwiShowMessageBubbleSettings = false + var bwiShowMessageBubbleSettings = true // bwi disable encrypted option in message context menu var roomContextualMenuShowEncryptionOption = false @@ -507,4 +507,7 @@ class BWIBuildSettings: NSObject { var showSideMenuCoachMessage = false var roomMembersAllowUserVerification = false + + // one flag for all layout changes to the element login flow + var bumLoginFlowLayout = true } diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index e57f58afd..bad0647c2 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -26,7 +26,6 @@ extension BWIBuildSettings { bwiPersonalState = true bwiEnableErrorTracking = true bwiMatomoTrackingDefaultState = false - bwiShowMessageBubbles = true showSessionManager = true locationSharingEnabled = false bwiLocationShareButtonVisible = false diff --git a/Riot/Assets/de.lproj/Bwi.strings b/Riot/Assets/de.lproj/Bwi.strings index 091ddfb88..6e612d12e 100644 --- a/Riot/Assets/de.lproj/Bwi.strings +++ b/Riot/Assets/de.lproj/Bwi.strings @@ -540,3 +540,6 @@ "welcome_experience_start" = "Loslegen"; +// MARK - new login flow +"authentication_server_selection_login_title" = "Willkommen!"; +"authentication_login_username" = "Nutzername"; diff --git a/Riot/Assets/en.lproj/Bwi.strings b/Riot/Assets/en.lproj/Bwi.strings index 6f4af4822..c245e9712 100644 --- a/Riot/Assets/en.lproj/Bwi.strings +++ b/Riot/Assets/en.lproj/Bwi.strings @@ -389,3 +389,25 @@ "poll_edit_form_poll_type" = "Poll type"; "poll_edit_form_poll_type_closed" = "Hidden Poll"; "poll_edit_form_poll_type_open" = "Open poll"; + +// MARK: - Welcome Experience +"welcome_experience_title1" = "Willkommen beim BundesMessenger"; +"welcome_experience_description1" = "Aus Deutschland, für Deutschland: Der souveräne Messenger für die besonderen Anforderungen des öffentlichen Sektors."; + +"welcome_experience_title2" = "Sicher und von überall"; +"welcome_experience_description2" = "Von überall auf die eigenen Chats zugreifen und nahtlos zwischen Smartphone, Tablet und Computer wechseln. Und: Alle Daten sind Ende-zu-Ende verschlüsselt auf eigenen Servern gespeichert."; + +"welcome_experience_title3" = "Einfach vernetzen"; +"welcome_experience_description3" = "Kolleg*innen aus der gesamten Organisation sofort finden, und zwar ohne erst nach Kontaktdaten suchen zu müssen."; + +"welcome_experience_title4" = "Chatten wie gewohnt"; +"welcome_experience_description4" = "Text, Bilder, Videos, Dokumente, Standorte oder Sprachnachrichten versenden und vieles mehr – an einzelne Personen oder in Gruppen. Und auf Wunsch auch über die Organisationsgrenze hinweg."; + +"welcome_experience_title5" = "Gleich loslegen?"; +"welcome_experience_description5" = "Ob deine Organisation den BundesMessenger schon eingeführt hat, findest du auf der nächsten Seite heraus. Falls nicht: Überzeuge deinen Admin doch einfach!"; + +"welcome_experience_start" = "Loslegen"; + +// MARK: - new login flow +"authentication_server_selection_login_title" = "Welcome!"; +"authentication_login_username" = "Username"; diff --git a/Riot/Generated/BWIStrings.swift b/Riot/Generated/BWIStrings.swift index 434fe6840..c23697477 100644 --- a/Riot/Generated/BWIStrings.swift +++ b/Riot/Generated/BWIStrings.swift @@ -27,6 +27,14 @@ public class BWIL10n: NSObject { public static var authUserIdPlaceholder: String { return BWIL10n.tr("Bwi", "auth_user_id_placeholder") } + /// Nutzername + public static var authenticationLoginUsername: String { + return BWIL10n.tr("Bwi", "authentication_login_username") + } + /// Willkommen! + public static var authenticationServerSelectionLoginTitle: String { + return BWIL10n.tr("Bwi", "authentication_server_selection_login_title") + } /// PIN eingeben public static var biometricsModeCantUnlockButtonTitle: String { return BWIL10n.tr("Bwi", "biometrics_mode_cant_unlock_button_title") @@ -771,6 +779,10 @@ public class BWIL10n: NSObject { public static var locationSharingCopyrightLabel: String { return BWIL10n.tr("Bwi", "location_sharing_copyright_label") } + /// Mapbox Maps SDK für iOS + public static var locationSharingMapCreditsTitle: String { + return BWIL10n.tr("Bwi", "location_sharing_map_credits_title") + } /// Diagnosedaten senden public static var matomoSettingsSendDiagnosticData: String { return BWIL10n.tr("Bwi", "MATOMO_SETTINGS_SEND_DIAGNOSTIC_DATA") @@ -1503,6 +1515,50 @@ public class BWIL10n: NSObject { public static var warning: String { return BWIL10n.tr("Bwi", "warning") } + /// Aus Deutschland, für Deutschland: Der souveräne Messenger für die besonderen Anforderungen des öffentlichen Sektors. + public static var welcomeExperienceDescription1: String { + return BWIL10n.tr("Bwi", "welcome_experience_description1") + } + /// Von überall auf die eigenen Chats zugreifen und nahtlos zwischen Smartphone, Tablet und Computer wechseln. Und: Alle Daten sind Ende-zu-Ende verschlüsselt auf eigenen Servern gespeichert. + public static var welcomeExperienceDescription2: String { + return BWIL10n.tr("Bwi", "welcome_experience_description2") + } + /// Kolleg*innen aus der gesamten Organisation sofort finden, und zwar ohne erst nach Kontaktdaten suchen zu müssen. + public static var welcomeExperienceDescription3: String { + return BWIL10n.tr("Bwi", "welcome_experience_description3") + } + /// Text, Bilder, Videos, Dokumente, Standorte oder Sprachnachrichten versenden und vieles mehr – an einzelne Personen oder in Gruppen. Und auf Wunsch auch über die Organisationsgrenze hinweg. + public static var welcomeExperienceDescription4: String { + return BWIL10n.tr("Bwi", "welcome_experience_description4") + } + /// Ob deine Organisation den BundesMessenger schon eingeführt hat, findest du auf der nächsten Seite heraus. Falls nicht: Überzeuge deinen Admin doch einfach! + public static var welcomeExperienceDescription5: String { + return BWIL10n.tr("Bwi", "welcome_experience_description5") + } + /// Loslegen + public static var welcomeExperienceStart: String { + return BWIL10n.tr("Bwi", "welcome_experience_start") + } + /// Willkommen beim BundesMessenger + public static var welcomeExperienceTitle1: String { + return BWIL10n.tr("Bwi", "welcome_experience_title1") + } + /// Sicher und von überall + public static var welcomeExperienceTitle2: String { + return BWIL10n.tr("Bwi", "welcome_experience_title2") + } + /// Einfach vernetzen + public static var welcomeExperienceTitle3: String { + return BWIL10n.tr("Bwi", "welcome_experience_title3") + } + /// Chatten wie gewohnt + public static var welcomeExperienceTitle4: String { + return BWIL10n.tr("Bwi", "welcome_experience_title4") + } + /// Gleich loslegen? + public static var welcomeExperienceTitle5: String { + return BWIL10n.tr("Bwi", "welcome_experience_title5") + } } // swiftlint:enable function_parameter_count identifier_name line_length type_body_length diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 8105eacb3..8495741c7 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -50,6 +50,11 @@ internal class Asset: NSObject { internal static let birthdayCake = ImageAsset(name: "birthday_cake") internal static let fileAttachmentIcon = ImageAsset(name: "file_attachment_icon") internal static let fileScanInfected = ImageAsset(name: "file_scan_infected") + internal static let welcomeExperience1 = ImageAsset(name: "welcome_experience_1") + internal static let welcomeExperience2 = ImageAsset(name: "welcome_experience_2") + internal static let welcomeExperience3 = ImageAsset(name: "welcome_experience_3") + internal static let welcomeExperience4 = ImageAsset(name: "welcome_experience_4") + internal static let welcomeExperience5 = ImageAsset(name: "welcome_experience_5") 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 callAudioRouteBuiltin = ImageAsset(name: "call_audio_route_builtin") diff --git a/Riot/Modules/Onboarding/OnboardingCoordinator.swift b/Riot/Modules/Onboarding/OnboardingCoordinator.swift index 5f695e75a..bc1696653 100644 --- a/Riot/Modules/Onboarding/OnboardingCoordinator.swift +++ b/Riot/Modules/Onboarding/OnboardingCoordinator.swift @@ -160,16 +160,29 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol { /// Displays the next view in the flow after the splash screen. private func bwiSplashScreenCoordinator(_ coordinator: OnboardingBwiSplashScreenCoordinator, didCompleteWith result: OnboardingBwiSplashScreenViewModelResult) { - startLoading() - if BWIBuildSettings.shared.onboardingEnableNewAuthenticationFlow { - beginAuthentication(with: .login) { [weak self] in - self?.stopLoading() - } + + if BWIBuildSettings.shared.onboardingEnableNewAuthenticationFlow { + self.showWelcomeExperience() } else { showLegacyAuthenticationScreen(forceAsRootModule: true) } } + private func showWelcomeExperience() { + let welcomeVC = WelcomeExperienceViewController.makeViewController(completion: { [weak self] in + if BWIBuildSettings.shared.onboardingEnableNewAuthenticationFlow { + self?.startLoading() + self?.beginAuthentication(with: .login) { [weak self] in + self?.stopLoading() + } + } else { + self?.showLegacyAuthenticationScreen(forceAsRootModule: true) + } + }) + + self.navigationRouter.setRootModule(welcomeVC) + } + /// Show an empty screen when configuring soft logout flow private func showEmptyScreen() { MXLog.debug("[OnboardingCoordinator] showEmptyScreen") diff --git a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift index ea62c52c6..bdaebb945 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/AuthenticationServerInfoSection.swift @@ -37,23 +37,29 @@ struct AuthenticationServerInfoSection: View { var body: some View { VStack(alignment: .leading, spacing: 4) { - Text(title) - .font(theme.fonts.subheadline) - .foregroundColor(theme.colors.secondaryContent) - + if !BWIBuildSettings.shared.bumLoginFlowLayout { + Text(title) + .font(theme.fonts.subheadline) + .foregroundColor(theme.colors.secondaryContent) + } + HStack { + Text(address) .font(theme.fonts.body) .foregroundColor(theme.colors.primaryContent) Spacer() - Button(action: editAction) { - Text(VectorL10n.edit) - .font(theme.fonts.body) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .overlay(RoundedRectangle(cornerRadius: 8).stroke(theme.colors.accent)) + if !BWIBuildSettings.shared.bumLoginFlowLayout { + + Button(action: editAction) { + Text(VectorL10n.edit) + .font(theme.fonts.body) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .overlay(RoundedRectangle(cornerRadius: 8).stroke(theme.colors.accent)) + } } } } diff --git a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift index c6071ed05..ec20615ca 100644 --- a/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift +++ b/RiotSwiftUI/Modules/Authentication/Common/Service/MatrixSDK/AuthenticationService.swift @@ -220,6 +220,12 @@ class AuthenticationService: NSObject { /// - Parameter homeserverAddress: The homeserver string entered by the user. /// - Returns: A tuple containing the REST client for the server along with the homeserver state containing the login flows. private func loginFlow(for homeserverAddress: String) async throws -> (AuthenticationRestClient, AuthenticationState.Homeserver) { + + // bwi: we start with an empty url, element tries to load a wellknown from localhost for 15 seconds in that case + guard homeserverAddress.count > 0 else { + throw AuthenticationError.invalidHomeserver + } + let homeserverAddress = HomeserverAddress.sanitized(homeserverAddress) guard var homeserverURL = URL(string: homeserverAddress) else { diff --git a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift index 03798ce49..8077254c2 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift @@ -98,7 +98,7 @@ struct AuthenticationLoginScreen: View { /// The form with text fields for username and password, along with a submit button. var loginForm: some View { VStack(spacing: 14) { - RoundedBorderTextField(placeHolder: VectorL10n.authenticationLoginUsername, + RoundedBorderTextField(placeHolder: BWIL10n.authenticationLoginUsername, text: $viewModel.username, isFirstResponder: false, configuration: UIKitTextInputConfiguration(returnKeyType: .next, @@ -118,13 +118,15 @@ struct AuthenticationLoginScreen: View { onCommit: submit) .accessibilityIdentifier("passwordTextField") - Button { viewModel.send(viewAction: .forgotPassword) } label: { - Text(VectorL10n.authenticationLoginForgotPassword) - .font(theme.fonts.body) + if !BWIBuildSettings.shared.bumLoginFlowLayout { + Button { viewModel.send(viewAction: .forgotPassword) } label: { + Text(VectorL10n.authenticationLoginForgotPassword) + .font(theme.fonts.body) + } + .frame(maxWidth: .infinity, alignment: .trailing) + .padding(.bottom, 8) } - .frame(maxWidth: .infinity, alignment: .trailing) - .padding(.bottom, 8) - + Button(action: submit) { Text(VectorL10n.next) } diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift index c94df50ca..2803c4a1a 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/AuthenticationServerSelectionModels.swift @@ -38,7 +38,7 @@ struct AuthenticationServerSelectionViewState: BindableState { var hasModalPresentation: Bool var headerTitle: String { - flow == .login ? VectorL10n.authenticationServerSelectionLoginTitle : VectorL10n.authenticationServerSelectionRegisterTitle + flow == .login ? BWIL10n.authenticationServerSelectionLoginTitle : VectorL10n.authenticationServerSelectionRegisterTitle } var headerMessage: String { diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift index c5d521701..885957df4 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/Coordinator/AuthenticationServerSelectionCoordinator.swift @@ -116,6 +116,17 @@ final class AuthenticationServerSelectionCoordinator: Coordinator, Presentable { let homeserverAddress = HomeserverAddress.sanitized(homeserverAddress) + if BWIBuildSettings.shared.bwiEnableLoginProtection { + let protectionService = LoginProtectionService() + protectionService.hashes = BWIBuildSettings.shared.bwiHashes + + guard protectionService.isValid(homeserverAddress) else { + stopLoading() + authenticationServerSelectionViewModel.displayError(.footerMessage(BWIL10n.bwiLoginProtectionErrorMessage(AppInfo.current.displayName))) + return + } + } + Task { do { try await authenticationService.startFlow(parameters.flow, for: homeserverAddress) diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift index 54144006b..510359ee9 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift @@ -58,7 +58,7 @@ struct AuthenticationServerSelectionScreen: View { /// The title, message and icon at the top of the screen. var header: some View { VStack(spacing: 8) { - OnboardingIconImage(image: Asset.Images.authenticationServerSelectionIcon) + OnboardingIconImage(image: Asset.Images.welcomeExperience1) .padding(.bottom, 8) Text(viewModel.viewState.headerTitle) diff --git a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift index 4993ce002..73a158168 100644 --- a/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift +++ b/RiotSwiftUI/Modules/Onboarding/Common/OnboardingIcon.swift @@ -24,8 +24,8 @@ struct OnboardingIconImage: View { var body: some View { Image(image.name) .resizable() - .renderingMode(.template) - .foregroundColor(theme.colors.accent) + //.renderingMode(.template) + //.foregroundColor(theme.colors.accent) .frame(width: OnboardingMetrics.iconSize, height: OnboardingMetrics.iconSize) .background(Circle().foregroundColor(.white).padding(2)) .accessibilityHidden(true) diff --git a/bwi/WelcomeExperience/WelcomeExperienceView.swift b/bwi/WelcomeExperience/WelcomeExperienceView.swift index fba923013..41951b587 100644 --- a/bwi/WelcomeExperience/WelcomeExperienceView.swift +++ b/bwi/WelcomeExperience/WelcomeExperienceView.swift @@ -3,20 +3,24 @@ import SwiftUI /// Helper class for making our SwiftUI view available to ObjectiveC @objcMembers class WelcomeExperienceViewController: NSObject { - class func makeViewController(session: MXSession) -> UIViewController { - return UIHostingController(rootView: WelcomeExperienceView()) + class func makeViewController(completion: (() -> Void)?) -> UIViewController { + let welcomeExperienceView = WelcomeExperienceView(completion: completion) + + return UIHostingController(rootView: welcomeExperienceView) } } struct WelcomeExperienceView: View { + + @State var completion: (() -> Void)? + var body: some View { TabView { - Page(image: "welcome_experience_1", title: NSLocalizedString("welcome_experience_title1", comment: ""), description: NSLocalizedString("welcome_experience_title1", comment: "")) - Page(image: "welcome_experience_2", title: NSLocalizedString("welcome_experience_title2", comment: ""), description: NSLocalizedString("welcome_experience_title2", comment: "")) - Page(image: "welcome_experience_3", title: NSLocalizedString("welcome_experience_title3", comment: ""), description: NSLocalizedString("welcome_experience_title3", comment: "")) - Page(image: "welcome_experience_4", title: NSLocalizedString("welcome_experience_title4", comment: ""), description: NSLocalizedString("welcome_experience_title4", comment: "")) - Page(image: "welcome_experience_5", title: NSLocalizedString("welcome_experience_title5", comment: ""), description: NSLocalizedString("welcome_experience_title5", comment: "")) - Page(image: "welcome_experience_2", title: NSLocalizedString("welcome_experience_title2", comment: ""), description: NSLocalizedString("welcome_experience_title2", comment: ""), doneButton: NSLocalizedString("welcome_experience_start", comment: "")) + Page(image: "welcome_experience_1", title: BWIL10n.welcomeExperienceTitle1, description: BWIL10n.welcomeExperienceDescription1) + Page(image: "welcome_experience_2", title: BWIL10n.welcomeExperienceTitle2, description: BWIL10n.welcomeExperienceDescription2) + Page(image: "welcome_experience_3", title: BWIL10n.welcomeExperienceTitle3, description: BWIL10n.welcomeExperienceDescription3) + Page(image: "welcome_experience_4", title: BWIL10n.welcomeExperienceTitle4, description: BWIL10n.welcomeExperienceDescription4) + Page(image: "welcome_experience_5", title: BWIL10n.welcomeExperienceTitle5, description: BWIL10n.welcomeExperienceDescription5, doneButton: BWIL10n.welcomeExperienceStart, buttonCallback: completion) } .tabViewStyle(.page) .indexViewStyle(.page(backgroundDisplayMode: .always)) @@ -30,13 +34,15 @@ fileprivate struct Page: View { var description: String var doneButton: String? + @State var buttonCallback: (() -> Void)? + var body: some View { GeometryReader { geometry in - VStack(alignment: .center, spacing: 50) { + VStack(alignment: .center, spacing: 40) { Image(image) .resizable() .aspectRatio(contentMode: .fit) - .frame(width: geometry.size.height * 0.3) + .frame(width: geometry.size.height * 0.25) Text(title) .foregroundColor(.black) @@ -48,8 +54,8 @@ fileprivate struct Page: View { .multilineTextAlignment(.center) Spacer() - if let doneButton = doneButton { - Button(action: {}) { + if let doneButton = doneButton, let buttonCallback = buttonCallback { + Button(action: buttonCallback) { Text(doneButton) .foregroundColor(.white) .padding(.vertical, 10) @@ -59,7 +65,6 @@ fileprivate struct Page: View { } .padding(.bottom, 100) } - } .padding(.top, 100) .padding(.horizontal, 20)