Simplify the screen templates

This commit is contained in:
Stefan Ceriu
2023-08-02 13:24:57 +03:00
committed by Stefan Ceriu
parent 28c2490910
commit 6e5854abf9
8 changed files with 60 additions and 160 deletions
@@ -148,10 +148,8 @@ class MockScreen: Identifiable {
userIndicatorController: ServiceLocator.shared.userIndicatorController)) userIndicatorController: ServiceLocator.shared.userIndicatorController))
navigationStackCoordinator.setRootCoordinator(coordinator) navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator return navigationStackCoordinator
case .simpleRegular: case .templateScreen:
return TemplateScreenCoordinator(parameters: .init(promptType: .regular)) return TemplateScreenCoordinator(parameters: .init())
case .simpleUpgrade:
return TemplateScreenCoordinator(parameters: .init(promptType: .upgrade))
case .home: case .home:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let session = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:matrix.org"), let session = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:matrix.org"),
@@ -28,8 +28,7 @@ enum UITestsScreenIdentifier: String {
case analyticsPrompt case analyticsPrompt
case analyticsSettingsScreen case analyticsSettingsScreen
case migration case migration
case simpleRegular case templateScreen
case simpleUpgrade
case home case home
case settings case settings
case bugReport case bugReport
@@ -17,13 +17,10 @@
import Combine import Combine
import SwiftUI import SwiftUI
struct TemplateScreenCoordinatorParameters { struct TemplateScreenCoordinatorParameters { }
let promptType: TemplateScreenPromptType
}
enum TemplateScreenCoordinatorAction { enum TemplateScreenCoordinatorAction {
case accept case done
case cancel
// Consider adding CustomStringConvertible conformance if the actions contain PII // Consider adding CustomStringConvertible conformance if the actions contain PII
} }
@@ -41,18 +38,17 @@ final class TemplateScreenCoordinator: CoordinatorProtocol {
init(parameters: TemplateScreenCoordinatorParameters) { init(parameters: TemplateScreenCoordinatorParameters) {
self.parameters = parameters self.parameters = parameters
viewModel = TemplateScreenViewModel(promptType: parameters.promptType) viewModel = TemplateScreenViewModel()
} }
func start() { func start() {
viewModel.actions.sink { [weak self] action in viewModel.actions.sink { [weak self] action in
MXLog.info("Coordinator: received view model action: \(action)")
guard let self else { return } guard let self else { return }
switch action { switch action {
case .accept: case .done:
MXLog.info("User accepted the prompt.") self.actionsSubject.send(.done)
self.actionsSubject.send(.accept)
case .cancel:
self.actionsSubject.send(.cancel)
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -16,50 +16,25 @@
import Foundation import Foundation
enum TemplateScreenPromptType {
case regular
case upgrade
}
extension TemplateScreenPromptType: Identifiable, CaseIterable {
var id: Self { self }
var title: String {
switch self {
case .regular:
return "Make this chat public?"
case .upgrade:
return "Privacy warning"
}
}
var imageSystemName: String {
switch self {
case .regular:
return "app.gift"
case .upgrade:
return "shield"
}
}
}
enum TemplateScreenViewModelAction { enum TemplateScreenViewModelAction {
case accept case done
case cancel
// Consider adding CustomStringConvertible conformance if the actions contain PII // Consider adding CustomStringConvertible conformance if the actions contain PII
} }
struct TemplateScreenViewState: BindableState { struct TemplateScreenViewState: BindableState {
var promptType: TemplateScreenPromptType var title: String
var count: Int var placeholder: String
var bindings: TemplateScreenViewStateBindings
}
struct TemplateScreenViewStateBindings {
var composerText: String
} }
enum TemplateScreenViewAction { enum TemplateScreenViewAction {
case incrementCount case done
case decrementCount case textChanged
case accept
case cancel
// Consider adding CustomStringConvertible conformance if the actions contain PII // Consider adding CustomStringConvertible conformance if the actions contain PII
} }
@@ -26,22 +26,22 @@ class TemplateScreenViewModel: TemplateScreenViewModelType, TemplateScreenViewMo
actionsSubject.eraseToAnyPublisher() actionsSubject.eraseToAnyPublisher()
} }
init(promptType: TemplateScreenPromptType, initialCount: Int = 0) { init() {
super.init(initialViewState: TemplateScreenViewState(promptType: promptType, count: 0)) super.init(initialViewState: TemplateScreenViewState(title: "Template title",
placeholder: "Enter something here",
bindings: .init(composerText: "Initial composer text")))
} }
// MARK: - Public // MARK: - Public
override func process(viewAction: TemplateScreenViewAction) { override func process(viewAction: TemplateScreenViewAction) {
MXLog.info("View model: received view action: \(viewAction)")
switch viewAction { switch viewAction {
case .accept: case .done:
actionsSubject.send(.accept) actionsSubject.send(.done)
case .cancel: case .textChanged:
actionsSubject.send(.cancel) MXLog.info("View model: composer text changed to: \(state.bindings.composerText)")
case .incrementCount:
state.count += 1
case .decrementCount:
state.count -= 1
} }
} }
} }
@@ -17,75 +17,30 @@
import SwiftUI import SwiftUI
struct TemplateScreen: View { struct TemplateScreen: View {
@Environment(\.colorScheme) private var colorScheme
var counterColor: Color {
colorScheme == .light ? .compound.textSecondary : .compound.textInfoPrimary
}
@ObservedObject var context: TemplateScreenViewModel.Context @ObservedObject var context: TemplateScreenViewModel.Context
var body: some View { var body: some View {
ScrollView { Form {
mainContent Section {
.padding(.top, 50) TextField(text: $context.composerText) {
.padding(.horizontal) Text(context.viewState.placeholder)
.readableFrame() .compoundFormTextFieldPlaceholder()
}
.safeAreaInset(edge: .bottom) {
buttons
.padding(.horizontal)
.padding(.vertical)
.readableFrame()
.background(Color.compound.bgSubtleSecondary)
}
}
/// The main content of the view to be shown in a scroll view.
var mainContent: some View {
VStack(spacing: 36) {
Text(context.viewState.promptType.title)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textPrimary)
.accessibilityIdentifier("title")
Image(systemName: context.viewState.promptType.imageSystemName)
.resizable()
.scaledToFit()
.frame(width: 100)
HStack {
Text("Counter: \(context.viewState.count)")
.font(.compound.bodyLG)
.multilineTextAlignment(.center)
.foregroundColor(counterColor)
Button("−") {
context.send(viewAction: .decrementCount)
} }
.buttonStyle(.elementGhost()) .textFieldStyle(.compoundForm)
Button("+") { Button {
context.send(viewAction: .incrementCount) context.send(viewAction: .done)
} label: {
Label("Done", systemImage: "door.left.hand.closed")
} }
.buttonStyle(.elementGhost()) .buttonStyle(.compoundFormCentred())
} }
.compoundFormSection()
} }
} .compoundForm()
.navigationTitle(context.viewState.title)
/// The action buttons shown at the bottom of the view. .onChange(of: context.composerText) { _ in
var buttons: some View { context.send(viewAction: .textChanged)
VStack {
Button { context.send(viewAction: .accept) } label: {
Text("Accept")
}
.buttonStyle(.elementAction(.xLarge))
Button { context.send(viewAction: .cancel) } label: {
Text("Cancel")
.padding(.vertical, 12)
}
} }
} }
} }
@@ -93,12 +48,10 @@ struct TemplateScreen: View {
// MARK: - Previews // MARK: - Previews
struct TemplateScreen_Previews: PreviewProvider { struct TemplateScreen_Previews: PreviewProvider {
static let regularViewModel = TemplateScreenViewModel(promptType: .regular) static let viewModel = TemplateScreenViewModel()
static let upgradeViewModel = TemplateScreenViewModel(promptType: .upgrade)
static var previews: some View { static var previews: some View {
TemplateScreen(context: regularViewModel.context) NavigationStack {
.previewDisplayName("Regular") TemplateScreen(context: viewModel.context)
TemplateScreen(context: upgradeViewModel.context) }
.previewDisplayName("Upgrade")
} }
} }
@@ -19,25 +19,12 @@ import XCTest
@MainActor @MainActor
class TemplateScreenUITests: XCTestCase { class TemplateScreenUITests: XCTestCase {
func testRegularScreen() async throws { func testScreen() async throws {
let app = Application.launch(.simpleRegular) let app = Application.launch(.templateScreen)
let title = app.staticTexts["title"] let title = app.staticTexts["Template title"]
XCTAssert(title.exists) XCTAssert(title.exists)
XCTAssertEqual(title.label, "Make this chat public?")
try await app.assertScreenshot(.simpleRegular) try await app.assertScreenshot(.templateScreen)
}
func testUpgradeScreen() async throws {
let app = Application.launch(.simpleUpgrade)
let title = app.staticTexts["title"]
XCTAssert(title.exists)
XCTAssertEqual(title.label, "Privacy warning")
try await app.assertScreenshot(.simpleUpgrade)
} }
} }
@@ -20,10 +20,6 @@ import XCTest
@MainActor @MainActor
class TemplateScreenViewModelTests: XCTestCase { class TemplateScreenViewModelTests: XCTestCase {
private enum Constants {
static let counterInitialValue = 0
}
var viewModel: TemplateScreenViewModelProtocol! var viewModel: TemplateScreenViewModelProtocol!
var context: TemplateScreenViewModelType.Context { var context: TemplateScreenViewModelType.Context {
@@ -31,21 +27,17 @@ class TemplateScreenViewModelTests: XCTestCase {
} }
override func setUpWithError() throws { override func setUpWithError() throws {
viewModel = TemplateScreenViewModel(promptType: .regular, initialCount: Constants.counterInitialValue) viewModel = TemplateScreenViewModel()
} }
func testInitialState() { func testInitialState() {
XCTAssertEqual(context.viewState.count, Constants.counterInitialValue) XCTAssertFalse(context.viewState.placeholder.isEmpty)
XCTAssertFalse(context.composerText.isEmpty)
} }
func testCounter() async throws { func testCounter() async throws {
context.send(viewAction: .incrementCount) context.composerText = "123"
XCTAssertEqual(context.viewState.count, 1) context.send(viewAction: .textChanged)
XCTAssertEqual(context.composerText, "123")
context.send(viewAction: .incrementCount)
XCTAssertEqual(context.viewState.count, 2)
context.send(viewAction: .decrementCount)
XCTAssertEqual(context.viewState.count, 1)
} }
} }