diff --git a/Config/BWIBuildSettings.swift b/Config/BWIBuildSettings.swift index a68e9d1d1..a57030097 100644 --- a/Config/BWIBuildSettings.swift +++ b/Config/BWIBuildSettings.swift @@ -427,6 +427,8 @@ class BWIBuildSettings: NSObject { var onboardingShowAccountPersonalization = false var onboardingEnableNewAuthenticationFlow = true + // show app specific welcoming screens + var onboardingShowWelcomeScreens = true // --- @@ -525,6 +527,9 @@ class BWIBuildSettings: NSObject { // one flag for all layout changes to the element login flow var bumLoginFlowLayout = true + // one flag for app specific layout changes + var bwiLoginFlowLayout = true + // website for users in public service that want a backend var bumAdvertizementURLString = "https://messenger.bwi.de/ich-will-bum"; diff --git a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift index a3810f8cf..d85b294d7 100644 --- a/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift +++ b/Config/BuM-Beta/BWIBuildSettings+BuM-Beta.swift @@ -30,6 +30,7 @@ extension BWIBuildSettings { showSessionManager = true locationSharingEnabled = false bwiLocationShareButtonVisible = false + bwiLoginFlowLayout = false } } diff --git a/Config/BuM/BWIBuildSettings+BuM.swift b/Config/BuM/BWIBuildSettings+BuM.swift index a3aea700c..d2f56db07 100644 --- a/Config/BuM/BWIBuildSettings+BuM.swift +++ b/Config/BuM/BWIBuildSettings+BuM.swift @@ -23,6 +23,8 @@ extension BWIBuildSettings { secondaryAppName = "BundesMessenger" locationSharingEnabled = false bwiLocationShareButtonVisible = false + bwiLoginFlowLayout = false + authScreenShowTestServerOptions = false } } diff --git a/Riot/Modules/Authentication/AuthenticationCoordinator.swift b/Riot/Modules/Authentication/AuthenticationCoordinator.swift index 3026cd013..70803640d 100644 --- a/Riot/Modules/Authentication/AuthenticationCoordinator.swift +++ b/Riot/Modules/Authentication/AuthenticationCoordinator.swift @@ -130,27 +130,33 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc } let flow: AuthenticationFlow = initialScreen == .login ? .login : .register - do { - // Start the flow using the default server (or a provisioning link if set). - try await authenticationService.startFlow(flow) - } catch { - MXLog.error("[AuthenticationCoordinator] start: Failed to start, showing server selection.") + + // bwi: force serverselection on test apps + if BWIBuildSettings.shared.authScreenShowTestServerOptions && BWIBuildSettings.shared.bwiLoginFlowLayout { showServerSelectionScreen(for: flow) - return - } - - switch initialScreen { - case .registration: - if authenticationService.state.homeserver.needsRegistrationFallback { - showFallback(for: flow) - } else { - showRegistrationScreen() + } else { + do { + // Start the flow using the default server (or a provisioning link if set). + try await authenticationService.startFlow(flow) + } catch { + MXLog.error("[AuthenticationCoordinator] start: Failed to start, showing server selection.") + showServerSelectionScreen(for: flow) + return } - case .login: - if authenticationService.state.homeserver.needsLoginFallback { - showFallback(for: flow) - } else { - showLoginScreen() + + switch initialScreen { + case .registration: + if authenticationService.state.homeserver.needsRegistrationFallback { + showFallback(for: flow) + } else { + showRegistrationScreen() + } + case .login: + if authenticationService.state.homeserver.needsLoginFallback { + showFallback(for: flow) + } else { + showLoginScreen() + } } } } diff --git a/Riot/Modules/Onboarding/OnboardingCoordinator.swift b/Riot/Modules/Onboarding/OnboardingCoordinator.swift index bc1696653..05814c5ca 100644 --- a/Riot/Modules/Onboarding/OnboardingCoordinator.swift +++ b/Riot/Modules/Onboarding/OnboardingCoordinator.swift @@ -161,8 +161,13 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol { /// Displays the next view in the flow after the splash screen. private func bwiSplashScreenCoordinator(_ coordinator: OnboardingBwiSplashScreenCoordinator, didCompleteWith result: OnboardingBwiSplashScreenViewModelResult) { - if BWIBuildSettings.shared.onboardingEnableNewAuthenticationFlow { - self.showWelcomeExperience() + if BWIBuildSettings.shared.onboardingEnableNewAuthenticationFlow { + // bwi: show app specific welcoming screens if enabled + if BWIBuildSettings.shared.onboardingShowWelcomeScreens { + self.showWelcomeExperience() + } else { + self.beginAuthentication(with: .login) + } } else { showLegacyAuthenticationScreen(forceAsRootModule: true) } diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift index 204eddd57..dac56608d 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginModels.swift @@ -33,6 +33,8 @@ enum AuthenticationLoginViewModelResult: CustomStringConvertible { case fallback /// Continue with QR login case qrLogin + /// bwi: register info + case register /// A string representation of the result, ignoring any associated values that could leak PII. var description: String { @@ -51,6 +53,8 @@ enum AuthenticationLoginViewModelResult: CustomStringConvertible { return "fallback" case .qrLogin: return "qrLogin" + case .register: + return "register" } } } @@ -112,6 +116,8 @@ enum AuthenticationLoginViewAction { case continueWithSSO(SSOIdentityProvider) /// Continue using QR login case qrLogin + /// bwi: register info + case register } enum AuthenticationLoginErrorType: Hashable { diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift index f1180c1d1..64ee83363 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModel.swift @@ -52,6 +52,8 @@ class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, Authentica Task { await callback?(.continueWithSSO(provider)) } case .qrLogin: Task { await callback?(.qrLogin) } + case .register: + Task { await callback?(.register) } } } @@ -78,4 +80,20 @@ class AuthenticationLoginViewModel: AuthenticationLoginViewModelType, Authentica state.bindings.alertInfo = AlertInfo(id: type) } } + + // bwi: show custom alert + @MainActor func displayInfoAlert(_ type: AuthenticationLoginViewAction) { + switch type { + case .forgotPassword: + state.bindings.alertInfo = AlertInfo(id: .unknown, + title: BWIL10n.authForgotPassword, + message: BWIL10n.bwiAuthForgotPasswordAlertText) + case .register: + state.bindings.alertInfo = AlertInfo(id: .unknown, + title: BWIL10n.bwiAuthRegisterAlertTitle, + message: BWIL10n.bwiAuthRegisterAlertText) + default: + return + } + } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift index 87ef46fce..bbd8f6a7b 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/AuthenticationLoginViewModelProtocol.swift @@ -31,4 +31,8 @@ protocol AuthenticationLoginViewModelProtocol { /// Display an error to the user. /// - Parameter type: The type of error to be displayed. @MainActor func displayError(_ type: AuthenticationLoginErrorType) + + /// bwi: Display an info alert. + /// - Parameter type: The type of the message to be displayed. + @MainActor func displayInfoAlert(_ type: AuthenticationLoginViewAction) } diff --git a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift index a1b899ad6..f71f6c0c9 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/Coordinator/AuthenticationLoginCoordinator.swift @@ -123,7 +123,12 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { case .parseUsername(let username): self.parseUsername(username) case .forgotPassword: - self.showForgotPasswordScreen() + // bwi: show info alert + if BWIBuildSettings.shared.forgotPasswordInformationAlert { + self.authenticationLoginViewModel.displayInfoAlert(.forgotPassword) + } else { + self.showForgotPasswordScreen() + } case .login(let username, let password): self.login(username: username, password: password) case .continueWithSSO(let identityProvider): @@ -132,6 +137,9 @@ final class AuthenticationLoginCoordinator: Coordinator, Presentable { self.callback?(.fallback) case .qrLogin: self.showQRLoginScreen() + case .register: + // bwi: show info alert + self.authenticationLoginViewModel.displayInfoAlert(.register) } } } diff --git a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift index ff09c7624..d7c82822c 100644 --- a/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/Login/View/AuthenticationLoginScreen.swift @@ -20,6 +20,9 @@ struct AuthenticationLoginScreen: View { // MARK: - Properties // MARK: Private + private enum CustomText { + case username, submit + } @Environment(\.theme) private var theme: ThemeSwiftUI @@ -35,8 +38,8 @@ struct AuthenticationLoginScreen: View { VStack { ScrollView { VStack(spacing: 0) { - if BWIBuildSettings.shared.bumLoginFlowLayout { - ServerIcon(image: Asset.SharedImages.loginFlowLogo, size: OnboardingMetrics.iconSize) + if BWIBuildSettings.shared.bumLoginFlowLayout || BWIBuildSettings.shared.bwiLoginFlowLayout { + ServerIcon(image: getServerIcon(), size: OnboardingMetrics.iconSize) .padding(.top, OnboardingMetrics.topPaddingToNavigationBar) .padding(.bottom, 16) } else { @@ -45,14 +48,19 @@ struct AuthenticationLoginScreen: View { .padding(.bottom, 28) } - serverInfo - .padding(.leading, 12) - .padding(.bottom, 16) + if !BWIBuildSettings.shared.bwiLoginFlowLayout { + serverInfo + .padding(.leading, 12) + .padding(.bottom, 16) - Rectangle() - .fill(theme.colors.quinaryContent) - .frame(height: 1) - .padding(.bottom, 22) + Rectangle() + .fill(theme.colors.quinaryContent) + .frame(height: 1) + .padding(.bottom, 22) + } else { + // bwi: show cutom header + authLoginHeaderlineText + } if BWIBuildSettings.shared.bumLoginFlowLayout { loginDescription @@ -115,7 +123,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: BWIL10n.authenticationLoginUsername, + RoundedBorderTextField(placeHolder: getCustomText(text: .username), text: $viewModel.username, isFirstResponder: false, configuration: UIKitTextInputConfiguration(returnKeyType: .next, @@ -134,8 +142,8 @@ struct AuthenticationLoginScreen: View { onEditingChanged: passwordEditingChanged, onCommit: submit) .accessibilityIdentifier("passwordTextField") - - if !BWIBuildSettings.shared.bumLoginFlowLayout { + // bwi: hide nv forgot password button + if !BWIBuildSettings.shared.bumLoginFlowLayout && !BWIBuildSettings.shared.bwiLoginFlowLayout { Button { viewModel.send(viewAction: .forgotPassword) } label: { Text(VectorL10n.authenticationLoginForgotPassword) .font(theme.fonts.body) @@ -145,11 +153,20 @@ struct AuthenticationLoginScreen: View { } Button(action: submit) { - Text(VectorL10n.next) + Text(getCustomText(text: .submit)) } .buttonStyle(PrimaryActionButtonStyle()) .disabled(!viewModel.viewState.canSubmit) .accessibilityIdentifier("nextButton") + .padding([.vertical], BWIBuildSettings.shared.bwiLoginFlowLayout ? 36 : 0) + + + if BWIBuildSettings.shared.authScreenShowForgotPassword { + forgotPasswordButton + } + if BWIBuildSettings.shared.bwiEnableRegisterInfo { + registerButton + } } } @@ -239,6 +256,59 @@ struct AuthenticationLoginScreen: View { func qrLogin() { viewModel.send(viewAction: .qrLogin) } + + // bwi: custom forgot password button + var forgotPasswordButton: some View { + Button { + viewModel.send(viewAction: .forgotPassword) + } label: { + Text(BWIL10n.authForgotPassword) + .font(theme.fonts.body) + } + .frame(maxWidth: .infinity, alignment: .center) + .padding(.bottom, 8) + } + + // bwi: custom register button + var registerButton: some View { + Button { + viewModel.send(viewAction: .register) + } label: { + Text(BWIL10n.bwiAuthRegisterButtonTitle) + .font(theme.fonts.body) + } + .frame(maxWidth: .infinity, alignment: .center) + .padding(.bottom, 8) + } + + // bwi: custom header + var authLoginHeaderlineText: some View { + VStack(alignment: .leading) { + Text(BWIL10n.authLoginHeadlineText) + .font(theme.fonts.body) + .foregroundColor(theme.colors.primaryContent) + Text(BWIL10n.authLoginSubheadlineText) + .font(theme.fonts.subheadline) + .foregroundColor(theme.colors.secondaryContent) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding([.vertical], 36) + } + + // bwi: get app specific ServerIcon + func getServerIcon() -> ImageAsset { + return BWIBuildSettings.shared.bwiLoginFlowLayout ? Asset.Images.launchScreenLogo : Asset.SharedImages.loginFlowLogo + } + + // bwi: get app specific text + private func getCustomText(text: CustomText) -> String { + switch text { + case .submit: + return BWIBuildSettings.shared.bwiLoginFlowLayout ? BWIL10n.authenticationServerSelectionSubmitButtonTitle : VectorL10n.next + case .username: + return BWIBuildSettings.shared.bwiLoginFlowLayout ? BWIL10n.authUserIdPlaceholder : BWIL10n.authenticationLoginUsername + } + } } // MARK: - Previews diff --git a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift index 39469e528..866fe1845 100644 --- a/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift +++ b/RiotSwiftUI/Modules/Authentication/ServerSelection/View/AuthenticationServerSelectionScreen.swift @@ -67,6 +67,9 @@ struct AuthenticationServerSelectionScreen: View { message: Text(BWIL10n.authenticationServerSelectionServerDeniedMessage), dismissButton: .default(Text(VectorL10n.ok))) } + if BWIBuildSettings.shared.authScreenShowTestServerOptions { + serverSelectionButton + } } .readableFrame() .padding(.horizontal, 16) @@ -221,6 +224,24 @@ struct AuthenticationServerSelectionScreen: View { UIApplication.shared.open(url, options: [:], completionHandler: nil) } } + + // bwi: show server selection button + var serverSelectionButton: some View { + VStack() { + Menu(content: { + ForEach(ServerURLHelper.shared.serverSettings, id: \.self) { server in + Button(server.name, action: { + viewModel.homeserverAddress = server.serverUrl + }) + } + }, label: { + Button(action: { return }) { + Text(BWIL10n.bwiAuthBetaSelectionButtonTitle) + } + .buttonStyle(PrimaryActionButtonStyle()) + }) + } + } } diff --git a/bwi/CommonUI/ServerIcon.swift b/bwi/CommonUI/ServerIcon.swift index 5b72b0f38..e768b6c61 100644 --- a/bwi/CommonUI/ServerIcon.swift +++ b/bwi/CommonUI/ServerIcon.swift @@ -27,7 +27,7 @@ struct ServerIcon: View { Image(image.name) .resizable() .frame(width: size, height: size) - .background(Color.white) + .background(BWIBuildSettings.shared.bwiLoginFlowLayout ? Color.clear : Color.white) .cornerRadius(20) .accessibilityHidden(true) }