mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-30 21:26:57 +02:00
Add FTUE display name screen.
Use UserSession instead of userId & MXSession in OnboardingCoordinator
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// Copyright 2021 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 OnboardingDisplayNameCoordinatorParameters {
|
||||
let userSession: UserSession
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
final class OnboardingDisplayNameCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: OnboardingDisplayNameCoordinatorParameters
|
||||
private let onboardingDisplayNameHostingController: VectorHostingController
|
||||
private var onboardingDisplayNameViewModel: OnboardingDisplayNameViewModelProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: ((UserSession) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: OnboardingDisplayNameCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
let viewModel = OnboardingDisplayNameViewModel.makeOnboardingDisplayNameViewModel(onboardingDisplayNameService: OnboardingDisplayNameService(userSession: parameters.userSession))
|
||||
let view = OnboardingDisplayNameScreen(viewModel: viewModel.context)
|
||||
onboardingDisplayNameViewModel = viewModel
|
||||
onboardingDisplayNameHostingController = VectorHostingController(rootView: view)
|
||||
onboardingDisplayNameHostingController.enableNavigationBarScrollEdgesAppearance = true
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
MXLog.debug("[OnboardingDisplayNameCoordinator] did start.")
|
||||
onboardingDisplayNameViewModel.completion = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
MXLog.debug("[OnboardingDisplayNameCoordinator] OnboardingDisplayNameViewModel did complete.")
|
||||
self.completion?(self.parameters.userSession)
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.onboardingDisplayNameHostingController
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// Copyright 2021 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.
|
||||
@available(iOS 14.0, *)
|
||||
enum MockOnboardingDisplayNameScreenState: MockScreenState, CaseIterable {
|
||||
// A case for each state you want to represent
|
||||
// with specific, minimal associated data that will allow you
|
||||
// mock that screen.
|
||||
case emptyTextField
|
||||
case filledTextField(displayName: String)
|
||||
case operationInProgress(displayName: String)
|
||||
|
||||
/// The associated screen
|
||||
var screenType: Any.Type {
|
||||
OnboardingDisplayNameScreen.self
|
||||
}
|
||||
|
||||
/// A list of screen state definitions
|
||||
static var allCases: [MockOnboardingDisplayNameScreenState] {
|
||||
[
|
||||
MockOnboardingDisplayNameScreenState.emptyTextField,
|
||||
MockOnboardingDisplayNameScreenState.filledTextField(displayName: "Test User"),
|
||||
MockOnboardingDisplayNameScreenState.operationInProgress(displayName: "Test User"),
|
||||
]
|
||||
}
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
var screenView: ([Any], AnyView) {
|
||||
let service: MockOnboardingDisplayNameService
|
||||
switch self {
|
||||
case .emptyTextField:
|
||||
service = MockOnboardingDisplayNameService()
|
||||
case .filledTextField(let displayName):
|
||||
service = MockOnboardingDisplayNameService(displayName: displayName)
|
||||
case .operationInProgress(let displayName):
|
||||
service = MockOnboardingDisplayNameService(displayName: displayName, isWaiting: true)
|
||||
}
|
||||
let viewModel = OnboardingDisplayNameViewModel.makeOnboardingDisplayNameViewModel(onboardingDisplayNameService: service)
|
||||
|
||||
// can simulate service and viewModel actions here if needs be.
|
||||
|
||||
return (
|
||||
[service, viewModel], AnyView(OnboardingDisplayNameScreen(viewModel: viewModel.context))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
// MARK: View model
|
||||
|
||||
enum OnboardingDisplayNameViewModelResult {
|
||||
// Can probably be removed
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct OnboardingDisplayNameViewState: BindableState {
|
||||
var isWaiting = false
|
||||
var bindings: OnboardingDisplayNameBindings
|
||||
}
|
||||
|
||||
struct OnboardingDisplayNameBindings {
|
||||
var displayName: String
|
||||
var alertInfo: AlertInfo<Int>?
|
||||
}
|
||||
|
||||
enum OnboardingDisplayNameViewAction {
|
||||
case save
|
||||
case skip
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
import Combine
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias OnboardingDisplayNameViewModelType = StateStoreViewModel<OnboardingDisplayNameViewState,
|
||||
Never,
|
||||
OnboardingDisplayNameViewAction>
|
||||
@available(iOS 14, *)
|
||||
class OnboardingDisplayNameViewModel: OnboardingDisplayNameViewModelType, OnboardingDisplayNameViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let onboardingDisplayNameService: OnboardingDisplayNameServiceProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: (() -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
static func makeOnboardingDisplayNameViewModel(onboardingDisplayNameService: OnboardingDisplayNameServiceProtocol) -> OnboardingDisplayNameViewModelProtocol {
|
||||
return OnboardingDisplayNameViewModel(onboardingDisplayNameService: onboardingDisplayNameService)
|
||||
}
|
||||
|
||||
private init(onboardingDisplayNameService: OnboardingDisplayNameServiceProtocol) {
|
||||
self.onboardingDisplayNameService = onboardingDisplayNameService
|
||||
super.init(initialViewState: Self.defaultState(onboardingDisplayNameService: onboardingDisplayNameService))
|
||||
}
|
||||
|
||||
private static func defaultState(onboardingDisplayNameService: OnboardingDisplayNameServiceProtocol) -> OnboardingDisplayNameViewState {
|
||||
// Start with a blank display name to encourage the user not to just use the first part of their MXID.
|
||||
return OnboardingDisplayNameViewState(bindings: OnboardingDisplayNameBindings(displayName: ""))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: OnboardingDisplayNameViewAction) {
|
||||
switch viewAction {
|
||||
case .save:
|
||||
setDisplayName()
|
||||
case .skip:
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setDisplayName() {
|
||||
state.isWaiting = true
|
||||
|
||||
onboardingDisplayNameService.setDisplayName(context.displayName) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
self.state.isWaiting = false
|
||||
|
||||
switch result {
|
||||
case .success(_):
|
||||
self.completion?()
|
||||
case .failure(let error):
|
||||
self.state.bindings.alertInfo = AlertInfo(error: error as NSError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright 2021 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 OnboardingDisplayNameViewModelProtocol {
|
||||
|
||||
var completion: (() -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
static func makeOnboardingDisplayNameViewModel(onboardingDisplayNameService: OnboardingDisplayNameServiceProtocol) -> OnboardingDisplayNameViewModelProtocol
|
||||
@available(iOS 14, *)
|
||||
var context: OnboardingDisplayNameViewModelType.Context { get }
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// Copyright 2021 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 Combine
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingDisplayNameService: OnboardingDisplayNameServiceProtocol {
|
||||
|
||||
enum ServiceError: Error {
|
||||
case unknown
|
||||
}
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let userSession: UserSession
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var displayName: String? {
|
||||
userSession.account.userDisplayName
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(userSession: UserSession) {
|
||||
self.userSession = userSession
|
||||
}
|
||||
|
||||
func setDisplayName(_ displayName: String, completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
userSession.account.setUserDisplayName(displayName) {
|
||||
completion(.success(true))
|
||||
} failure: { error in
|
||||
completion(.failure(error ?? ServiceError.unknown))
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright 2021 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 Combine
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class MockOnboardingDisplayNameService: OnboardingDisplayNameServiceProtocol {
|
||||
var displayName: String?
|
||||
|
||||
#warning("isWaiting isn't handled.")
|
||||
init(displayName: String? = nil, isWaiting: Bool = false) {
|
||||
self.displayName = displayName
|
||||
}
|
||||
|
||||
func setDisplayName(_ displayName: String, completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
|
||||
self.displayName = displayName
|
||||
completion(.success(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright 2021 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 Combine
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
protocol OnboardingDisplayNameServiceProtocol {
|
||||
/// The user's current display name read from the `UserSession`.
|
||||
var displayName: String? { get }
|
||||
|
||||
/// Update the user's display name.
|
||||
func setDisplayName(_ displayName: String, completion: @escaping (Result<Bool, Error>) -> Void)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Copyright 2021 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 XCTest
|
||||
import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingDisplayNameUITests: MockScreenTest {
|
||||
|
||||
override class var screenType: MockScreenState.Type {
|
||||
return MockOnboardingDisplayNameScreenState.self
|
||||
}
|
||||
|
||||
override class func createTest() -> MockScreenTest {
|
||||
return OnboardingDisplayNameUITests(selector: #selector(verifyOnboardingDisplayNameScreen))
|
||||
}
|
||||
|
||||
func verifyOnboardingDisplayNameScreen() throws {
|
||||
guard let screenState = screenState as? MockOnboardingDisplayNameScreenState else { fatalError("no screen") }
|
||||
switch screenState {
|
||||
case .presence(let presence):
|
||||
verifyOnboardingDisplayNamePresence(presence: presence)
|
||||
case .longDisplayName(let name):
|
||||
verifyOnboardingDisplayNameLongName(name: name)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyOnboardingDisplayNamePresence(presence: OnboardingDisplayNamePresence) {
|
||||
let presenceText = app.staticTexts["presenceText"]
|
||||
XCTAssert(presenceText.exists)
|
||||
XCTAssertEqual(presenceText.label, presence.title)
|
||||
}
|
||||
|
||||
func verifyOnboardingDisplayNameLongName(name: String) {
|
||||
let displayNameText = app.staticTexts["displayNameText"]
|
||||
XCTAssert(displayNameText.exists)
|
||||
XCTAssertEqual(displayNameText.label, name)
|
||||
}
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright 2021 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 XCTest
|
||||
import Combine
|
||||
|
||||
@testable import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingDisplayNameViewModelTests: XCTestCase {
|
||||
private enum Constants {
|
||||
static let presenceInitialValue: OnboardingDisplayNamePresence = .offline
|
||||
static let displayName = "Alice"
|
||||
}
|
||||
var service: MockOnboardingDisplayNameService!
|
||||
var viewModel: OnboardingDisplayNameViewModelProtocol!
|
||||
var context: OnboardingDisplayNameViewModelType.Context!
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
override func setUpWithError() throws {
|
||||
service = MockOnboardingDisplayNameService(displayName: Constants.displayName, presence: Constants.presenceInitialValue)
|
||||
viewModel = OnboardingDisplayNameViewModel.makeOnboardingDisplayNameViewModel(onboardingDisplayNameService: service)
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
XCTAssertEqual(context.viewState.displayName, Constants.displayName)
|
||||
XCTAssertEqual(context.viewState.presence, Constants.presenceInitialValue)
|
||||
}
|
||||
|
||||
func testFirstPresenceReceived() throws {
|
||||
let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(1).first()
|
||||
XCTAssertEqual(try xcAwait(presencePublisher), [Constants.presenceInitialValue])
|
||||
}
|
||||
|
||||
func testPresenceUpdatesReceived() throws {
|
||||
let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(3).first()
|
||||
let awaitDeferred = xcAwaitDeferred(presencePublisher)
|
||||
let newPresenceValue1: OnboardingDisplayNamePresence = .online
|
||||
let newPresenceValue2: OnboardingDisplayNamePresence = .idle
|
||||
service.simulateUpdate(presence: newPresenceValue1)
|
||||
service.simulateUpdate(presence: newPresenceValue2)
|
||||
XCTAssertEqual(try awaitDeferred(), [Constants.presenceInitialValue, newPresenceValue1, newPresenceValue2])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright 2021 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
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct OnboardingDisplayNameScreen: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme: ThemeSwiftUI
|
||||
|
||||
@State private var isEditingTextField = false
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@ObservedObject var viewModel: OnboardingDisplayNameViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
header
|
||||
.padding(.bottom, 32)
|
||||
|
||||
textField
|
||||
.padding(.horizontal, 2)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
buttons
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
.accentColor(theme.colors.accent)
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.alert(item: $viewModel.alertInfo) { $0.alert }
|
||||
}
|
||||
|
||||
/// The icon, title and message views.
|
||||
var header: some View {
|
||||
VStack(spacing: 8) {
|
||||
Image(Asset.Images.onboardingCongratulationsIcon.name)
|
||||
.renderingMode(.template)
|
||||
.foregroundColor(theme.colors.accent)
|
||||
.padding(.bottom, 8)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Text(VectorL10n.onboardingDisplayNameTitle)
|
||||
.font(theme.fonts.title2B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
|
||||
Text(VectorL10n.onboardingDisplayNameMessage)
|
||||
.font(theme.fonts.subheadline)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
}
|
||||
}
|
||||
|
||||
/// The text field used to enter the displayname along with a hint.
|
||||
var textField: some View {
|
||||
VStack(spacing: 4) {
|
||||
TextField(VectorL10n.onboardingDisplayNamePlaceholder, text: $viewModel.displayName) {
|
||||
isEditingTextField = $0
|
||||
}
|
||||
.textFieldStyle(BorderedInputFieldStyle(theme: _theme, isEditing: isEditingTextField))
|
||||
|
||||
Text(VectorL10n.onboardingDisplayNameHint)
|
||||
.font(theme.fonts.caption2)
|
||||
.foregroundColor(theme.colors.tertiaryContent)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
/// The main action buttons in the form.
|
||||
var buttons: some View {
|
||||
VStack(spacing: 8) {
|
||||
Button(VectorL10n.onboardingDisplayNameSave) {
|
||||
viewModel.send(viewAction: .save)
|
||||
}
|
||||
.buttonStyle(PrimaryActionButtonStyle())
|
||||
.disabled(viewModel.displayName.isEmpty || viewModel.viewState.isWaiting)
|
||||
|
||||
#warning("Use font/theme")
|
||||
Button { viewModel.send(viewAction: .skip) } label: {
|
||||
Text(VectorL10n.onboardingDisplayNameSkip)
|
||||
.padding(12)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct OnboardingDisplayName_Previews: PreviewProvider {
|
||||
static let stateRenderer = MockOnboardingDisplayNameScreenState.stateRenderer
|
||||
static var previews: some View {
|
||||
stateRenderer.screenGroup(addNavigation: true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user