mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
Renaming screens returns (#829)
* Rename: StartChat -> StartChatScreen * Rename: Login -> LoginScreen * Rename: ServerSelection -> ServerSelectionScreen * Rename: SoftLogout -> SoftLogoutScreen
This commit is contained in:
+68
@@ -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
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// 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 AppAuth
|
||||
import SwiftUI
|
||||
|
||||
struct SoftLogoutScreenCoordinatorParameters {
|
||||
let authenticationService: AuthenticationServiceProxyProtocol
|
||||
let credentials: SoftLogoutScreenCredentials
|
||||
let keyBackupNeeded: Bool
|
||||
}
|
||||
|
||||
enum SoftLogoutScreenCoordinatorResult: CustomStringConvertible {
|
||||
/// Login was successful.
|
||||
case signedIn(UserSessionProtocol)
|
||||
/// Clear all user data
|
||||
case clearAllData
|
||||
|
||||
/// A string representation of the result, ignoring any associated values that could leak PII.
|
||||
var description: String {
|
||||
switch self {
|
||||
case .signedIn:
|
||||
return "signedIn"
|
||||
case .clearAllData:
|
||||
return "clearAllData"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (SoftLogoutScreenCoordinatorResult) -> Void)?
|
||||
|
||||
@MainActor init(parameters: SoftLogoutScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let homeserver = parameters.authenticationService.homeserver
|
||||
|
||||
viewModel = SoftLogoutScreenViewModel(credentials: parameters.credentials,
|
||||
homeserver: homeserver,
|
||||
keyBackupNeeded: parameters.keyBackupNeeded)
|
||||
|
||||
hostingController = UIHostingController(rootView: SoftLogoutScreen(context: viewModel.context))
|
||||
oidcUserAgent = OIDExternalUserAgentIOS(presenting: hostingController)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
viewModel.callback = { [weak self] result in
|
||||
guard let self else { return }
|
||||
MXLog.info("[SoftLogoutCoordinator] SoftLogoutViewModel did complete with result: \(result).")
|
||||
|
||||
switch result {
|
||||
case .login(let password):
|
||||
self.login(withPassword: password)
|
||||
case .forgotPassword:
|
||||
self.showForgotPasswordScreen()
|
||||
case .clearAllData:
|
||||
self.callback?(.clearAllData)
|
||||
case .continueWithOIDC:
|
||||
self.loginWithOIDC()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(SoftLogoutScreen(context: viewModel.context))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
static let loadingIndicatorIdentifier = "SoftLogoutLoading"
|
||||
|
||||
/// Show an activity indicator whilst loading.
|
||||
@MainActor private func startLoading() {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
|
||||
type: .modal,
|
||||
title: L10n.commonLoading,
|
||||
persistent: true))
|
||||
}
|
||||
|
||||
/// Hide the currently displayed activity indicator.
|
||||
@MainActor private func stopLoading() {
|
||||
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.loadingIndicatorIdentifier)
|
||||
}
|
||||
|
||||
/// Shows the forgot password screen.
|
||||
@MainActor private func showForgotPasswordScreen() {
|
||||
viewModel.displayError(.alert("Not implemented."))
|
||||
}
|
||||
|
||||
/// Login with the supplied username and password.
|
||||
@MainActor private func login(withPassword password: String) {
|
||||
let username = parameters.credentials.userId
|
||||
|
||||
startLoading()
|
||||
|
||||
Task {
|
||||
switch await authenticationService.login(username: username,
|
||||
password: password,
|
||||
initialDeviceName: UIDevice.current.initialDeviceName,
|
||||
deviceId: parameters.credentials.deviceId) {
|
||||
case .success(let userSession):
|
||||
callback?(.signedIn(userSession))
|
||||
stopLoading()
|
||||
case .failure(let error):
|
||||
stopLoading()
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loginWithOIDC() {
|
||||
guard let oidcUserAgent else {
|
||||
handleError(AuthenticationServiceError.oidcError(.notSupported))
|
||||
return
|
||||
}
|
||||
|
||||
startLoading()
|
||||
|
||||
Task {
|
||||
switch await authenticationService.loginWithOIDC(userAgent: oidcUserAgent) {
|
||||
case .success(let userSession):
|
||||
callback?(.signedIn(userSession))
|
||||
stopLoading()
|
||||
case .failure(let error):
|
||||
stopLoading()
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes an error to either update the flow or display it to the user.
|
||||
private func handleError(_ error: AuthenticationServiceError) {
|
||||
switch error {
|
||||
case .invalidCredentials:
|
||||
viewModel.displayError(.alert(L10n.screenLoginErrorInvalidCredentials))
|
||||
case .accountDeactivated:
|
||||
viewModel.displayError(.alert(L10n.screenLoginErrorDeactivatedAccount))
|
||||
default:
|
||||
viewModel.displayError(.alert(L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SoftLogoutScreenCredentials {
|
||||
let userId: String
|
||||
let homeserverName: String
|
||||
let userDisplayName: String
|
||||
let deviceId: String?
|
||||
}
|
||||
|
||||
enum SoftLogoutScreenViewModelAction: CustomStringConvertible {
|
||||
/// Login with password
|
||||
case login(String)
|
||||
/// Forgot password
|
||||
case forgotPassword
|
||||
/// Clear all user data
|
||||
case clearAllData
|
||||
/// Continue using OIDC.
|
||||
case continueWithOIDC
|
||||
|
||||
/// A string representation of the result, ignoring any associated values that could leak PII.
|
||||
var description: String {
|
||||
switch self {
|
||||
case .login:
|
||||
return "login"
|
||||
case .forgotPassword:
|
||||
return "forgotPassword"
|
||||
case .clearAllData:
|
||||
return "clearAllData"
|
||||
case .continueWithOIDC:
|
||||
return "continueWithOIDC"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SoftLogoutScreenViewState: BindableState {
|
||||
/// Soft logout credentials
|
||||
var credentials: SoftLogoutScreenCredentials
|
||||
|
||||
/// Data about the selected homeserver.
|
||||
var homeserver: LoginHomeserver
|
||||
|
||||
/// Flag indicating soft logged out user needs backup for some keys
|
||||
var keyBackupNeeded: Bool
|
||||
|
||||
/// View state that can be bound to from SwiftUI.
|
||||
var bindings: SoftLogoutScreenBindings
|
||||
|
||||
/// The types of login supported by the homeserver.
|
||||
var loginMode: LoginMode { homeserver.loginMode }
|
||||
|
||||
/// Whether to show recover encryption keys message
|
||||
var showRecoverEncryptionKeysMessage: Bool {
|
||||
keyBackupNeeded
|
||||
}
|
||||
|
||||
/// `true` when valid credentials have been entered and a homeserver has been loaded.
|
||||
var canSubmit: Bool {
|
||||
!bindings.password.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
struct SoftLogoutScreenBindings {
|
||||
/// The password input by the user.
|
||||
var password: String
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<SoftLogoutScreenErrorType>?
|
||||
}
|
||||
|
||||
enum SoftLogoutScreenViewAction {
|
||||
/// Login.
|
||||
case login
|
||||
/// Forgot password
|
||||
case forgotPassword
|
||||
/// Clear all user data.
|
||||
case clearAllData
|
||||
/// Continue using OIDC.
|
||||
case continueWithOIDC
|
||||
}
|
||||
|
||||
enum SoftLogoutScreenErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
/// An unknown error occurred.
|
||||
case unknown
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
typealias SoftLogoutScreenViewModelType = StateStoreViewModel<SoftLogoutScreenViewState, SoftLogoutScreenViewAction>
|
||||
|
||||
class SoftLogoutScreenViewModel: SoftLogoutScreenViewModelType, SoftLogoutScreenViewModelProtocol {
|
||||
var callback: (@MainActor (SoftLogoutScreenViewModelAction) -> Void)?
|
||||
|
||||
init(credentials: SoftLogoutScreenCredentials,
|
||||
homeserver: LoginHomeserver,
|
||||
keyBackupNeeded: Bool,
|
||||
password: String = "") {
|
||||
let bindings = SoftLogoutScreenBindings(password: password)
|
||||
let viewState = SoftLogoutScreenViewState(credentials: credentials,
|
||||
homeserver: homeserver,
|
||||
keyBackupNeeded: keyBackupNeeded,
|
||||
bindings: bindings)
|
||||
super.init(initialViewState: viewState)
|
||||
}
|
||||
|
||||
override func process(viewAction: SoftLogoutScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .login:
|
||||
callback?(.login(state.bindings.password))
|
||||
case .forgotPassword:
|
||||
callback?(.forgotPassword)
|
||||
case .clearAllData:
|
||||
callback?(.clearAllData)
|
||||
case .continueWithOIDC:
|
||||
callback?(.continueWithOIDC)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor func displayError(_ type: SoftLogoutScreenErrorType) {
|
||||
switch type {
|
||||
case .alert(let message):
|
||||
state.bindings.alertInfo = AlertInfo(id: type,
|
||||
title: L10n.commonError,
|
||||
message: message)
|
||||
case .unknown:
|
||||
state.bindings.alertInfo = AlertInfo(id: type)
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
protocol SoftLogoutScreenViewModelProtocol {
|
||||
var callback: (@MainActor (SoftLogoutScreenViewModelAction) -> Void)? { get set }
|
||||
var context: SoftLogoutScreenViewModelType.Context { get }
|
||||
|
||||
/// Display an error to the user.
|
||||
@MainActor func displayError(_ type: SoftLogoutScreenErrorType)
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SoftLogoutScreen: View {
|
||||
@State private var showingClearDataConfirmation = false
|
||||
|
||||
/// The focus state of the password text field.
|
||||
@FocusState private var isPasswordFocused: Bool
|
||||
|
||||
@ObservedObject var context: SoftLogoutScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
header
|
||||
.padding(.top, UIConstants.titleTopPaddingToNavigationBar)
|
||||
.padding(.bottom, 36)
|
||||
|
||||
switch context.viewState.loginMode {
|
||||
case .password:
|
||||
loginForm
|
||||
case .oidc:
|
||||
oidcButton
|
||||
default:
|
||||
loginUnavailableText
|
||||
}
|
||||
|
||||
clearDataForm
|
||||
.padding(.top, 16)
|
||||
}
|
||||
.readableFrame()
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
.background(Color.element.background.ignoresSafeArea())
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
}
|
||||
|
||||
/// The title, message and icon at the top of the screen.
|
||||
var header: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text(UntranslatedL10n.softLogoutSigninTitle)
|
||||
.font(.element.title2Bold)
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.title)
|
||||
|
||||
Text(UntranslatedL10n.softLogoutSigninNotice(context.viewState.credentials.homeserverName, context.viewState.credentials.userDisplayName, context.viewState.credentials.userId))
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.message)
|
||||
|
||||
if context.viewState.showRecoverEncryptionKeysMessage {
|
||||
Text(UntranslatedL10n.softLogoutSigninE2eWarningNotice)
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The form with text fields for username and password, along with a submit button.
|
||||
var loginForm: some View {
|
||||
VStack(spacing: 14) {
|
||||
SecureField(L10n.commonPassword, text: $context.password)
|
||||
.focused($isPasswordFocused)
|
||||
.textFieldStyle(.elementInput())
|
||||
.textContentType(.password)
|
||||
.submitLabel(.done)
|
||||
.onSubmit(submit)
|
||||
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.password)
|
||||
|
||||
Button { context.send(viewAction: .forgotPassword) } label: {
|
||||
Text(UntranslatedL10n.softLogoutForgotPassword)
|
||||
.font(.element.body)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.padding(.bottom, 8)
|
||||
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.forgotPassword)
|
||||
|
||||
Button(action: submit) {
|
||||
Text(L10n.actionNext)
|
||||
}
|
||||
.buttonStyle(.elementAction(.xLarge))
|
||||
.disabled(!context.viewState.canSubmit)
|
||||
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.next)
|
||||
}
|
||||
}
|
||||
|
||||
/// The OIDC button that can be used for login.
|
||||
var oidcButton: some View {
|
||||
Button { context.send(viewAction: .continueWithOIDC) } label: {
|
||||
Text(L10n.actionContinue)
|
||||
}
|
||||
.buttonStyle(.elementAction(.xLarge))
|
||||
}
|
||||
|
||||
/// Text shown if neither password or OIDC login is supported.
|
||||
var loginUnavailableText: some View {
|
||||
Text(L10n.screenLoginErrorUnsupportedAuthentication)
|
||||
.font(.body)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
/// The text field and submit button where the user enters an email address.
|
||||
var clearDataForm: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text(UntranslatedL10n.softLogoutClearDataTitle)
|
||||
.font(.element.title2Bold)
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearDataTitle)
|
||||
|
||||
Text(UntranslatedL10n.softLogoutClearDataNotice)
|
||||
.font(.element.body)
|
||||
.multilineTextAlignment(.leading)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearDataMessage)
|
||||
.padding(.bottom, 12)
|
||||
|
||||
Button(action: clearData) {
|
||||
Text(UntranslatedL10n.softLogoutClearDataSubmit)
|
||||
}
|
||||
.buttonStyle(.elementAction(.xLarge, color: .element.alert))
|
||||
.accessibilityIdentifier(A11yIdentifiers.softLogoutScreen.clearData)
|
||||
.alert(UntranslatedL10n.softLogoutClearDataDialogTitle,
|
||||
isPresented: $showingClearDataConfirmation) {
|
||||
Button(L10n.screenSignoutConfirmationDialogSubmit,
|
||||
role: .destructive,
|
||||
action: clearData)
|
||||
} message: {
|
||||
Text(UntranslatedL10n.softLogoutClearDataDialogContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the `login` view action so long as a valid email address has been input.
|
||||
func submit() {
|
||||
guard context.viewState.canSubmit else { return }
|
||||
context.send(viewAction: .login)
|
||||
}
|
||||
|
||||
/// Sends the `forgotPassword` view action.
|
||||
func forgotPassword() {
|
||||
context.send(viewAction: .forgotPassword)
|
||||
}
|
||||
|
||||
/// Sends the `clearAllData` view action.
|
||||
func clearData() {
|
||||
context.send(viewAction: .clearAllData)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct SoftLogoutScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ForEach(MockSoftLogoutScreenState.allCases) { state in
|
||||
screen(for: state.viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
static func screen(for viewModel: SoftLogoutScreenViewModel) -> some View {
|
||||
NavigationStack {
|
||||
SoftLogoutScreen(context: viewModel.context)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button { } label: {
|
||||
Text("\(Image(systemName: "chevron.backward")) Back")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user