Renaming screens returns (#829)

* Rename: StartChat -> StartChatScreen

* Rename: Login -> LoginScreen

* Rename: ServerSelection -> ServerSelectionScreen

* Rename: SoftLogout -> SoftLogoutScreen
This commit is contained in:
Stefan Ceriu
2023-04-25 12:13:03 +03:00
committed by GitHub
parent 24572c6582
commit b726cd149a
31 changed files with 286 additions and 286 deletions
@@ -220,18 +220,18 @@ class AppCoordinator: AppCoordinatorProtocol {
displayName = name displayName = name
} }
let credentials = SoftLogoutCredentials(userId: userSession.userID, let credentials = SoftLogoutScreenCredentials(userId: userSession.userID,
homeserverName: userSession.homeserver, homeserverName: userSession.homeserver,
userDisplayName: displayName, userDisplayName: displayName,
deviceId: userSession.deviceID) deviceId: userSession.deviceID)
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore) let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore)
_ = await authenticationService.configure(for: userSession.homeserver) _ = await authenticationService.configure(for: userSession.homeserver)
let parameters = SoftLogoutCoordinatorParameters(authenticationService: authenticationService, let parameters = SoftLogoutScreenCoordinatorParameters(authenticationService: authenticationService,
credentials: credentials, credentials: credentials,
keyBackupNeeded: false) keyBackupNeeded: false)
let coordinator = SoftLogoutCoordinator(parameters: parameters) let coordinator = SoftLogoutScreenCoordinator(parameters: parameters)
coordinator.callback = { result in coordinator.callback = { result in
switch result { switch result {
case .signedIn(let session): case .signedIn(let session):
@@ -72,10 +72,10 @@ class AuthenticationCoordinator: CoordinatorProtocol {
} }
private func showServerSelectionScreen() { private func showServerSelectionScreen() {
let parameters = ServerSelectionCoordinatorParameters(authenticationService: authenticationService, let parameters = ServerSelectionScreenCoordinatorParameters(authenticationService: authenticationService,
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false) isModallyPresented: false)
let coordinator = ServerSelectionCoordinator(parameters: parameters) let coordinator = ServerSelectionScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in coordinator.callback = { [weak self] action in
guard let self else { return } guard let self else { return }
@@ -92,10 +92,10 @@ class AuthenticationCoordinator: CoordinatorProtocol {
} }
private func showLoginScreen() { private func showLoginScreen() {
let parameters = LoginCoordinatorParameters(authenticationService: authenticationService, let parameters = LoginScreenCoordinatorParameters(authenticationService: authenticationService,
navigationStackCoordinator: navigationStackCoordinator) navigationStackCoordinator: navigationStackCoordinator)
let coordinator = LoginCoordinator(parameters: parameters) let coordinator = LoginScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in coordinator.callback = { [weak self] action in
guard let self else { return } guard let self else { return }
@@ -17,21 +17,21 @@
import AppAuth import AppAuth
import SwiftUI import SwiftUI
struct LoginCoordinatorParameters { struct LoginScreenCoordinatorParameters {
/// The service used to authenticate the user. /// The service used to authenticate the user.
let authenticationService: AuthenticationServiceProxyProtocol let authenticationService: AuthenticationServiceProxyProtocol
/// The navigation router used to present the server selection screen. /// The navigation router used to present the server selection screen.
let navigationStackCoordinator: NavigationStackCoordinator let navigationStackCoordinator: NavigationStackCoordinator
} }
enum LoginCoordinatorAction { enum LoginScreenCoordinatorAction {
/// Login was successful. /// Login was successful.
case signedIn(UserSessionProtocol) case signedIn(UserSessionProtocol)
} }
final class LoginCoordinator: CoordinatorProtocol { final class LoginScreenCoordinator: CoordinatorProtocol {
private let parameters: LoginCoordinatorParameters private let parameters: LoginScreenCoordinatorParameters
private var viewModel: LoginViewModelProtocol private var viewModel: LoginScreenViewModelProtocol
private let hostingController: UIViewController private let hostingController: UIViewController
/// Passed to the OIDC service to provide a view controller from which to present the authentication session. /// Passed to the OIDC service to provide a view controller from which to present the authentication session.
private let oidcUserAgent: OIDExternalUserAgentIOS? private let oidcUserAgent: OIDExternalUserAgentIOS?
@@ -45,14 +45,14 @@ final class LoginCoordinator: CoordinatorProtocol {
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService } private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
private var navigationStackCoordinator: NavigationStackCoordinator { parameters.navigationStackCoordinator } private var navigationStackCoordinator: NavigationStackCoordinator { parameters.navigationStackCoordinator }
var callback: (@MainActor (LoginCoordinatorAction) -> Void)? var callback: (@MainActor (LoginScreenCoordinatorAction) -> Void)?
// MARK: - Setup // MARK: - Setup
init(parameters: LoginCoordinatorParameters) { init(parameters: LoginScreenCoordinatorParameters) {
self.parameters = parameters self.parameters = parameters
viewModel = LoginViewModel(homeserver: parameters.authenticationService.homeserver) viewModel = LoginScreenViewModel(homeserver: parameters.authenticationService.homeserver)
hostingController = UIHostingController(rootView: LoginScreen(context: viewModel.context)) hostingController = UIHostingController(rootView: LoginScreen(context: viewModel.context))
oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController) oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController)
@@ -199,11 +199,11 @@ final class LoginCoordinator: CoordinatorProtocol {
/// Presents the server selection screen as a modal. /// Presents the server selection screen as a modal.
private func presentServerSelectionScreen() { private func presentServerSelectionScreen() {
let parameters = ServerSelectionCoordinatorParameters(authenticationService: authenticationService, let parameters = ServerSelectionScreenCoordinatorParameters(authenticationService: authenticationService,
userIndicatorController: ServiceLocator.shared.userIndicatorController, userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false) isModallyPresented: false)
let coordinator = ServerSelectionCoordinator(parameters: parameters) let coordinator = ServerSelectionScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self, weak coordinator] action in coordinator.callback = { [weak self, weak coordinator] action in
guard let self, let coordinator else { return } guard let self, let coordinator else { return }
self.serverSelectionCoordinator(coordinator, didCompleteWith: action) self.serverSelectionCoordinator(coordinator, didCompleteWith: action)
@@ -213,8 +213,8 @@ final class LoginCoordinator: CoordinatorProtocol {
} }
/// Handles the result from the server selection modal, dismissing it after updating the view. /// Handles the result from the server selection modal, dismissing it after updating the view.
private func serverSelectionCoordinator(_ coordinator: ServerSelectionCoordinator, private func serverSelectionCoordinator(_ coordinator: ServerSelectionScreenCoordinator,
didCompleteWith action: ServerSelectionCoordinatorAction) { didCompleteWith action: ServerSelectionScreenCoordinatorAction) {
if action == .updated { if action == .updated {
updateViewModel() updateViewModel()
} }
@@ -16,7 +16,7 @@
import Foundation import Foundation
enum LoginViewModelAction: CustomStringConvertible { enum LoginScreenViewModelAction: CustomStringConvertible {
/// The user would like to select another server. /// The user would like to select another server.
case selectServer case selectServer
/// Parse the username and update the homeserver if included. /// Parse the username and update the homeserver if included.
@@ -45,13 +45,13 @@ enum LoginViewModelAction: CustomStringConvertible {
} }
} }
struct LoginViewState: BindableState { struct LoginScreenViewState: BindableState {
/// Data about the selected homeserver. /// Data about the selected homeserver.
var homeserver: LoginHomeserver var homeserver: LoginHomeserver
/// Whether a new homeserver is currently being loaded. /// Whether a new homeserver is currently being loaded.
var isLoading = false var isLoading = false
/// View state that can be bound to from SwiftUI. /// View state that can be bound to from SwiftUI.
var bindings: LoginBindings var bindings: LoginScreenBindings
/// The types of login supported by the homeserver. /// The types of login supported by the homeserver.
var loginMode: LoginMode { homeserver.loginMode } var loginMode: LoginMode { homeserver.loginMode }
@@ -67,16 +67,16 @@ struct LoginViewState: BindableState {
} }
} }
struct LoginBindings { struct LoginScreenBindings {
/// The username input by the user. /// The username input by the user.
var username = "" var username = ""
/// The password input by the user. /// The password input by the user.
var password = "" var password = ""
/// Information describing the currently displayed alert. /// Information describing the currently displayed alert.
var alertInfo: AlertInfo<LoginErrorType>? var alertInfo: AlertInfo<LoginScreenErrorType>?
} }
enum LoginViewAction { enum LoginScreenViewAction {
/// The user would like to select another server. /// The user would like to select another server.
case selectServer case selectServer
/// Parse the username to detect if a homeserver is included. /// Parse the username to detect if a homeserver is included.
@@ -89,7 +89,7 @@ enum LoginViewAction {
case continueWithOIDC case continueWithOIDC
} }
enum LoginErrorType: Hashable { enum LoginScreenErrorType: Hashable {
/// A specific error message shown in an alert. /// A specific error message shown in an alert.
case alert(String) case alert(String)
/// Looking up the homeserver from the username failed. /// Looking up the homeserver from the username failed.
@@ -16,19 +16,19 @@
import SwiftUI import SwiftUI
typealias LoginViewModelType = StateStoreViewModel<LoginViewState, LoginViewAction> typealias LoginScreenViewModelType = StateStoreViewModel<LoginScreenViewState, LoginScreenViewAction>
class LoginViewModel: LoginViewModelType, LoginViewModelProtocol { class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtocol {
var callback: (@MainActor (LoginViewModelAction) -> Void)? var callback: (@MainActor (LoginScreenViewModelAction) -> Void)?
init(homeserver: LoginHomeserver) { init(homeserver: LoginHomeserver) {
let bindings = LoginBindings() let bindings = LoginScreenBindings()
let viewState = LoginViewState(homeserver: homeserver, bindings: bindings) let viewState = LoginScreenViewState(homeserver: homeserver, bindings: bindings)
super.init(initialViewState: viewState) super.init(initialViewState: viewState)
} }
override func process(viewAction: LoginViewAction) { override func process(viewAction: LoginScreenViewAction) {
switch viewAction { switch viewAction {
case .selectServer: case .selectServer:
callback?(.selectServer) callback?(.selectServer)
@@ -52,7 +52,7 @@ class LoginViewModel: LoginViewModelType, LoginViewModelProtocol {
state.homeserver = homeserver state.homeserver = homeserver
} }
func displayError(_ type: LoginErrorType) { func displayError(_ type: LoginScreenErrorType) {
switch type { switch type {
case .alert(let message): case .alert(let message):
state.bindings.alertInfo = AlertInfo(id: type, state.bindings.alertInfo = AlertInfo(id: type,
@@ -17,9 +17,9 @@
import Foundation import Foundation
@MainActor @MainActor
protocol LoginViewModelProtocol { protocol LoginScreenViewModelProtocol {
var callback: (@MainActor (LoginViewModelAction) -> Void)? { get set } var callback: (@MainActor (LoginScreenViewModelAction) -> Void)? { get set }
var context: LoginViewModelType.Context { get } var context: LoginScreenViewModelType.Context { get }
/// Update the view to reflect that a new homeserver is being loaded. /// Update the view to reflect that a new homeserver is being loaded.
/// - Parameter isLoading: Whether or not the homeserver is being loaded. /// - Parameter isLoading: Whether or not the homeserver is being loaded.
@@ -31,5 +31,5 @@ protocol LoginViewModelProtocol {
/// Display an error to the user. /// Display an error to the user.
/// - Parameter type: The type of error to be displayed. /// - Parameter type: The type of error to be displayed.
func displayError(_ type: LoginErrorType) func displayError(_ type: LoginScreenErrorType)
} }
@@ -22,7 +22,7 @@ struct LoginScreen: View {
/// The focus state of the password text field. /// The focus state of the password text field.
@FocusState private var isPasswordFocused: Bool @FocusState private var isPasswordFocused: Bool
@ObservedObject var context: LoginViewModel.Context @ObservedObject var context: LoginScreenViewModel.Context
var body: some View { var body: some View {
ScrollView { ScrollView {
@@ -146,26 +146,26 @@ struct LoginScreen: View {
// MARK: - Previews // MARK: - Previews
struct Login_Previews: PreviewProvider { struct LoginScreen_Previews: PreviewProvider {
static let credentialsViewModel: LoginViewModel = { static let credentialsViewModel: LoginScreenViewModel = {
let viewModel = LoginViewModel(homeserver: .mockMatrixDotOrg) let viewModel = LoginScreenViewModel(homeserver: .mockMatrixDotOrg)
viewModel.context.username = "alice" viewModel.context.username = "alice"
viewModel.context.password = "password" viewModel.context.password = "password"
return viewModel return viewModel
}() }()
static var previews: some View { static var previews: some View {
screen(for: LoginViewModel(homeserver: .mockMatrixDotOrg)) screen(for: LoginScreenViewModel(homeserver: .mockMatrixDotOrg))
.previewDisplayName("matrix.org") .previewDisplayName("matrix.org")
screen(for: credentialsViewModel) screen(for: credentialsViewModel)
.previewDisplayName("Credentials Entered") .previewDisplayName("Credentials Entered")
screen(for: LoginViewModel(homeserver: .mockOIDC)) screen(for: LoginScreenViewModel(homeserver: .mockOIDC))
.previewDisplayName("OIDC") .previewDisplayName("OIDC")
screen(for: LoginViewModel(homeserver: .mockUnsupported)) screen(for: LoginScreenViewModel(homeserver: .mockUnsupported))
.previewDisplayName("Unsupported") .previewDisplayName("Unsupported")
} }
static func screen(for viewModel: LoginViewModel) -> some View { static func screen(for viewModel: LoginScreenViewModel) -> some View {
NavigationStack { NavigationStack {
LoginScreen(context: viewModel.context) LoginScreen(context: viewModel.context)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
@@ -23,22 +23,22 @@ enum MockServerSelectionScreenState: CaseIterable {
case nonModal case nonModal
/// Generate the view struct for the screen state. /// Generate the view struct for the screen state.
@MainActor var viewModel: ServerSelectionViewModel { @MainActor var viewModel: ServerSelectionScreenViewModel {
switch self { switch self {
case .matrix: case .matrix:
return ServerSelectionViewModel(homeserverAddress: "https://matrix.org", return ServerSelectionScreenViewModel(homeserverAddress: "https://matrix.org",
isModallyPresented: true) isModallyPresented: true)
case .emptyAddress: case .emptyAddress:
return ServerSelectionViewModel(homeserverAddress: "", return ServerSelectionScreenViewModel(homeserverAddress: "",
isModallyPresented: true) isModallyPresented: true)
case .invalidAddress: case .invalidAddress:
let viewModel = ServerSelectionViewModel(homeserverAddress: "thisisbad", let viewModel = ServerSelectionScreenViewModel(homeserverAddress: "thisisbad",
isModallyPresented: true) isModallyPresented: true)
viewModel.displayError(.footerMessage(L10n.errorUnknown)) viewModel.displayError(.footerMessage(L10n.errorUnknown))
return viewModel return viewModel
case .nonModal: case .nonModal:
return ServerSelectionViewModel(homeserverAddress: "https://matrix.org", return ServerSelectionScreenViewModel(homeserverAddress: "https://matrix.org",
isModallyPresented: false) isModallyPresented: false)
} }
} }
} }
@@ -16,7 +16,7 @@
import SwiftUI import SwiftUI
struct ServerSelectionCoordinatorParameters { struct ServerSelectionScreenCoordinatorParameters {
/// The service used to authenticate the user. /// The service used to authenticate the user.
let authenticationService: AuthenticationServiceProxyProtocol let authenticationService: AuthenticationServiceProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol let userIndicatorController: UserIndicatorControllerProtocol
@@ -24,23 +24,23 @@ struct ServerSelectionCoordinatorParameters {
let isModallyPresented: Bool let isModallyPresented: Bool
} }
enum ServerSelectionCoordinatorAction { enum ServerSelectionScreenCoordinatorAction {
case updated case updated
case dismiss case dismiss
} }
final class ServerSelectionCoordinator: CoordinatorProtocol { final class ServerSelectionScreenCoordinator: CoordinatorProtocol {
private let parameters: ServerSelectionCoordinatorParameters private let parameters: ServerSelectionScreenCoordinatorParameters
private let userIndicatorController: UserIndicatorControllerProtocol private let userIndicatorController: UserIndicatorControllerProtocol
private var viewModel: ServerSelectionViewModelProtocol private var viewModel: ServerSelectionScreenViewModelProtocol
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService } private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
var callback: (@MainActor (ServerSelectionCoordinatorAction) -> Void)? var callback: (@MainActor (ServerSelectionScreenCoordinatorAction) -> Void)?
init(parameters: ServerSelectionCoordinatorParameters) { init(parameters: ServerSelectionScreenCoordinatorParameters) {
self.parameters = parameters self.parameters = parameters
viewModel = ServerSelectionViewModel(homeserverAddress: parameters.authenticationService.homeserver.address, viewModel = ServerSelectionScreenViewModel(homeserverAddress: parameters.authenticationService.homeserver.address,
isModallyPresented: parameters.isModallyPresented) isModallyPresented: parameters.isModallyPresented)
userIndicatorController = parameters.userIndicatorController userIndicatorController = parameters.userIndicatorController
} }
@@ -16,14 +16,14 @@
import Foundation import Foundation
enum ServerSelectionViewModelAction { enum ServerSelectionScreenViewModelAction {
/// The user would like to use the homeserver at the given address. /// The user would like to use the homeserver at the given address.
case confirm(homeserverAddress: String) case confirm(homeserverAddress: String)
/// Dismiss the view without using the entered address. /// Dismiss the view without using the entered address.
case dismiss case dismiss
} }
struct ServerSelectionViewState: BindableState { struct ServerSelectionScreenViewState: BindableState {
/// The message to be shown in the text field footer when no error has occurred. /// The message to be shown in the text field footer when no error has occurred.
private let regularFooterMessage = { private let regularFooterMessage = {
let linkPlaceholder = "{link}" let linkPlaceholder = "{link}"
@@ -35,7 +35,7 @@ struct ServerSelectionViewState: BindableState {
}() }()
/// View state that can be bound to from SwiftUI. /// View state that can be bound to from SwiftUI.
var bindings: ServerSelectionBindings var bindings: ServerSelectionScreenBindings
/// An error message to be shown in the text field footer. /// An error message to be shown in the text field footer.
var footerErrorMessage: String? var footerErrorMessage: String?
/// Whether the screen is presented modally or within a navigation stack. /// Whether the screen is presented modally or within a navigation stack.
@@ -62,14 +62,14 @@ struct ServerSelectionViewState: BindableState {
} }
} }
struct ServerSelectionBindings { struct ServerSelectionScreenBindings {
/// The homeserver address input by the user. /// The homeserver address input by the user.
var homeserverAddress: String var homeserverAddress: String
/// Information describing the currently displayed alert. /// Information describing the currently displayed alert.
var alertInfo: AlertInfo<ServerSelectionErrorType>? var alertInfo: AlertInfo<ServerSelectionScreenErrorType>?
} }
enum ServerSelectionViewAction { enum ServerSelectionScreenViewAction {
/// The user would like to use the homeserver at the input address. /// The user would like to use the homeserver at the input address.
case confirm case confirm
/// Dismiss the view without using the entered address. /// Dismiss the view without using the entered address.
@@ -78,7 +78,7 @@ enum ServerSelectionViewAction {
case clearFooterError case clearFooterError
} }
enum ServerSelectionErrorType: Hashable { enum ServerSelectionScreenErrorType: Hashable {
/// An error message to be shown in the text field footer. /// An error message to be shown in the text field footer.
case footerMessage(String) case footerMessage(String)
/// An alert that allows the user to learn about sliding sync. /// An alert that allows the user to learn about sliding sync.
@@ -16,19 +16,19 @@
import SwiftUI import SwiftUI
typealias ServerSelectionViewModelType = StateStoreViewModel<ServerSelectionViewState, ServerSelectionViewAction> typealias ServerSelectionScreenViewModelType = StateStoreViewModel<ServerSelectionScreenViewState, ServerSelectionScreenViewAction>
class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionViewModelProtocol { class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, ServerSelectionScreenViewModelProtocol {
var callback: (@MainActor (ServerSelectionViewModelAction) -> Void)? var callback: (@MainActor (ServerSelectionScreenViewModelAction) -> Void)?
init(homeserverAddress: String, isModallyPresented: Bool) { init(homeserverAddress: String, isModallyPresented: Bool) {
let bindings = ServerSelectionBindings(homeserverAddress: homeserverAddress) let bindings = ServerSelectionScreenBindings(homeserverAddress: homeserverAddress)
super.init(initialViewState: ServerSelectionViewState(bindings: bindings, super.init(initialViewState: ServerSelectionScreenViewState(bindings: bindings,
isModallyPresented: isModallyPresented)) isModallyPresented: isModallyPresented))
} }
override func process(viewAction: ServerSelectionViewAction) { override func process(viewAction: ServerSelectionScreenViewAction) {
switch viewAction { switch viewAction {
case .confirm: case .confirm:
callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress)) callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress))
@@ -39,7 +39,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
} }
} }
func displayError(_ type: ServerSelectionErrorType) { func displayError(_ type: ServerSelectionScreenErrorType) {
switch type { switch type {
case .footerMessage(let message): case .footerMessage(let message):
withElementAnimation { withElementAnimation {
@@ -17,10 +17,10 @@
import Foundation import Foundation
@MainActor @MainActor
protocol ServerSelectionViewModelProtocol { protocol ServerSelectionScreenViewModelProtocol {
var callback: (@MainActor (ServerSelectionViewModelAction) -> Void)? { get set } var callback: (@MainActor (ServerSelectionScreenViewModelAction) -> Void)? { get set }
var context: ServerSelectionViewModelType.Context { get } var context: ServerSelectionScreenViewModelType.Context { get }
/// Displays an error to the user. /// Displays an error to the user.
func displayError(_ type: ServerSelectionErrorType) func displayError(_ type: ServerSelectionScreenErrorType)
} }
@@ -17,7 +17,7 @@
import SwiftUI import SwiftUI
struct ServerSelectionScreen: View { struct ServerSelectionScreen: View {
@ObservedObject var context: ServerSelectionViewModel.Context @ObservedObject var context: ServerSelectionScreenViewModel.Context
var body: some View { var body: some View {
ScrollView { ScrollView {
@@ -1,68 +0,0 @@
//
// 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 Foundation
import SwiftUI
/// Using an enum for the screen allows you define the different state cases with
/// the relevant associated data for each case.
enum MockSoftLogoutScreenState: String, CaseIterable {
// A case for each state you want to represent
// with specific, minimal associated data that will allow you
// mock that screen.
case emptyPassword
case enteredPassword
case oidc
case unsupported
case keyBackupNeeded
/// Generate the view struct for the screen state.
@MainActor var viewModel: SoftLogoutViewModel {
let credentials = SoftLogoutCredentials(userId: "@mock:matrix.org",
homeserverName: "matrix.org",
userDisplayName: "mock",
deviceId: nil)
switch self {
case .emptyPassword:
return SoftLogoutViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: false)
case .enteredPassword:
return SoftLogoutViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: false,
password: "12345678")
case .oidc:
return SoftLogoutViewModel(credentials: credentials,
homeserver: .mockOIDC,
keyBackupNeeded: false)
case .unsupported:
return SoftLogoutViewModel(credentials: credentials,
homeserver: .mockUnsupported,
keyBackupNeeded: false)
case .keyBackupNeeded:
return SoftLogoutViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true)
}
}
}
extension MockSoftLogoutScreenState: Identifiable {
var id: String {
rawValue
}
}
@@ -0,0 +1,68 @@
//
// 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 Foundation
import SwiftUI
/// Using an enum for the screen allows you define the different state cases with
/// the relevant associated data for each case.
enum MockSoftLogoutScreenState: String, CaseIterable {
// A case for each state you want to represent
// with specific, minimal associated data that will allow you
// mock that screen.
case emptyPassword
case enteredPassword
case oidc
case unsupported
case keyBackupNeeded
/// Generate the view struct for the screen state.
@MainActor var viewModel: SoftLogoutScreenViewModel {
let credentials = SoftLogoutScreenCredentials(userId: "@mock:matrix.org",
homeserverName: "matrix.org",
userDisplayName: "mock",
deviceId: nil)
switch self {
case .emptyPassword:
return SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: false)
case .enteredPassword:
return SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: false,
password: "12345678")
case .oidc:
return SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockOIDC,
keyBackupNeeded: false)
case .unsupported:
return SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockUnsupported,
keyBackupNeeded: false)
case .keyBackupNeeded:
return SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true)
}
}
}
extension MockSoftLogoutScreenState: Identifiable {
var id: String {
rawValue
}
}
@@ -17,13 +17,13 @@
import AppAuth import AppAuth
import SwiftUI import SwiftUI
struct SoftLogoutCoordinatorParameters { struct SoftLogoutScreenCoordinatorParameters {
let authenticationService: AuthenticationServiceProxyProtocol let authenticationService: AuthenticationServiceProxyProtocol
let credentials: SoftLogoutCredentials let credentials: SoftLogoutScreenCredentials
let keyBackupNeeded: Bool let keyBackupNeeded: Bool
} }
enum SoftLogoutCoordinatorResult: CustomStringConvertible { enum SoftLogoutScreenCoordinatorResult: CustomStringConvertible {
/// Login was successful. /// Login was successful.
case signedIn(UserSessionProtocol) case signedIn(UserSessionProtocol)
/// Clear all user data /// Clear all user data
@@ -40,26 +40,26 @@ enum SoftLogoutCoordinatorResult: CustomStringConvertible {
} }
} }
final class SoftLogoutCoordinator: CoordinatorProtocol { final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
private let parameters: SoftLogoutCoordinatorParameters private let parameters: SoftLogoutScreenCoordinatorParameters
private var viewModel: SoftLogoutViewModelProtocol private var viewModel: SoftLogoutScreenViewModelProtocol
private let hostingController: UIViewController private let hostingController: UIViewController
/// Passed to the OIDC service to provide a view controller from which to present the authentication session. /// Passed to the OIDC service to provide a view controller from which to present the authentication session.
private let oidcUserAgent: OIDExternalUserAgentIOS? private let oidcUserAgent: OIDExternalUserAgentIOS?
/// The wizard used to handle the registration flow. /// The wizard used to handle the registration flow.
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService } private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
var callback: (@MainActor (SoftLogoutCoordinatorResult) -> Void)?
@MainActor init(parameters: SoftLogoutCoordinatorParameters) { var callback: (@MainActor (SoftLogoutScreenCoordinatorResult) -> Void)?
@MainActor init(parameters: SoftLogoutScreenCoordinatorParameters) {
self.parameters = parameters self.parameters = parameters
let homeserver = parameters.authenticationService.homeserver let homeserver = parameters.authenticationService.homeserver
viewModel = SoftLogoutViewModel(credentials: parameters.credentials, viewModel = SoftLogoutScreenViewModel(credentials: parameters.credentials,
homeserver: homeserver, homeserver: homeserver,
keyBackupNeeded: parameters.keyBackupNeeded) keyBackupNeeded: parameters.keyBackupNeeded)
hostingController = UIHostingController(rootView: SoftLogoutScreen(context: viewModel.context)) hostingController = UIHostingController(rootView: SoftLogoutScreen(context: viewModel.context))
oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController) oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController)
@@ -16,14 +16,14 @@
import SwiftUI import SwiftUI
struct SoftLogoutCredentials { struct SoftLogoutScreenCredentials {
let userId: String let userId: String
let homeserverName: String let homeserverName: String
let userDisplayName: String let userDisplayName: String
let deviceId: String? let deviceId: String?
} }
enum SoftLogoutViewModelAction: CustomStringConvertible { enum SoftLogoutScreenViewModelAction: CustomStringConvertible {
/// Login with password /// Login with password
case login(String) case login(String)
/// Forgot password /// Forgot password
@@ -48,9 +48,9 @@ enum SoftLogoutViewModelAction: CustomStringConvertible {
} }
} }
struct SoftLogoutViewState: BindableState { struct SoftLogoutScreenViewState: BindableState {
/// Soft logout credentials /// Soft logout credentials
var credentials: SoftLogoutCredentials var credentials: SoftLogoutScreenCredentials
/// Data about the selected homeserver. /// Data about the selected homeserver.
var homeserver: LoginHomeserver var homeserver: LoginHomeserver
@@ -59,7 +59,7 @@ struct SoftLogoutViewState: BindableState {
var keyBackupNeeded: Bool var keyBackupNeeded: Bool
/// View state that can be bound to from SwiftUI. /// View state that can be bound to from SwiftUI.
var bindings: SoftLogoutBindings var bindings: SoftLogoutScreenBindings
/// The types of login supported by the homeserver. /// The types of login supported by the homeserver.
var loginMode: LoginMode { homeserver.loginMode } var loginMode: LoginMode { homeserver.loginMode }
@@ -75,14 +75,14 @@ struct SoftLogoutViewState: BindableState {
} }
} }
struct SoftLogoutBindings { struct SoftLogoutScreenBindings {
/// The password input by the user. /// The password input by the user.
var password: String var password: String
/// Information describing the currently displayed alert. /// Information describing the currently displayed alert.
var alertInfo: AlertInfo<SoftLogoutErrorType>? var alertInfo: AlertInfo<SoftLogoutScreenErrorType>?
} }
enum SoftLogoutViewAction { enum SoftLogoutScreenViewAction {
/// Login. /// Login.
case login case login
/// Forgot password /// Forgot password
@@ -93,7 +93,7 @@ enum SoftLogoutViewAction {
case continueWithOIDC case continueWithOIDC
} }
enum SoftLogoutErrorType: Hashable { enum SoftLogoutScreenErrorType: Hashable {
/// A specific error message shown in an alert. /// A specific error message shown in an alert.
case alert(String) case alert(String)
/// An unknown error occurred. /// An unknown error occurred.
@@ -16,24 +16,24 @@
import SwiftUI import SwiftUI
typealias SoftLogoutViewModelType = StateStoreViewModel<SoftLogoutViewState, SoftLogoutViewAction> typealias SoftLogoutScreenViewModelType = StateStoreViewModel<SoftLogoutScreenViewState, SoftLogoutScreenViewAction>
class SoftLogoutViewModel: SoftLogoutViewModelType, SoftLogoutViewModelProtocol { class SoftLogoutScreenViewModel: SoftLogoutScreenViewModelType, SoftLogoutScreenViewModelProtocol {
var callback: (@MainActor (SoftLogoutViewModelAction) -> Void)? var callback: (@MainActor (SoftLogoutScreenViewModelAction) -> Void)?
init(credentials: SoftLogoutCredentials, init(credentials: SoftLogoutScreenCredentials,
homeserver: LoginHomeserver, homeserver: LoginHomeserver,
keyBackupNeeded: Bool, keyBackupNeeded: Bool,
password: String = "") { password: String = "") {
let bindings = SoftLogoutBindings(password: password) let bindings = SoftLogoutScreenBindings(password: password)
let viewState = SoftLogoutViewState(credentials: credentials, let viewState = SoftLogoutScreenViewState(credentials: credentials,
homeserver: homeserver, homeserver: homeserver,
keyBackupNeeded: keyBackupNeeded, keyBackupNeeded: keyBackupNeeded,
bindings: bindings) bindings: bindings)
super.init(initialViewState: viewState) super.init(initialViewState: viewState)
} }
override func process(viewAction: SoftLogoutViewAction) { override func process(viewAction: SoftLogoutScreenViewAction) {
switch viewAction { switch viewAction {
case .login: case .login:
callback?(.login(state.bindings.password)) callback?(.login(state.bindings.password))
@@ -46,7 +46,7 @@ class SoftLogoutViewModel: SoftLogoutViewModelType, SoftLogoutViewModelProtocol
} }
} }
@MainActor func displayError(_ type: SoftLogoutErrorType) { @MainActor func displayError(_ type: SoftLogoutScreenErrorType) {
switch type { switch type {
case .alert(let message): case .alert(let message):
state.bindings.alertInfo = AlertInfo(id: type, state.bindings.alertInfo = AlertInfo(id: type,
@@ -16,10 +16,10 @@
import Foundation import Foundation
protocol SoftLogoutViewModelProtocol { protocol SoftLogoutScreenViewModelProtocol {
var callback: (@MainActor (SoftLogoutViewModelAction) -> Void)? { get set } var callback: (@MainActor (SoftLogoutScreenViewModelAction) -> Void)? { get set }
var context: SoftLogoutViewModelType.Context { get } var context: SoftLogoutScreenViewModelType.Context { get }
/// Display an error to the user. /// Display an error to the user.
@MainActor func displayError(_ type: SoftLogoutErrorType) @MainActor func displayError(_ type: SoftLogoutScreenErrorType)
} }
@@ -22,7 +22,7 @@ struct SoftLogoutScreen: View {
/// The focus state of the password text field. /// The focus state of the password text field.
@FocusState private var isPasswordFocused: Bool @FocusState private var isPasswordFocused: Bool
@ObservedObject var context: SoftLogoutViewModel.Context @ObservedObject var context: SoftLogoutScreenViewModel.Context
var body: some View { var body: some View {
ScrollView { ScrollView {
@@ -171,14 +171,14 @@ struct SoftLogoutScreen: View {
// MARK: - Previews // MARK: - Previews
struct SoftLogout_Previews: PreviewProvider { struct SoftLogoutScreen_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ForEach(MockSoftLogoutScreenState.allCases) { state in ForEach(MockSoftLogoutScreenState.allCases) { state in
screen(for: state.viewModel) screen(for: state.viewModel)
} }
} }
static func screen(for viewModel: SoftLogoutViewModel) -> some View { static func screen(for viewModel: SoftLogoutScreenViewModel) -> some View {
NavigationStack { NavigationStack {
SoftLogoutScreen(context: viewModel.context) SoftLogoutScreen(context: viewModel.context)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
@@ -17,32 +17,32 @@
import Combine import Combine
import SwiftUI import SwiftUI
struct StartChatCoordinatorParameters { struct StartChatScreenCoordinatorParameters {
let userSession: UserSessionProtocol let userSession: UserSessionProtocol
weak var userIndicatorController: UserIndicatorControllerProtocol? weak var userIndicatorController: UserIndicatorControllerProtocol?
let navigationStackCoordinator: NavigationStackCoordinator? let navigationStackCoordinator: NavigationStackCoordinator?
let userDiscoveryService: UserDiscoveryServiceProtocol let userDiscoveryService: UserDiscoveryServiceProtocol
} }
enum StartChatCoordinatorAction { enum StartChatScreenCoordinatorAction {
case close case close
case openRoom(withIdentifier: String) case openRoom(withIdentifier: String)
} }
final class StartChatCoordinator: CoordinatorProtocol { final class StartChatScreenCoordinator: CoordinatorProtocol {
private let parameters: StartChatCoordinatorParameters private let parameters: StartChatScreenCoordinatorParameters
private var viewModel: StartChatViewModelProtocol private var viewModel: StartChatScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<StartChatCoordinatorAction, Never> = .init() private let actionsSubject: PassthroughSubject<StartChatScreenCoordinatorAction, Never> = .init()
private var cancellables: Set<AnyCancellable> = .init() private var cancellables: Set<AnyCancellable> = .init()
var actions: AnyPublisher<StartChatCoordinatorAction, Never> { var actions: AnyPublisher<StartChatScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
init(parameters: StartChatCoordinatorParameters) { init(parameters: StartChatScreenCoordinatorParameters) {
self.parameters = parameters self.parameters = parameters
viewModel = StartChatViewModel(userSession: parameters.userSession, userIndicatorController: parameters.userIndicatorController, userDiscoveryService: parameters.userDiscoveryService) viewModel = StartChatScreenViewModel(userSession: parameters.userSession, userIndicatorController: parameters.userIndicatorController, userDiscoveryService: parameters.userDiscoveryService)
} }
func start() { func start() {
@@ -16,18 +16,18 @@
import Foundation import Foundation
enum StartChatErrorType: Error { enum StartChatScreenErrorType: Error {
case failedCreatingRoom case failedCreatingRoom
case unknown case unknown
} }
enum StartChatViewModelAction { enum StartChatScreenViewModelAction {
case close case close
case createRoom case createRoom
case openRoom(withIdentifier: String) case openRoom(withIdentifier: String)
} }
struct StartChatViewState: BindableState { struct StartChatScreenViewState: BindableState {
var bindings = StartChatScreenViewStateBindings() var bindings = StartChatScreenViewStateBindings()
var usersSection: UserDiscoverySection = .init(type: .suggestions, users: []) var usersSection: UserDiscoverySection = .init(type: .suggestions, users: [])
@@ -44,10 +44,10 @@ struct StartChatScreenViewStateBindings {
var searchQuery = "" var searchQuery = ""
/// Information describing the currently displayed alert. /// Information describing the currently displayed alert.
var alertInfo: AlertInfo<StartChatErrorType>? var alertInfo: AlertInfo<StartChatScreenErrorType>?
} }
enum StartChatViewAction { enum StartChatScreenViewAction {
case close case close
case createRoom case createRoom
case inviteFriends case inviteFriends
@@ -17,14 +17,14 @@
import Combine import Combine
import SwiftUI import SwiftUI
typealias StartChatViewModelType = StateStoreViewModel<StartChatViewState, StartChatViewAction> typealias StartChatScreenViewModelType = StateStoreViewModel<StartChatScreenViewState, StartChatScreenViewAction>
class StartChatViewModel: StartChatViewModelType, StartChatViewModelProtocol { class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenViewModelProtocol {
private let userSession: UserSessionProtocol private let userSession: UserSessionProtocol
private let actionsSubject: PassthroughSubject<StartChatViewModelAction, Never> = .init() private let actionsSubject: PassthroughSubject<StartChatScreenViewModelAction, Never> = .init()
private let userDiscoveryService: UserDiscoveryServiceProtocol private let userDiscoveryService: UserDiscoveryServiceProtocol
var actions: AnyPublisher<StartChatViewModelAction, Never> { var actions: AnyPublisher<StartChatScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
@@ -34,14 +34,14 @@ class StartChatViewModel: StartChatViewModelType, StartChatViewModelProtocol {
self.userSession = userSession self.userSession = userSession
self.userIndicatorController = userIndicatorController self.userIndicatorController = userIndicatorController
self.userDiscoveryService = userDiscoveryService self.userDiscoveryService = userDiscoveryService
super.init(initialViewState: StartChatViewState(), imageProvider: userSession.mediaProvider) super.init(initialViewState: StartChatScreenViewState(), imageProvider: userSession.mediaProvider)
setupBindings() setupBindings()
} }
// MARK: - Public // MARK: - Public
override func process(viewAction: StartChatViewAction) { override func process(viewAction: StartChatScreenViewAction) {
switch viewAction { switch viewAction {
case .close: case .close:
actionsSubject.send(.close) actionsSubject.send(.close)
@@ -17,7 +17,7 @@
import Combine import Combine
@MainActor @MainActor
protocol StartChatViewModelProtocol { protocol StartChatScreenViewModelProtocol {
var actions: AnyPublisher<StartChatViewModelAction, Never> { get } var actions: AnyPublisher<StartChatScreenViewModelAction, Never> { get }
var context: StartChatViewModelType.Context { get } var context: StartChatScreenViewModelType.Context { get }
} }
@@ -17,7 +17,7 @@
import SwiftUI import SwiftUI
struct StartChatScreen: View { struct StartChatScreen: View {
@ObservedObject var context: StartChatViewModel.Context @ObservedObject var context: StartChatScreenViewModel.Context
var body: some View { var body: some View {
Form { Form {
@@ -130,14 +130,14 @@ struct StartChatScreen: View {
// MARK: - Previews // MARK: - Previews
struct StartChat_Previews: PreviewProvider { struct StartChatScreen_Previews: PreviewProvider {
static let viewModel = { static let viewModel = {
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"), let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"),
mediaProvider: MockMediaProvider()) mediaProvider: MockMediaProvider())
let userDiscoveryService = UserDiscoveryServiceMock() let userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.fetchSuggestionsReturnValue = .success([.mockAlice]) userDiscoveryService.fetchSuggestionsReturnValue = .success([.mockAlice])
userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice]) userDiscoveryService.searchProfilesWithReturnValue = .success([.mockAlice])
let viewModel = StartChatViewModel(userSession: userSession, userIndicatorController: nil, userDiscoveryService: userDiscoveryService) let viewModel = StartChatScreenViewModel(userSession: userSession, userIndicatorController: nil, userDiscoveryService: userDiscoveryService)
return viewModel return viewModel
}() }()
@@ -289,8 +289,8 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
let userIndicatorController = UserIndicatorController(rootCoordinator: startChatNavigationStackCoordinator) let userIndicatorController = UserIndicatorController(rootCoordinator: startChatNavigationStackCoordinator)
let userDiscoveryService = UserDiscoveryService(clientProxy: userSession.clientProxy) let userDiscoveryService = UserDiscoveryService(clientProxy: userSession.clientProxy)
let parameters = StartChatCoordinatorParameters(userSession: userSession, userIndicatorController: userIndicatorController, navigationStackCoordinator: startChatNavigationStackCoordinator, userDiscoveryService: userDiscoveryService) let parameters = StartChatScreenCoordinatorParameters(userSession: userSession, userIndicatorController: userIndicatorController, navigationStackCoordinator: startChatNavigationStackCoordinator, userDiscoveryService: userDiscoveryService)
let coordinator = StartChatCoordinator(parameters: parameters) let coordinator = StartChatScreenCoordinator(parameters: parameters)
coordinator.actions.sink { [weak self] action in coordinator.actions.sink { [weak self] action in
guard let self else { return } guard let self else { return }
switch action { switch action {
@@ -62,21 +62,21 @@ class MockScreen: Identifiable {
switch id { switch id {
case .login: case .login:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(), let coordinator = LoginScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
navigationStackCoordinator: navigationStackCoordinator)) navigationStackCoordinator: navigationStackCoordinator))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .serverSelection: case .serverSelection:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(), let coordinator = ServerSelectionScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userIndicatorController: MockUserIndicatorController(), userIndicatorController: MockUserIndicatorController(),
isModallyPresented: true)) isModallyPresented: true))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .serverSelectionNonModal: case .serverSelectionNonModal:
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(), return ServerSelectionScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userIndicatorController: MockUserIndicatorController(), userIndicatorController: MockUserIndicatorController(),
isModallyPresented: false)) isModallyPresented: false))
case .analyticsPrompt: case .analyticsPrompt:
return AnalyticsPromptScreenCoordinator() return AnalyticsPromptScreenCoordinator()
case .analyticsSettingsScreen: case .analyticsSettingsScreen:
@@ -92,13 +92,13 @@ class MockScreen: Identifiable {
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .softLogout: case .softLogout:
let credentials = SoftLogoutCredentials(userId: "@mock:matrix.org", let credentials = SoftLogoutScreenCredentials(userId: "@mock:matrix.org",
homeserverName: "matrix.org", homeserverName: "matrix.org",
userDisplayName: "mock", userDisplayName: "mock",
deviceId: "ABCDEFGH") deviceId: "ABCDEFGH")
return SoftLogoutCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(), return SoftLogoutScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
credentials: credentials, credentials: credentials,
keyBackupNeeded: false)) keyBackupNeeded: false))
case .simpleRegular: case .simpleRegular:
return TemplateScreenCoordinator(parameters: .init(promptType: .regular)) return TemplateScreenCoordinator(parameters: .init(promptType: .regular))
case .simpleUpgrade: case .simpleUpgrade:
@@ -321,8 +321,8 @@ class MockScreen: Identifiable {
userDiscoveryMock.fetchSuggestionsReturnValue = .success([.mockAlice, .mockBob, .mockCharlie]) userDiscoveryMock.fetchSuggestionsReturnValue = .success([.mockAlice, .mockBob, .mockCharlie])
userDiscoveryMock.searchProfilesWithReturnValue = .success([]) userDiscoveryMock.searchProfilesWithReturnValue = .success([])
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"), mediaProvider: MockMediaProvider()) let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"), mediaProvider: MockMediaProvider())
let parameters: StartChatCoordinatorParameters = .init(userSession: userSession, navigationStackCoordinator: navigationStackCoordinator, userDiscoveryService: userDiscoveryMock) let parameters: StartChatScreenCoordinatorParameters = .init(userSession: userSession, navigationStackCoordinator: navigationStackCoordinator, userDiscoveryService: userDiscoveryMock)
let coordinator = StartChatCoordinator(parameters: parameters) let coordinator = StartChatScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .startChatWithSearchResults: case .startChatWithSearchResults:
@@ -332,7 +332,7 @@ class MockScreen: Identifiable {
userDiscoveryMock.fetchSuggestionsReturnValue = .success([]) userDiscoveryMock.fetchSuggestionsReturnValue = .success([])
userDiscoveryMock.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby]) userDiscoveryMock.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby])
let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())
let coordinator = StartChatCoordinator(parameters: .init(userSession: userSession, navigationStackCoordinator: navigationStackCoordinator, userDiscoveryService: userDiscoveryMock)) let coordinator = StartChatScreenCoordinator(parameters: .init(userSession: userSession, navigationStackCoordinator: navigationStackCoordinator, userDiscoveryService: userDiscoveryMock))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .roomMemberDetailsAccountOwner: case .roomMemberDetailsAccountOwner:
+4 -4
View File
@@ -21,11 +21,11 @@ import XCTest
@MainActor @MainActor
class LoginViewModelTests: XCTestCase { class LoginViewModelTests: XCTestCase {
let defaultHomeserver = LoginHomeserver.mockMatrixDotOrg let defaultHomeserver = LoginHomeserver.mockMatrixDotOrg
var viewModel: LoginViewModelProtocol! var viewModel: LoginScreenViewModelProtocol!
var context: LoginViewModelType.Context! var context: LoginScreenViewModelType.Context!
@MainActor override func setUp() async throws { @MainActor override func setUp() async throws {
viewModel = LoginViewModel(homeserver: defaultHomeserver) viewModel = LoginScreenViewModel(homeserver: defaultHomeserver)
context = viewModel.context context = viewModel.context
} }
@@ -135,7 +135,7 @@ class LoginViewModelTests: XCTestCase {
func testLogsForPassword() { func testLogsForPassword() {
// Given the coordinator and view model results that contain passwords. // Given the coordinator and view model results that contain passwords.
let password = "supersecretpassword" let password = "supersecretpassword"
let viewModelAction: LoginViewModelAction = .login(username: "Alice", password: password) let viewModelAction: LoginScreenViewModelAction = .login(username: "Alice", password: password)
// When creating a string representation of those results (e.g. for logging). // When creating a string representation of those results (e.g. for logging).
let viewModelActionString = "\(viewModelAction)" let viewModelActionString = "\(viewModelAction)"
@@ -24,11 +24,11 @@ class ServerSelectionViewModelTests: XCTestCase {
static let counterInitialValue = 0 static let counterInitialValue = 0
} }
var viewModel: ServerSelectionViewModelProtocol! var viewModel: ServerSelectionScreenViewModelProtocol!
var context: ServerSelectionViewModelType.Context! var context: ServerSelectionScreenViewModelType.Context!
@MainActor override func setUp() { @MainActor override func setUp() {
viewModel = ServerSelectionViewModel(homeserverAddress: "", isModallyPresented: true) viewModel = ServerSelectionScreenViewModel(homeserverAddress: "", isModallyPresented: true)
context = viewModel.context context = viewModel.context
} }
@@ -19,17 +19,17 @@ import XCTest
@testable import ElementX @testable import ElementX
class SoftLogoutViewModelTests: XCTestCase { class SoftLogoutViewModelTests: XCTestCase {
let credentials = SoftLogoutCredentials(userId: "mock_user_id", let credentials = SoftLogoutScreenCredentials(userId: "mock_user_id",
homeserverName: "https://matrix.org", homeserverName: "https://matrix.org",
userDisplayName: "mock_username", userDisplayName: "mock_username",
deviceId: "ABCDEFGH") deviceId: "ABCDEFGH")
@MainActor func testInitialStateForMatrixOrg() { @MainActor func testInitialStateForMatrixOrg() {
let viewModel = SoftLogoutViewModel(credentials: credentials, let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg, homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true) keyBackupNeeded: true)
let context = viewModel.context let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email. // Given a view model where the user hasn't yet sent the verification email.
XCTAssert(context.password.isEmpty, "The view model should start with an empty password.") XCTAssert(context.password.isEmpty, "The view model should start with an empty password.")
XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.") XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.")
@@ -38,10 +38,10 @@ class SoftLogoutViewModelTests: XCTestCase {
} }
@MainActor func testInitialStateForMatrixOrgPasswordEntered() { @MainActor func testInitialStateForMatrixOrgPasswordEntered() {
let viewModel = SoftLogoutViewModel(credentials: credentials, let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg, homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true, keyBackupNeeded: true,
password: "12345678") password: "12345678")
let context = viewModel.context let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email. // Given a view model where the user hasn't yet sent the verification email.
@@ -49,13 +49,13 @@ class SoftLogoutViewModelTests: XCTestCase {
XCTAssertEqual(context.viewState.loginMode, .password, "The view model should show login form for the given homeserver.") XCTAssertEqual(context.viewState.loginMode, .password, "The view model should show login form for the given homeserver.")
XCTAssert(context.viewState.showRecoverEncryptionKeysMessage, "The view model should show recover encryption keys message.") XCTAssert(context.viewState.showRecoverEncryptionKeysMessage, "The view model should show recover encryption keys message.")
} }
@MainActor func testInitialStateForBasicServer() { @MainActor func testInitialStateForBasicServer() {
let viewModel = SoftLogoutViewModel(credentials: credentials, let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockBasicServer, homeserver: .mockBasicServer,
keyBackupNeeded: false) keyBackupNeeded: false)
let context = viewModel.context let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email. // Given a view model where the user hasn't yet sent the verification email.
XCTAssert(context.password.isEmpty, "The view model should start with an empty password.") XCTAssert(context.password.isEmpty, "The view model should start with an empty password.")
XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.") XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.")
@@ -64,22 +64,22 @@ class SoftLogoutViewModelTests: XCTestCase {
} }
@MainActor func testInitialStateForOIDC() { @MainActor func testInitialStateForOIDC() {
let viewModel = SoftLogoutViewModel(credentials: credentials, let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockOIDC, homeserver: .mockOIDC,
keyBackupNeeded: false) keyBackupNeeded: false)
let context = viewModel.context let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email. // Given a view model where the user hasn't yet sent the verification email.
XCTAssert(context.password.isEmpty, "The view model should start with an empty password.") XCTAssert(context.password.isEmpty, "The view model should start with an empty password.")
XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.") XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.")
XCTAssertTrue(context.viewState.loginMode.supportsOIDCFlow, "The view model should show OIDC button for the given homeserver.") XCTAssertTrue(context.viewState.loginMode.supportsOIDCFlow, "The view model should show OIDC button for the given homeserver.")
XCTAssertFalse(context.viewState.showRecoverEncryptionKeysMessage, "The view model should not show recover encryption keys message.") XCTAssertFalse(context.viewState.showRecoverEncryptionKeysMessage, "The view model should not show recover encryption keys message.")
} }
@MainActor func testInitialStateForUnsupported() { @MainActor func testInitialStateForUnsupported() {
let viewModel = SoftLogoutViewModel(credentials: credentials, let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockUnsupported, homeserver: .mockUnsupported,
keyBackupNeeded: false) keyBackupNeeded: false)
let context = viewModel.context let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email. // Given a view model where the user hasn't yet sent the verification email.
@@ -20,11 +20,11 @@ import XCTest
@MainActor @MainActor
class StartChatScreenViewModelTests: XCTestCase { class StartChatScreenViewModelTests: XCTestCase {
var viewModel: StartChatViewModelProtocol! var viewModel: StartChatScreenViewModelProtocol!
var clientProxy: MockClientProxy! var clientProxy: MockClientProxy!
var userDiscoveryService: UserDiscoveryServiceMock! var userDiscoveryService: UserDiscoveryServiceMock!
var context: StartChatViewModel.Context { var context: StartChatScreenViewModel.Context {
viewModel.context viewModel.context
} }
@@ -34,7 +34,7 @@ class StartChatScreenViewModelTests: XCTestCase {
userDiscoveryService.fetchSuggestionsReturnValue = .success([]) userDiscoveryService.fetchSuggestionsReturnValue = .success([])
userDiscoveryService.searchProfilesWithReturnValue = .success([]) userDiscoveryService.searchProfilesWithReturnValue = .success([])
let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())
viewModel = StartChatViewModel(userSession: userSession, userIndicatorController: nil, userDiscoveryService: userDiscoveryService) viewModel = StartChatScreenViewModel(userSession: userSession, userIndicatorController: nil, userDiscoveryService: userDiscoveryService)
setupAppSettings() setupAppSettings()
ServiceLocator.shared.settings.startChatUserSuggestionsEnabled = true ServiceLocator.shared.settings.startChatUserSuggestionsEnabled = true
@@ -65,7 +65,7 @@ class StartChatScreenViewModelTests: XCTestCase {
} }
@discardableResult @discardableResult
private func search(query: String) async -> StartChatViewState? { private func search(query: String) async -> StartChatScreenViewState? {
viewModel.context.searchQuery = query viewModel.context.searchQuery = query
return await context.$viewState.nextValue return await context.$viewState.nextValue
} }