Skip to content

Commit

Permalink
Handle waiting list error. (#1165)
Browse files Browse the repository at this point in the history
  • Loading branch information
pixlwave authored Jun 26, 2023
1 parent bc7faec commit 2fce96d
Show file tree
Hide file tree
Showing 25 changed files with 589 additions and 19 deletions.
44 changes: 44 additions & 0 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@
"soft_logout_clear_data_submit" = "Clear all data";
"soft_logout_clear_data_dialog_title" = "Clear data";
"soft_logout_clear_data_dialog_content" = "Clear all data currently stored on this device?\nSign in again to access your account data and messages.";

"common_refreshing" = "Refreshing…";
"screen_waitlist_title" = "You're on the waitlist!";
"screen_waitlist_title_success" = "You're in!";
"screen_waitlist_message" = "There's a high demand for %1$@ on %2$@ at the moment. Come back to the app in a few days and try again.\n\nThanks for your patience!";
"screen_waitlist_message_success" = "Welcome to %1$@";
4 changes: 2 additions & 2 deletions ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,10 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
displayName = name
}

let credentials = SoftLogoutScreenCredentials(userId: userSession.userID,
let credentials = SoftLogoutScreenCredentials(userID: userSession.userID,
homeserverName: userSession.homeserver,
userDisplayName: displayName,
deviceId: userSession.deviceID)
deviceID: userSession.deviceID)

let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore)
_ = await authenticationService.configure(for: userSession.homeserver)
Expand Down
16 changes: 16 additions & 0 deletions ElementX/Sources/Generated/Strings+Untranslated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ 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
public enum UntranslatedL10n {
/// Refreshing…
public static var commonRefreshing: String { return UntranslatedL10n.tr("Untranslated", "common_refreshing") }
/// There's a high demand for %1$@ on %2$@ at the moment. Come back to the app in a few days and try again.
///
/// Thanks for your patience!
public static func screenWaitlistMessage(_ p1: Any, _ p2: Any) -> String {
return UntranslatedL10n.tr("Untranslated", "screen_waitlist_message", String(describing: p1), String(describing: p2))
}
/// Welcome to %1$@
public static func screenWaitlistMessageSuccess(_ p1: Any) -> String {
return UntranslatedL10n.tr("Untranslated", "screen_waitlist_message_success", String(describing: p1))
}
/// You're on the waitlist!
public static var screenWaitlistTitle: String { return UntranslatedL10n.tr("Untranslated", "screen_waitlist_title") }
/// You're in!
public static var screenWaitlistTitleSuccess: String { return UntranslatedL10n.tr("Untranslated", "screen_waitlist_title_success") }
/// Clear all data currently stored on this device?
/// Sign in again to access your account data and messages.
public static var softLogoutClearDataDialogContent: String { return UntranslatedL10n.tr("Untranslated", "soft_logout_clear_data_dialog_content") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,33 @@ class AuthenticationCoordinator: CoordinatorProtocol {
case .configuredForOIDC:
// Pop back to the confirmation screen for OIDC login to continue.
navigationStackCoordinator.pop(animated: false)
case .isOnWaitlist(let credentials):
showWaitlistScreen(for: credentials)
}
}

navigationStackCoordinator.push(coordinator)
}

private func showWaitlistScreen(for credentials: WaitlistScreenCredentials) {
let parameters = WaitlistScreenCoordinatorParameters(credentials: credentials,
authenticationService: authenticationService)
let coordinator = WaitlistScreenCoordinator(parameters: parameters)

coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {
case .signedIn(let userSession):
userHasSignedIn(userSession: userSession)
case .cancel:
navigationStackCoordinator.pop()
}
}
.store(in: &cancellables)

navigationStackCoordinator.push(coordinator)
}

private func userHasSignedIn(userSession: UserSessionProtocol) {
showAnalyticsPromptIfNeeded { [weak self] in
guard let self else { return }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ enum LoginScreenCoordinatorAction {
case configuredForOIDC
/// Login was successful.
case signedIn(UserSessionProtocol)
/// The user's request to login failed due to being on the proxy waitlist.
case isOnWaitlist(WaitlistScreenCredentials)
}

final class LoginScreenCoordinator: CoordinatorProtocol {
Expand Down Expand Up @@ -117,13 +119,22 @@ final class LoginScreenCoordinator: CoordinatorProtocol {
switch await authenticationService.login(username: username,
password: password,
initialDeviceName: UIDevice.current.initialDeviceName,
deviceId: nil) {
deviceID: nil) {
case .success(let userSession):
callback?(.signedIn(userSession))
stopLoading()
case .failure(let error):
stopLoading()
handleError(error)
switch error {
case .isOnWaitlist:
callback?(.isOnWaitlist(.init(username: username,
password: password,
initialDeviceName: UIDevice.current.initialDeviceName,
deviceID: nil,
homeserver: authenticationService.homeserver.value)))
default:
handleError(error)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ enum MockSoftLogoutScreenState: String, CaseIterable {

/// Generate the view struct for the screen state.
@MainActor var viewModel: SoftLogoutScreenViewModel {
let credentials = SoftLogoutScreenCredentials(userId: "@mock:matrix.org",
let credentials = SoftLogoutScreenCredentials(userID: "@mock:matrix.org",
homeserverName: "matrix.org",
userDisplayName: "mock",
deviceId: nil)
deviceID: nil)
switch self {
case .emptyPassword:
return SoftLogoutScreenViewModel(credentials: credentials,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ final class SoftLogoutScreenCoordinator: CoordinatorProtocol {

/// Login with the supplied username and password.
@MainActor private func login(withPassword password: String) {
let username = parameters.credentials.userId
let username = parameters.credentials.userID

startLoading()

Task {
switch await authenticationService.login(username: username,
password: password,
initialDeviceName: UIDevice.current.initialDeviceName,
deviceId: parameters.credentials.deviceId) {
deviceID: parameters.credentials.deviceID) {
case .success(let userSession):
callback?(.signedIn(userSession))
stopLoading()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
import SwiftUI

struct SoftLogoutScreenCredentials {
let userId: String
let userID: String
let homeserverName: String
let userDisplayName: String
let deviceId: String?
let deviceID: String?
}

enum SoftLogoutScreenViewModelAction: CustomStringConvertible {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ struct SoftLogoutScreen: View {
.foregroundColor(.compound.textPrimary)
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.title)

Text(UntranslatedL10n.softLogoutSigninNotice(context.viewState.credentials.homeserverName, context.viewState.credentials.userDisplayName, context.viewState.credentials.userId))
Text(UntranslatedL10n.softLogoutSigninNotice(context.viewState.credentials.homeserverName, context.viewState.credentials.userDisplayName, context.viewState.credentials.userID))
.font(.compound.bodyLG)
.multilineTextAlignment(.leading)
.foregroundColor(.compound.textPrimary)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import SwiftUI

struct WaitlistScreen: View {
@ObservedObject var context: WaitlistScreenViewModel.Context

var body: some View {
FullscreenDialog(topPadding: UIConstants.iconTopPaddingToNavigationBar) {
header
} bottomContent: {
buttons
}
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.navigationBarBackButtonHidden()
.toolbar { toolbar }
.toolbar(.visible, for: .navigationBar) // Layout consistency in all states.
.overlay {
EffectsView(effect: context.viewState.isWaiting ? .none : .confetti)
.ignoresSafeArea()
.allowsHitTesting(false)
}
}

/// The main content of the view to be shown in a scroll view.
var header: some View {
VStack(spacing: 8) {
AuthenticationIconImage(image: Image(systemName: context.viewState.iconSymbolName))
.fontWeight(.semibold)
.padding(.bottom, 8)

Text(context.viewState.title)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textPrimary)
.fixedSize(horizontal: false, vertical: true)

Text(context.viewState.message)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
}
.padding(.horizontal, 16)
}

/// The action buttons shown at the bottom of the view.
@ViewBuilder
var buttons: some View {
if let userSession = context.viewState.userSession {
Button { context.send(viewAction: .continue(userSession)) } label: {
Text(L10n.actionContinue)
}
.buttonStyle(.elementAction(.xLarge))
}
}

@ToolbarContentBuilder
var toolbar: some ToolbarContent {
if context.viewState.isWaiting {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
}
}
}

// MARK: - Previews

struct WaitlistScreen_Previews: PreviewProvider {
static let viewModel = WaitlistScreenViewModel(homeserver: .mockMatrixDotOrg)
static let successViewModel = {
let viewModel = WaitlistScreenViewModel(homeserver: .mockMatrixDotOrg)
viewModel.update(userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@alice:matrix.org"),
mediaProvider: MockMediaProvider()))
return viewModel
}()

static var previews: some View {
NavigationStack {
WaitlistScreen(context: viewModel.context)
}
.previewDisplayName("Waiting")

NavigationStack {
WaitlistScreen(context: successViewModel.context)
}
.previewDisplayName("Success")
}
}
Loading

0 comments on commit 2fce96d

Please sign in to comment.