mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-11 02:17:43 +02:00
Merge branch 'feature/3707_login_flow_new_designs' into 'develop'
MESSENGER-3707 new login flow and welcome integration See merge request bwmessenger/bundesmessenger/bundesmessenger-ios!55
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ extension BWIBuildSettings {
|
||||
bwiPersonalState = true
|
||||
bwiEnableErrorTracking = true
|
||||
bwiMatomoTrackingDefaultState = false
|
||||
bwiShowMessageBubbles = true
|
||||
showSessionManager = true
|
||||
locationSharingEnabled = false
|
||||
bwiLocationShareButtonVisible = false
|
||||
|
||||
@@ -540,3 +540,6 @@
|
||||
|
||||
"welcome_experience_start" = "Loslegen";
|
||||
|
||||
// MARK - new login flow
|
||||
"authentication_server_selection_login_title" = "Willkommen!";
|
||||
"authentication_login_username" = "Nutzername";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+11
@@ -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)
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user