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 b9cd938756
commit 10ab981335
31 changed files with 286 additions and 286 deletions

View File

@@ -220,18 +220,18 @@ class AppCoordinator: AppCoordinatorProtocol {
displayName = name
}
let credentials = SoftLogoutCredentials(userId: userSession.userID,
homeserverName: userSession.homeserver,
userDisplayName: displayName,
deviceId: userSession.deviceID)
let credentials = SoftLogoutScreenCredentials(userId: userSession.userID,
homeserverName: userSession.homeserver,
userDisplayName: displayName,
deviceId: userSession.deviceID)
let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore)
_ = await authenticationService.configure(for: userSession.homeserver)
let parameters = SoftLogoutCoordinatorParameters(authenticationService: authenticationService,
credentials: credentials,
keyBackupNeeded: false)
let coordinator = SoftLogoutCoordinator(parameters: parameters)
let parameters = SoftLogoutScreenCoordinatorParameters(authenticationService: authenticationService,
credentials: credentials,
keyBackupNeeded: false)
let coordinator = SoftLogoutScreenCoordinator(parameters: parameters)
coordinator.callback = { result in
switch result {
case .signedIn(let session):

View File

@@ -72,10 +72,10 @@ class AuthenticationCoordinator: CoordinatorProtocol {
}
private func showServerSelectionScreen() {
let parameters = ServerSelectionCoordinatorParameters(authenticationService: authenticationService,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false)
let coordinator = ServerSelectionCoordinator(parameters: parameters)
let parameters = ServerSelectionScreenCoordinatorParameters(authenticationService: authenticationService,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false)
let coordinator = ServerSelectionScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in
guard let self else { return }
@@ -92,10 +92,10 @@ class AuthenticationCoordinator: CoordinatorProtocol {
}
private func showLoginScreen() {
let parameters = LoginCoordinatorParameters(authenticationService: authenticationService,
navigationStackCoordinator: navigationStackCoordinator)
let coordinator = LoginCoordinator(parameters: parameters)
let parameters = LoginScreenCoordinatorParameters(authenticationService: authenticationService,
navigationStackCoordinator: navigationStackCoordinator)
let coordinator = LoginScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self] action in
guard let self else { return }

View File

@@ -17,21 +17,21 @@
import AppAuth
import SwiftUI
struct LoginCoordinatorParameters {
struct LoginScreenCoordinatorParameters {
/// The service used to authenticate the user.
let authenticationService: AuthenticationServiceProxyProtocol
/// The navigation router used to present the server selection screen.
let navigationStackCoordinator: NavigationStackCoordinator
}
enum LoginCoordinatorAction {
enum LoginScreenCoordinatorAction {
/// Login was successful.
case signedIn(UserSessionProtocol)
}
final class LoginCoordinator: CoordinatorProtocol {
private let parameters: LoginCoordinatorParameters
private var viewModel: LoginViewModelProtocol
final class LoginScreenCoordinator: CoordinatorProtocol {
private let parameters: LoginScreenCoordinatorParameters
private var viewModel: LoginScreenViewModelProtocol
private let hostingController: UIViewController
/// Passed to the OIDC service to provide a view controller from which to present the authentication session.
private let oidcUserAgent: OIDExternalUserAgentIOS?
@@ -45,14 +45,14 @@ final class LoginCoordinator: CoordinatorProtocol {
private var authenticationService: AuthenticationServiceProxyProtocol { parameters.authenticationService }
private var navigationStackCoordinator: NavigationStackCoordinator { parameters.navigationStackCoordinator }
var callback: (@MainActor (LoginCoordinatorAction) -> Void)?
var callback: (@MainActor (LoginScreenCoordinatorAction) -> Void)?
// MARK: - Setup
init(parameters: LoginCoordinatorParameters) {
init(parameters: LoginScreenCoordinatorParameters) {
self.parameters = parameters
viewModel = LoginViewModel(homeserver: parameters.authenticationService.homeserver)
viewModel = LoginScreenViewModel(homeserver: parameters.authenticationService.homeserver)
hostingController = UIHostingController(rootView: LoginScreen(context: viewModel.context))
oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController)
@@ -199,11 +199,11 @@ final class LoginCoordinator: CoordinatorProtocol {
/// Presents the server selection screen as a modal.
private func presentServerSelectionScreen() {
let parameters = ServerSelectionCoordinatorParameters(authenticationService: authenticationService,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false)
let parameters = ServerSelectionScreenCoordinatorParameters(authenticationService: authenticationService,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
isModallyPresented: false)
let coordinator = ServerSelectionCoordinator(parameters: parameters)
let coordinator = ServerSelectionScreenCoordinator(parameters: parameters)
coordinator.callback = { [weak self, weak coordinator] action in
guard let self, let coordinator else { return }
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.
private func serverSelectionCoordinator(_ coordinator: ServerSelectionCoordinator,
didCompleteWith action: ServerSelectionCoordinatorAction) {
private func serverSelectionCoordinator(_ coordinator: ServerSelectionScreenCoordinator,
didCompleteWith action: ServerSelectionScreenCoordinatorAction) {
if action == .updated {
updateViewModel()
}

View File

@@ -16,7 +16,7 @@
import Foundation
enum LoginViewModelAction: CustomStringConvertible {
enum LoginScreenViewModelAction: CustomStringConvertible {
/// The user would like to select another server.
case selectServer
/// 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.
var homeserver: LoginHomeserver
/// Whether a new homeserver is currently being loaded.
var isLoading = false
/// View state that can be bound to from SwiftUI.
var bindings: LoginBindings
var bindings: LoginScreenBindings
/// The types of login supported by the homeserver.
var loginMode: LoginMode { homeserver.loginMode }
@@ -67,16 +67,16 @@ struct LoginViewState: BindableState {
}
}
struct LoginBindings {
struct LoginScreenBindings {
/// The username input by the user.
var username = ""
/// The password input by the user.
var password = ""
/// 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.
case selectServer
/// Parse the username to detect if a homeserver is included.
@@ -89,7 +89,7 @@ enum LoginViewAction {
case continueWithOIDC
}
enum LoginErrorType: Hashable {
enum LoginScreenErrorType: Hashable {
/// A specific error message shown in an alert.
case alert(String)
/// Looking up the homeserver from the username failed.

View File

@@ -16,19 +16,19 @@
import SwiftUI
typealias LoginViewModelType = StateStoreViewModel<LoginViewState, LoginViewAction>
typealias LoginScreenViewModelType = StateStoreViewModel<LoginScreenViewState, LoginScreenViewAction>
class LoginViewModel: LoginViewModelType, LoginViewModelProtocol {
var callback: (@MainActor (LoginViewModelAction) -> Void)?
class LoginScreenViewModel: LoginScreenViewModelType, LoginScreenViewModelProtocol {
var callback: (@MainActor (LoginScreenViewModelAction) -> Void)?
init(homeserver: LoginHomeserver) {
let bindings = LoginBindings()
let viewState = LoginViewState(homeserver: homeserver, bindings: bindings)
let bindings = LoginScreenBindings()
let viewState = LoginScreenViewState(homeserver: homeserver, bindings: bindings)
super.init(initialViewState: viewState)
}
override func process(viewAction: LoginViewAction) {
override func process(viewAction: LoginScreenViewAction) {
switch viewAction {
case .selectServer:
callback?(.selectServer)
@@ -52,7 +52,7 @@ class LoginViewModel: LoginViewModelType, LoginViewModelProtocol {
state.homeserver = homeserver
}
func displayError(_ type: LoginErrorType) {
func displayError(_ type: LoginScreenErrorType) {
switch type {
case .alert(let message):
state.bindings.alertInfo = AlertInfo(id: type,

View File

@@ -17,9 +17,9 @@
import Foundation
@MainActor
protocol LoginViewModelProtocol {
var callback: (@MainActor (LoginViewModelAction) -> Void)? { get set }
var context: LoginViewModelType.Context { get }
protocol LoginScreenViewModelProtocol {
var callback: (@MainActor (LoginScreenViewModelAction) -> Void)? { get set }
var context: LoginScreenViewModelType.Context { get }
/// Update the view to reflect that a new 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.
/// - Parameter type: The type of error to be displayed.
func displayError(_ type: LoginErrorType)
func displayError(_ type: LoginScreenErrorType)
}

View File

@@ -22,7 +22,7 @@ struct LoginScreen: View {
/// The focus state of the password text field.
@FocusState private var isPasswordFocused: Bool
@ObservedObject var context: LoginViewModel.Context
@ObservedObject var context: LoginScreenViewModel.Context
var body: some View {
ScrollView {
@@ -146,26 +146,26 @@ struct LoginScreen: View {
// MARK: - Previews
struct Login_Previews: PreviewProvider {
static let credentialsViewModel: LoginViewModel = {
let viewModel = LoginViewModel(homeserver: .mockMatrixDotOrg)
struct LoginScreen_Previews: PreviewProvider {
static let credentialsViewModel: LoginScreenViewModel = {
let viewModel = LoginScreenViewModel(homeserver: .mockMatrixDotOrg)
viewModel.context.username = "alice"
viewModel.context.password = "password"
return viewModel
}()
static var previews: some View {
screen(for: LoginViewModel(homeserver: .mockMatrixDotOrg))
screen(for: LoginScreenViewModel(homeserver: .mockMatrixDotOrg))
.previewDisplayName("matrix.org")
screen(for: credentialsViewModel)
.previewDisplayName("Credentials Entered")
screen(for: LoginViewModel(homeserver: .mockOIDC))
screen(for: LoginScreenViewModel(homeserver: .mockOIDC))
.previewDisplayName("OIDC")
screen(for: LoginViewModel(homeserver: .mockUnsupported))
screen(for: LoginScreenViewModel(homeserver: .mockUnsupported))
.previewDisplayName("Unsupported")
}
static func screen(for viewModel: LoginViewModel) -> some View {
static func screen(for viewModel: LoginScreenViewModel) -> some View {
NavigationStack {
LoginScreen(context: viewModel.context)
.navigationBarTitleDisplayMode(.inline)

View File

@@ -23,22 +23,22 @@ enum MockServerSelectionScreenState: CaseIterable {
case nonModal
/// Generate the view struct for the screen state.
@MainActor var viewModel: ServerSelectionViewModel {
@MainActor var viewModel: ServerSelectionScreenViewModel {
switch self {
case .matrix:
return ServerSelectionViewModel(homeserverAddress: "https://matrix.org",
isModallyPresented: true)
return ServerSelectionScreenViewModel(homeserverAddress: "https://matrix.org",
isModallyPresented: true)
case .emptyAddress:
return ServerSelectionViewModel(homeserverAddress: "",
isModallyPresented: true)
return ServerSelectionScreenViewModel(homeserverAddress: "",
isModallyPresented: true)
case .invalidAddress:
let viewModel = ServerSelectionViewModel(homeserverAddress: "thisisbad",
isModallyPresented: true)
let viewModel = ServerSelectionScreenViewModel(homeserverAddress: "thisisbad",
isModallyPresented: true)
viewModel.displayError(.footerMessage(L10n.errorUnknown))
return viewModel
case .nonModal:
return ServerSelectionViewModel(homeserverAddress: "https://matrix.org",
isModallyPresented: false)
return ServerSelectionScreenViewModel(homeserverAddress: "https://matrix.org",
isModallyPresented: false)
}
}
}

View File

@@ -16,7 +16,7 @@
import SwiftUI
struct ServerSelectionCoordinatorParameters {
struct ServerSelectionScreenCoordinatorParameters {
/// The service used to authenticate the user.
let authenticationService: AuthenticationServiceProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
@@ -24,23 +24,23 @@ struct ServerSelectionCoordinatorParameters {
let isModallyPresented: Bool
}
enum ServerSelectionCoordinatorAction {
enum ServerSelectionScreenCoordinatorAction {
case updated
case dismiss
}
final class ServerSelectionCoordinator: CoordinatorProtocol {
private let parameters: ServerSelectionCoordinatorParameters
final class ServerSelectionScreenCoordinator: CoordinatorProtocol {
private let parameters: ServerSelectionScreenCoordinatorParameters
private let userIndicatorController: UserIndicatorControllerProtocol
private var viewModel: ServerSelectionViewModelProtocol
private var viewModel: ServerSelectionScreenViewModelProtocol
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
viewModel = ServerSelectionViewModel(homeserverAddress: parameters.authenticationService.homeserver.address,
isModallyPresented: parameters.isModallyPresented)
viewModel = ServerSelectionScreenViewModel(homeserverAddress: parameters.authenticationService.homeserver.address,
isModallyPresented: parameters.isModallyPresented)
userIndicatorController = parameters.userIndicatorController
}

View File

@@ -16,14 +16,14 @@
import Foundation
enum ServerSelectionViewModelAction {
enum ServerSelectionScreenViewModelAction {
/// The user would like to use the homeserver at the given address.
case confirm(homeserverAddress: String)
/// Dismiss the view without using the entered address.
case dismiss
}
struct ServerSelectionViewState: BindableState {
struct ServerSelectionScreenViewState: BindableState {
/// The message to be shown in the text field footer when no error has occurred.
private let regularFooterMessage = {
let linkPlaceholder = "{link}"
@@ -35,7 +35,7 @@ struct ServerSelectionViewState: BindableState {
}()
/// 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.
var footerErrorMessage: String?
/// 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.
var homeserverAddress: String
/// 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.
case confirm
/// Dismiss the view without using the entered address.
@@ -78,7 +78,7 @@ enum ServerSelectionViewAction {
case clearFooterError
}
enum ServerSelectionErrorType: Hashable {
enum ServerSelectionScreenErrorType: Hashable {
/// An error message to be shown in the text field footer.
case footerMessage(String)
/// An alert that allows the user to learn about sliding sync.

View File

@@ -16,19 +16,19 @@
import SwiftUI
typealias ServerSelectionViewModelType = StateStoreViewModel<ServerSelectionViewState, ServerSelectionViewAction>
typealias ServerSelectionScreenViewModelType = StateStoreViewModel<ServerSelectionScreenViewState, ServerSelectionScreenViewAction>
class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionViewModelProtocol {
var callback: (@MainActor (ServerSelectionViewModelAction) -> Void)?
class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, ServerSelectionScreenViewModelProtocol {
var callback: (@MainActor (ServerSelectionScreenViewModelAction) -> Void)?
init(homeserverAddress: String, isModallyPresented: Bool) {
let bindings = ServerSelectionBindings(homeserverAddress: homeserverAddress)
let bindings = ServerSelectionScreenBindings(homeserverAddress: homeserverAddress)
super.init(initialViewState: ServerSelectionViewState(bindings: bindings,
isModallyPresented: isModallyPresented))
super.init(initialViewState: ServerSelectionScreenViewState(bindings: bindings,
isModallyPresented: isModallyPresented))
}
override func process(viewAction: ServerSelectionViewAction) {
override func process(viewAction: ServerSelectionScreenViewAction) {
switch viewAction {
case .confirm:
callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress))
@@ -39,7 +39,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
}
}
func displayError(_ type: ServerSelectionErrorType) {
func displayError(_ type: ServerSelectionScreenErrorType) {
switch type {
case .footerMessage(let message):
withElementAnimation {

View File

@@ -17,10 +17,10 @@
import Foundation
@MainActor
protocol ServerSelectionViewModelProtocol {
var callback: (@MainActor (ServerSelectionViewModelAction) -> Void)? { get set }
var context: ServerSelectionViewModelType.Context { get }
protocol ServerSelectionScreenViewModelProtocol {
var callback: (@MainActor (ServerSelectionScreenViewModelAction) -> Void)? { get set }
var context: ServerSelectionScreenViewModelType.Context { get }
/// Displays an error to the user.
func displayError(_ type: ServerSelectionErrorType)
func displayError(_ type: ServerSelectionScreenErrorType)
}

View File

@@ -17,7 +17,7 @@
import SwiftUI
struct ServerSelectionScreen: View {
@ObservedObject var context: ServerSelectionViewModel.Context
@ObservedObject var context: ServerSelectionScreenViewModel.Context
var body: some View {
ScrollView {

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -17,13 +17,13 @@
import AppAuth
import SwiftUI
struct SoftLogoutCoordinatorParameters {
struct SoftLogoutScreenCoordinatorParameters {
let authenticationService: AuthenticationServiceProxyProtocol
let credentials: SoftLogoutCredentials
let credentials: SoftLogoutScreenCredentials
let keyBackupNeeded: Bool
}
enum SoftLogoutCoordinatorResult: CustomStringConvertible {
enum SoftLogoutScreenCoordinatorResult: CustomStringConvertible {
/// Login was successful.
case signedIn(UserSessionProtocol)
/// Clear all user data
@@ -40,26 +40,26 @@ enum SoftLogoutCoordinatorResult: CustomStringConvertible {
}
}
final class SoftLogoutCoordinator: CoordinatorProtocol {
private let parameters: SoftLogoutCoordinatorParameters
private var viewModel: SoftLogoutViewModelProtocol
final class SoftLogoutScreenCoordinator: CoordinatorProtocol {
private let parameters: SoftLogoutScreenCoordinatorParameters
private var viewModel: SoftLogoutScreenViewModelProtocol
private let hostingController: UIViewController
/// Passed to the OIDC service to provide a view controller from which to present the authentication session.
private let oidcUserAgent: OIDExternalUserAgentIOS?
/// The wizard used to handle the registration flow.
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
let homeserver = parameters.authenticationService.homeserver
viewModel = SoftLogoutViewModel(credentials: parameters.credentials,
homeserver: homeserver,
keyBackupNeeded: parameters.keyBackupNeeded)
viewModel = SoftLogoutScreenViewModel(credentials: parameters.credentials,
homeserver: homeserver,
keyBackupNeeded: parameters.keyBackupNeeded)
hostingController = UIHostingController(rootView: SoftLogoutScreen(context: viewModel.context))
oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController)

View File

@@ -16,14 +16,14 @@
import SwiftUI
struct SoftLogoutCredentials {
struct SoftLogoutScreenCredentials {
let userId: String
let homeserverName: String
let userDisplayName: String
let deviceId: String?
}
enum SoftLogoutViewModelAction: CustomStringConvertible {
enum SoftLogoutScreenViewModelAction: CustomStringConvertible {
/// Login with password
case login(String)
/// Forgot password
@@ -48,9 +48,9 @@ enum SoftLogoutViewModelAction: CustomStringConvertible {
}
}
struct SoftLogoutViewState: BindableState {
struct SoftLogoutScreenViewState: BindableState {
/// Soft logout credentials
var credentials: SoftLogoutCredentials
var credentials: SoftLogoutScreenCredentials
/// Data about the selected homeserver.
var homeserver: LoginHomeserver
@@ -59,7 +59,7 @@ struct SoftLogoutViewState: BindableState {
var keyBackupNeeded: Bool
/// View state that can be bound to from SwiftUI.
var bindings: SoftLogoutBindings
var bindings: SoftLogoutScreenBindings
/// The types of login supported by the homeserver.
var loginMode: LoginMode { homeserver.loginMode }
@@ -75,14 +75,14 @@ struct SoftLogoutViewState: BindableState {
}
}
struct SoftLogoutBindings {
struct SoftLogoutScreenBindings {
/// The password input by the user.
var password: String
/// Information describing the currently displayed alert.
var alertInfo: AlertInfo<SoftLogoutErrorType>?
var alertInfo: AlertInfo<SoftLogoutScreenErrorType>?
}
enum SoftLogoutViewAction {
enum SoftLogoutScreenViewAction {
/// Login.
case login
/// Forgot password
@@ -93,7 +93,7 @@ enum SoftLogoutViewAction {
case continueWithOIDC
}
enum SoftLogoutErrorType: Hashable {
enum SoftLogoutScreenErrorType: Hashable {
/// A specific error message shown in an alert.
case alert(String)
/// An unknown error occurred.

View File

@@ -16,24 +16,24 @@
import SwiftUI
typealias SoftLogoutViewModelType = StateStoreViewModel<SoftLogoutViewState, SoftLogoutViewAction>
typealias SoftLogoutScreenViewModelType = StateStoreViewModel<SoftLogoutScreenViewState, SoftLogoutScreenViewAction>
class SoftLogoutViewModel: SoftLogoutViewModelType, SoftLogoutViewModelProtocol {
var callback: (@MainActor (SoftLogoutViewModelAction) -> Void)?
class SoftLogoutScreenViewModel: SoftLogoutScreenViewModelType, SoftLogoutScreenViewModelProtocol {
var callback: (@MainActor (SoftLogoutScreenViewModelAction) -> Void)?
init(credentials: SoftLogoutCredentials,
init(credentials: SoftLogoutScreenCredentials,
homeserver: LoginHomeserver,
keyBackupNeeded: Bool,
password: String = "") {
let bindings = SoftLogoutBindings(password: password)
let viewState = SoftLogoutViewState(credentials: credentials,
homeserver: homeserver,
keyBackupNeeded: keyBackupNeeded,
bindings: bindings)
let bindings = SoftLogoutScreenBindings(password: password)
let viewState = SoftLogoutScreenViewState(credentials: credentials,
homeserver: homeserver,
keyBackupNeeded: keyBackupNeeded,
bindings: bindings)
super.init(initialViewState: viewState)
}
override func process(viewAction: SoftLogoutViewAction) {
override func process(viewAction: SoftLogoutScreenViewAction) {
switch viewAction {
case .login:
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 {
case .alert(let message):
state.bindings.alertInfo = AlertInfo(id: type,

View File

@@ -16,10 +16,10 @@
import Foundation
protocol SoftLogoutViewModelProtocol {
var callback: (@MainActor (SoftLogoutViewModelAction) -> Void)? { get set }
var context: SoftLogoutViewModelType.Context { get }
protocol SoftLogoutScreenViewModelProtocol {
var callback: (@MainActor (SoftLogoutScreenViewModelAction) -> Void)? { get set }
var context: SoftLogoutScreenViewModelType.Context { get }
/// Display an error to the user.
@MainActor func displayError(_ type: SoftLogoutErrorType)
@MainActor func displayError(_ type: SoftLogoutScreenErrorType)
}

View File

@@ -22,7 +22,7 @@ struct SoftLogoutScreen: View {
/// The focus state of the password text field.
@FocusState private var isPasswordFocused: Bool
@ObservedObject var context: SoftLogoutViewModel.Context
@ObservedObject var context: SoftLogoutScreenViewModel.Context
var body: some View {
ScrollView {
@@ -171,14 +171,14 @@ struct SoftLogoutScreen: View {
// MARK: - Previews
struct SoftLogout_Previews: PreviewProvider {
struct SoftLogoutScreen_Previews: PreviewProvider {
static var previews: some View {
ForEach(MockSoftLogoutScreenState.allCases) { state in
screen(for: state.viewModel)
}
}
static func screen(for viewModel: SoftLogoutViewModel) -> some View {
static func screen(for viewModel: SoftLogoutScreenViewModel) -> some View {
NavigationStack {
SoftLogoutScreen(context: viewModel.context)
.navigationBarTitleDisplayMode(.inline)

View File

@@ -17,32 +17,32 @@
import Combine
import SwiftUI
struct StartChatCoordinatorParameters {
struct StartChatScreenCoordinatorParameters {
let userSession: UserSessionProtocol
weak var userIndicatorController: UserIndicatorControllerProtocol?
let navigationStackCoordinator: NavigationStackCoordinator?
let userDiscoveryService: UserDiscoveryServiceProtocol
}
enum StartChatCoordinatorAction {
enum StartChatScreenCoordinatorAction {
case close
case openRoom(withIdentifier: String)
}
final class StartChatCoordinator: CoordinatorProtocol {
private let parameters: StartChatCoordinatorParameters
private var viewModel: StartChatViewModelProtocol
private let actionsSubject: PassthroughSubject<StartChatCoordinatorAction, Never> = .init()
final class StartChatScreenCoordinator: CoordinatorProtocol {
private let parameters: StartChatScreenCoordinatorParameters
private var viewModel: StartChatScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<StartChatScreenCoordinatorAction, Never> = .init()
private var cancellables: Set<AnyCancellable> = .init()
var actions: AnyPublisher<StartChatCoordinatorAction, Never> {
var actions: AnyPublisher<StartChatScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: StartChatCoordinatorParameters) {
init(parameters: StartChatScreenCoordinatorParameters) {
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() {

View File

@@ -16,18 +16,18 @@
import Foundation
enum StartChatErrorType: Error {
enum StartChatScreenErrorType: Error {
case failedCreatingRoom
case unknown
}
enum StartChatViewModelAction {
enum StartChatScreenViewModelAction {
case close
case createRoom
case openRoom(withIdentifier: String)
}
struct StartChatViewState: BindableState {
struct StartChatScreenViewState: BindableState {
var bindings = StartChatScreenViewStateBindings()
var usersSection: UserDiscoverySection = .init(type: .suggestions, users: [])
@@ -44,10 +44,10 @@ struct StartChatScreenViewStateBindings {
var searchQuery = ""
/// Information describing the currently displayed alert.
var alertInfo: AlertInfo<StartChatErrorType>?
var alertInfo: AlertInfo<StartChatScreenErrorType>?
}
enum StartChatViewAction {
enum StartChatScreenViewAction {
case close
case createRoom
case inviteFriends

View File

@@ -17,14 +17,14 @@
import Combine
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 actionsSubject: PassthroughSubject<StartChatViewModelAction, Never> = .init()
private let actionsSubject: PassthroughSubject<StartChatScreenViewModelAction, Never> = .init()
private let userDiscoveryService: UserDiscoveryServiceProtocol
var actions: AnyPublisher<StartChatViewModelAction, Never> {
var actions: AnyPublisher<StartChatScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
@@ -34,14 +34,14 @@ class StartChatViewModel: StartChatViewModelType, StartChatViewModelProtocol {
self.userSession = userSession
self.userIndicatorController = userIndicatorController
self.userDiscoveryService = userDiscoveryService
super.init(initialViewState: StartChatViewState(), imageProvider: userSession.mediaProvider)
super.init(initialViewState: StartChatScreenViewState(), imageProvider: userSession.mediaProvider)
setupBindings()
}
// MARK: - Public
override func process(viewAction: StartChatViewAction) {
override func process(viewAction: StartChatScreenViewAction) {
switch viewAction {
case .close:
actionsSubject.send(.close)

View File

@@ -17,7 +17,7 @@
import Combine
@MainActor
protocol StartChatViewModelProtocol {
var actions: AnyPublisher<StartChatViewModelAction, Never> { get }
var context: StartChatViewModelType.Context { get }
protocol StartChatScreenViewModelProtocol {
var actions: AnyPublisher<StartChatScreenViewModelAction, Never> { get }
var context: StartChatScreenViewModelType.Context { get }
}

View File

@@ -17,7 +17,7 @@
import SwiftUI
struct StartChatScreen: View {
@ObservedObject var context: StartChatViewModel.Context
@ObservedObject var context: StartChatScreenViewModel.Context
var body: some View {
Form {
@@ -130,14 +130,14 @@ struct StartChatScreen: View {
// MARK: - Previews
struct StartChat_Previews: PreviewProvider {
struct StartChatScreen_Previews: PreviewProvider {
static let viewModel = {
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"),
mediaProvider: MockMediaProvider())
let userDiscoveryService = UserDiscoveryServiceMock()
userDiscoveryService.fetchSuggestionsReturnValue = .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
}()

View File

@@ -289,8 +289,8 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
let userIndicatorController = UserIndicatorController(rootCoordinator: startChatNavigationStackCoordinator)
let userDiscoveryService = UserDiscoveryService(clientProxy: userSession.clientProxy)
let parameters = StartChatCoordinatorParameters(userSession: userSession, userIndicatorController: userIndicatorController, navigationStackCoordinator: startChatNavigationStackCoordinator, userDiscoveryService: userDiscoveryService)
let coordinator = StartChatCoordinator(parameters: parameters)
let parameters = StartChatScreenCoordinatorParameters(userSession: userSession, userIndicatorController: userIndicatorController, navigationStackCoordinator: startChatNavigationStackCoordinator, userDiscoveryService: userDiscoveryService)
let coordinator = StartChatScreenCoordinator(parameters: parameters)
coordinator.actions.sink { [weak self] action in
guard let self else { return }
switch action {

View File

@@ -62,21 +62,21 @@ class MockScreen: Identifiable {
switch id {
case .login:
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
navigationStackCoordinator: navigationStackCoordinator))
let coordinator = LoginScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
navigationStackCoordinator: navigationStackCoordinator))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .serverSelection:
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userIndicatorController: MockUserIndicatorController(),
isModallyPresented: true))
let coordinator = ServerSelectionScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userIndicatorController: MockUserIndicatorController(),
isModallyPresented: true))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .serverSelectionNonModal:
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userIndicatorController: MockUserIndicatorController(),
isModallyPresented: false))
return ServerSelectionScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
userIndicatorController: MockUserIndicatorController(),
isModallyPresented: false))
case .analyticsPrompt:
return AnalyticsPromptScreenCoordinator()
case .analyticsSettingsScreen:
@@ -92,13 +92,13 @@ class MockScreen: Identifiable {
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .softLogout:
let credentials = SoftLogoutCredentials(userId: "@mock:matrix.org",
homeserverName: "matrix.org",
userDisplayName: "mock",
deviceId: "ABCDEFGH")
return SoftLogoutCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
credentials: credentials,
keyBackupNeeded: false))
let credentials = SoftLogoutScreenCredentials(userId: "@mock:matrix.org",
homeserverName: "matrix.org",
userDisplayName: "mock",
deviceId: "ABCDEFGH")
return SoftLogoutScreenCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
credentials: credentials,
keyBackupNeeded: false))
case .simpleRegular:
return TemplateScreenCoordinator(parameters: .init(promptType: .regular))
case .simpleUpgrade:
@@ -321,8 +321,8 @@ class MockScreen: Identifiable {
userDiscoveryMock.fetchSuggestionsReturnValue = .success([.mockAlice, .mockBob, .mockCharlie])
userDiscoveryMock.searchProfilesWithReturnValue = .success([])
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"), mediaProvider: MockMediaProvider())
let parameters: StartChatCoordinatorParameters = .init(userSession: userSession, navigationStackCoordinator: navigationStackCoordinator, userDiscoveryService: userDiscoveryMock)
let coordinator = StartChatCoordinator(parameters: parameters)
let parameters: StartChatScreenCoordinatorParameters = .init(userSession: userSession, navigationStackCoordinator: navigationStackCoordinator, userDiscoveryService: userDiscoveryMock)
let coordinator = StartChatScreenCoordinator(parameters: parameters)
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .startChatWithSearchResults:
@@ -332,7 +332,7 @@ class MockScreen: Identifiable {
userDiscoveryMock.fetchSuggestionsReturnValue = .success([])
userDiscoveryMock.searchProfilesWithReturnValue = .success([.mockBob, .mockBobby])
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)
return navigationStackCoordinator
case .roomMemberDetailsAccountOwner:

View File

@@ -21,11 +21,11 @@ import XCTest
@MainActor
class LoginViewModelTests: XCTestCase {
let defaultHomeserver = LoginHomeserver.mockMatrixDotOrg
var viewModel: LoginViewModelProtocol!
var context: LoginViewModelType.Context!
var viewModel: LoginScreenViewModelProtocol!
var context: LoginScreenViewModelType.Context!
@MainActor override func setUp() async throws {
viewModel = LoginViewModel(homeserver: defaultHomeserver)
viewModel = LoginScreenViewModel(homeserver: defaultHomeserver)
context = viewModel.context
}
@@ -135,7 +135,7 @@ class LoginViewModelTests: XCTestCase {
func testLogsForPassword() {
// Given the coordinator and view model results that contain passwords.
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).
let viewModelActionString = "\(viewModelAction)"

View File

@@ -24,11 +24,11 @@ class ServerSelectionViewModelTests: XCTestCase {
static let counterInitialValue = 0
}
var viewModel: ServerSelectionViewModelProtocol!
var context: ServerSelectionViewModelType.Context!
var viewModel: ServerSelectionScreenViewModelProtocol!
var context: ServerSelectionScreenViewModelType.Context!
@MainActor override func setUp() {
viewModel = ServerSelectionViewModel(homeserverAddress: "", isModallyPresented: true)
viewModel = ServerSelectionScreenViewModel(homeserverAddress: "", isModallyPresented: true)
context = viewModel.context
}

View File

@@ -19,17 +19,17 @@ import XCTest
@testable import ElementX
class SoftLogoutViewModelTests: XCTestCase {
let credentials = SoftLogoutCredentials(userId: "mock_user_id",
homeserverName: "https://matrix.org",
userDisplayName: "mock_username",
deviceId: "ABCDEFGH")
let credentials = SoftLogoutScreenCredentials(userId: "mock_user_id",
homeserverName: "https://matrix.org",
userDisplayName: "mock_username",
deviceId: "ABCDEFGH")
@MainActor func testInitialStateForMatrixOrg() {
let viewModel = SoftLogoutViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true)
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true)
let context = viewModel.context
// 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.")
XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.")
@@ -38,10 +38,10 @@ class SoftLogoutViewModelTests: XCTestCase {
}
@MainActor func testInitialStateForMatrixOrgPasswordEntered() {
let viewModel = SoftLogoutViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true,
password: "12345678")
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockMatrixDotOrg,
keyBackupNeeded: true,
password: "12345678")
let context = viewModel.context
// 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.")
XCTAssert(context.viewState.showRecoverEncryptionKeysMessage, "The view model should show recover encryption keys message.")
}
@MainActor func testInitialStateForBasicServer() {
let viewModel = SoftLogoutViewModel(credentials: credentials,
homeserver: .mockBasicServer,
keyBackupNeeded: false)
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockBasicServer,
keyBackupNeeded: false)
let context = viewModel.context
// 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.")
XCTAssertFalse(context.viewState.canSubmit, "The view model should start with an invalid password.")
@@ -64,22 +64,22 @@ class SoftLogoutViewModelTests: XCTestCase {
}
@MainActor func testInitialStateForOIDC() {
let viewModel = SoftLogoutViewModel(credentials: credentials,
homeserver: .mockOIDC,
keyBackupNeeded: false)
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockOIDC,
keyBackupNeeded: false)
let context = viewModel.context
// 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.")
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.")
XCTAssertFalse(context.viewState.showRecoverEncryptionKeysMessage, "The view model should not show recover encryption keys message.")
}
@MainActor func testInitialStateForUnsupported() {
let viewModel = SoftLogoutViewModel(credentials: credentials,
homeserver: .mockUnsupported,
keyBackupNeeded: false)
let viewModel = SoftLogoutScreenViewModel(credentials: credentials,
homeserver: .mockUnsupported,
keyBackupNeeded: false)
let context = viewModel.context
// Given a view model where the user hasn't yet sent the verification email.

View File

@@ -20,11 +20,11 @@ import XCTest
@MainActor
class StartChatScreenViewModelTests: XCTestCase {
var viewModel: StartChatViewModelProtocol!
var viewModel: StartChatScreenViewModelProtocol!
var clientProxy: MockClientProxy!
var userDiscoveryService: UserDiscoveryServiceMock!
var context: StartChatViewModel.Context {
var context: StartChatScreenViewModel.Context {
viewModel.context
}
@@ -34,7 +34,7 @@ class StartChatScreenViewModelTests: XCTestCase {
userDiscoveryService.fetchSuggestionsReturnValue = .success([])
userDiscoveryService.searchProfilesWithReturnValue = .success([])
let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())
viewModel = StartChatViewModel(userSession: userSession, userIndicatorController: nil, userDiscoveryService: userDiscoveryService)
viewModel = StartChatScreenViewModel(userSession: userSession, userIndicatorController: nil, userDiscoveryService: userDiscoveryService)
setupAppSettings()
ServiceLocator.shared.settings.startChatUserSuggestionsEnabled = true
@@ -65,7 +65,7 @@ class StartChatScreenViewModelTests: XCTestCase {
}
@discardableResult
private func search(query: String) async -> StartChatViewState? {
private func search(query: String) async -> StartChatScreenViewState? {
viewModel.context.searchQuery = query
return await context.$viewState.nextValue
}