Skip to content

Commit

Permalink
#40: Update login screen for EXI.
Browse files Browse the repository at this point in the history
  • Loading branch information
pixlwave committed Jun 15, 2022
1 parent 2b65d44 commit 62cf181
Show file tree
Hide file tree
Showing 24 changed files with 533 additions and 712 deletions.
140 changes: 84 additions & 56 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
"untranslated" = "Untranslated";
"screenshot_detected_title" = "You took a screenshot";
"screenshot_detected_message" = "Would you like to submit a bug report?";

// MARK: - Authentication

"authentication_login_title" = "Welcome back!";
"authentication_login_forgot_password" = "Forgot password";

"authentication_server_info_title" = "Choose your server to store your data";
"authentication_server_info_matrix_description" = "Join millions for free on the largest public server";
6 changes: 6 additions & 0 deletions ElementX/Sources/Generated/Assets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ internal typealias AssetImageTypeAlias = ImageAsset.Image
// swiftlint:disable identifier_name line_length nesting type_body_length type_name
internal enum Asset {
internal enum Images {
internal static let authenticationSsoIconApple = ImageAsset(name: "Images/authentication_sso_icon_apple")
internal static let authenticationSsoIconFacebook = ImageAsset(name: "Images/authentication_sso_icon_facebook")
internal static let authenticationSsoIconGithub = ImageAsset(name: "Images/authentication_sso_icon_github")
internal static let authenticationSsoIconGitlab = ImageAsset(name: "Images/authentication_sso_icon_gitlab")
internal static let authenticationSsoIconGoogle = ImageAsset(name: "Images/authentication_sso_icon_google")
internal static let authenticationSsoIconTwitter = ImageAsset(name: "Images/authentication_sso_icon_twitter")
internal static let splashScreenPage1 = ImageAsset(name: "Images/Splash Screen Page 1")
internal static let splashScreenPage2 = ImageAsset(name: "Images/Splash Screen Page 2")
internal static let splashScreenPage3 = ImageAsset(name: "Images/Splash Screen Page 3")
Expand Down
8 changes: 8 additions & 0 deletions ElementX/Sources/Generated/Strings+Untranslated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import Foundation
// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
extension ElementL10n {
/// Forgot password
public static let authenticationLoginForgotPassword = ElementL10n.tr("Untranslated", "authentication_login_forgot_password")
/// Welcome back!
public static let authenticationLoginTitle = ElementL10n.tr("Untranslated", "authentication_login_title")
/// Join millions for free on the largest public server
public static let authenticationServerInfoMatrixDescription = ElementL10n.tr("Untranslated", "authentication_server_info_matrix_description")
/// Choose your server to store your data
public static let authenticationServerInfoTitle = ElementL10n.tr("Untranslated", "authentication_server_info_title")
/// Would you like to submit a bug report?
public static let screenshotDetectedMessage = ElementL10n.tr("Untranslated", "screenshot_detected_message")
/// You took a screenshot
Expand Down
19 changes: 19 additions & 0 deletions ElementX/Sources/Other/Logging/MXLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,25 @@ private var logger: SwiftyBeaver.Type = {
logger.error(message, file, function, line: line)
}

public static func failure(_ message: @autoclosure () -> Any, _
file: String = #file,
_ function: String = #function,
line: Int = #line,
context: Any? = nil) {
logger.error(message(), file, function, line: line, context: context)
#if DEBUG
assertionFailure("\(message())")
#endif
}

@available(swift, obsoleted: 5.4)
@objc public static func logFailure(_ message: String, file: String, function: String, line: Int) {
logger.error(message, file, function, line: line)
#if DEBUG
assertionFailure(message)
#endif
}

// MARK: - Private

fileprivate static func configureLogger(_ logger: SwiftyBeaver.Type, withConfiguration configuration: MXLogConfiguration) {
Expand Down
7 changes: 4 additions & 3 deletions ElementX/Sources/Other/SwiftUI/ErrorHandling/AlertInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ struct AlertInfo<T: Hashable>: Identifiable {
/// The alert's message (optional).
var message: String? = nil
/// The alert's primary button title and action. Defaults to an Ok button with no action.
var primaryButton: (title: String, action: (() -> Void)?) = (VectorL10n.ok, nil)
var primaryButton: (title: String, action: (() -> Void)?) = (ElementL10n.ok, nil)
/// The alert's secondary button title and action.
var secondaryButton: (title: String, action: (() -> Void)?)? = nil
}

#warning("Remove the NSError extensions?")
extension AlertInfo {
/// Initialises the type with the title and message from an `NSError` along with the default Ok button.
init?(error: NSError? = nil) where T == Int {
Expand All @@ -50,8 +51,8 @@ extension AlertInfo {
guard error?.domain != NSURLErrorDomain && error?.code != NSURLErrorCancelled else { return nil }

self.id = id
title = error?.userInfo[NSLocalizedFailureReasonErrorKey] as? String ?? VectorL10n.error
message = error?.userInfo[NSLocalizedDescriptionKey] as? String ?? VectorL10n.errorCommonMessage
title = error?.userInfo[NSLocalizedFailureReasonErrorKey] as? String ?? ElementL10n.dialogTitleError
message = error?.userInfo[NSLocalizedDescriptionKey] as? String ?? ElementL10n.unknownError
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,18 @@ class AuthenticationCoordinator: Coordinator {
}

private func showLoginScreen() {
let parameters = LoginScreenCoordinatorParameters()
let coordinator = LoginScreenCoordinator(parameters: parameters)
let parameters = AuthenticationLoginCoordinatorParameters(navigationRouter: navigationRouter, loginMode: .password)
let coordinator = AuthenticationLoginCoordinator(parameters: parameters)

coordinator.callback = { [weak self, weak coordinator] action in
guard let self = self, let coordinator = coordinator else {
return
}

switch action {
case .login(let result):
case .login(let username, let password):
Task {
switch await self.login(username: result.username, password: result.password) {
switch await self.login(username: username, password: password) {
case .success(let userSession):
self.delegate?.authenticationCoordinator(self, didLoginWithSession: userSession)
self.remove(childCoordinator: coordinator)
Expand All @@ -90,6 +90,10 @@ class AuthenticationCoordinator: Coordinator {
MXLog.error("Failed logging in user with error: \(error)")
}
}
case .continueWithSSO(let provider):
break
case .fallback:
break
}
}

Expand Down
114 changes: 51 additions & 63 deletions ElementX/Sources/Screens/Authentication/AuthenticationModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,68 +16,6 @@

import Foundation

/// A value that represents an authentication flow as either login or register.
enum AuthenticationFlow {
case login
case register
}

/// A value that represents the type of authentication used.
enum AuthenticationType {
/// A username and password.
case password
/// SSO with the associated provider
case sso(SSOIdentityProvider)
/// Some other method such as the fall back page.
case other
}

/// Errors that can be thrown from `AuthenticationService`.
enum AuthenticationError: String, LocalizedError {
case invalidHomeserver
case loginFlowNotCalled
case missingMXRestClient

var errorDescription: String? {
switch self {
case .invalidHomeserver:
return VectorL10n.authenticationServerSelectionGenericError
default:
return VectorL10n.errorCommonMessage
}
}
}

/// Errors that can be thrown from `RegistrationWizard`
enum RegistrationError: String, LocalizedError {
case registrationDisabled
case createAccountNotCalled
case missingThreePIDData
case missingThreePIDURL
case threePIDValidationFailure
case threePIDClientFailure
case waitingForThreePIDValidation
case invalidPhoneNumber

var errorDescription: String? {
switch self {
case .registrationDisabled:
return VectorL10n.loginErrorRegistrationIsNotSupported
case .threePIDValidationFailure, .threePIDClientFailure:
return VectorL10n.authMsisdnValidationError
case .invalidPhoneNumber:
return VectorL10n.authenticationVerifyMsisdnInvalidPhoneNumber
default:
return VectorL10n.errorCommonMessage
}
}
}

/// Errors that can be thrown from `LoginWizard`
enum LoginError: String, Error {
case resetPasswordNotStarted
}

struct HomeserverAddress {
/// Ensures the address contains a scheme, otherwise makes it `https`.
static func sanitized(_ address: String) -> String {
Expand All @@ -86,7 +24,7 @@ struct HomeserverAddress {
}

/// Represents an SSO Identity Provider as provided in a login flow.
@objc class SSOIdentityProvider: NSObject, Identifiable {
struct SSOIdentityProvider: Identifiable, Equatable {
/// The id field is the Identity Provider identifier used for the SSO Web page redirection `/login/sso/redirect/{idp_id}`.
let id: String
/// The name field is a human readable string intended to be printed by the client.
Expand All @@ -103,3 +41,53 @@ struct HomeserverAddress {
self.iconURL = iconURL
}
}

/// The supported forms of login that a homeserver allows.
enum LoginMode {
/// The login mode hasn't been determined yet.
case unknown
/// The homeserver supports login with a password.
case password
/// The homeserver supports login via one or more SSO providers.
case sso(ssoIdentityProviders: [SSOIdentityProvider])
/// The homeserver supports login with either a password or via an SSO provider.
case ssoAndPassword(ssoIdentityProviders: [SSOIdentityProvider])
/// The homeserver only allows login with unsupported mechanisms. Use fallback instead.
case unsupported

var ssoIdentityProviders: [SSOIdentityProvider]? {
switch self {
case .sso(let ssoIdentityProviders), .ssoAndPassword(let ssoIdentityProviders):
return ssoIdentityProviders
default:
return nil
}
}

var hasSSO: Bool {
switch self {
case .sso, .ssoAndPassword:
return true
default:
return false
}
}

var supportsPasswordFlow: Bool {
switch self {
case .password, .ssoAndPassword:
return true
case .unknown, .unsupported, .sso:
return false
}
}

var isUnsupported: Bool {
switch self {
case .unsupported:
return true
default:
return false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ struct AuthenticationSSOButton: View {

// MARK: - Private

@Environment(\.theme) private var theme
@ScaledMetric private var iconSize = 24

private var renderingMode: Image.TemplateRenderingMode? {
Expand All @@ -47,8 +46,8 @@ struct AuthenticationSSOButton: View {
icon
.frame(maxWidth: .infinity, alignment: .leading)

Text(VectorL10n.socialLoginButtonTitleContinue(provider.name))
.foregroundColor(theme.colors.primaryContent)
Text(ElementL10n.loginSocialContinueWith(provider.name))
.foregroundColor(.element.primaryContent)
.multilineTextAlignment(.center)
.layoutPriority(1)

Expand All @@ -60,7 +59,7 @@ struct AuthenticationSSOButton: View {
.fixedSize(horizontal: false, vertical: true)
.contentShape(RoundedRectangle(cornerRadius: 8))
}
.buttonStyle(SecondaryActionButtonStyle(customColor: theme.colors.quinaryContent))
.buttonStyle(.elementGhost(.xLarge, color: .element.quinaryContent))
}

/// The icon with appropriate rendering mode and size for dynamic type.
Expand All @@ -71,7 +70,7 @@ struct AuthenticationSSOButton: View {
.resizable()
.scaledToFit()
.frame(width: iconSize, height: iconSize)
.foregroundColor(renderingMode == .template ? theme.colors.primaryContent : nil)
.foregroundColor(renderingMode == .template ? .element.primaryContent : nil)
}
}

Expand Down Expand Up @@ -111,9 +110,9 @@ struct AuthenticationSSOButton_Previews: PreviewProvider {

static var previews: some View {
buttons
.theme(.light).preferredColorScheme(.light)
.preferredColorScheme(.light)
.environment(\.sizeCategory, .accessibilityLarge)
buttons
.theme(.dark).preferredColorScheme(.dark)
.preferredColorScheme(.dark)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ import SwiftUI
/// along with an edit button to pick a different one.
struct AuthenticationServerInfoSection: View {

// MARK: - Private

@Environment(\.theme) private var theme

// MARK: - Public

let address: String
Expand All @@ -34,33 +30,31 @@ struct AuthenticationServerInfoSection: View {

var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(VectorL10n.authenticationServerInfoTitle)
.font(theme.fonts.subheadline)
.foregroundColor(theme.colors.secondaryContent)
Text(ElementL10n.authenticationServerInfoTitle)
.font(.element.subheadline)
.foregroundColor(.element.secondaryContent)

HStack {
VStack(alignment: .leading, spacing: 2) {
Text(address)
.font(theme.fonts.body)
.foregroundColor(theme.colors.primaryContent)
.font(.element.body)
.foregroundColor(.element.primaryContent)

if showMatrixDotOrgInfo {
Text(VectorL10n.authenticationServerInfoMatrixDescription)
.font(theme.fonts.caption1)
.foregroundColor(theme.colors.tertiaryContent)
Text(ElementL10n.authenticationServerInfoMatrixDescription)
.font(.element.caption1)
.foregroundColor(.element.tertiaryContent)
.accessibilityIdentifier("serverDescriptionText")
}
}

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))
Text(ElementL10n.edit)
.padding(.vertical, 2)
}
.buttonStyle(.elementGhost())
}
}
}
Expand Down
Loading

0 comments on commit 62cf181

Please sign in to comment.