Prepare onboarding flow for personalisation screens.

Add a BuildSetting to control the display of the personalisation screens.
Add a property to delay the AuthenticationCoordinator from showing any screens.
Add Congratulations screen to onboarding flow.
This commit is contained in:
Doug
2022-02-24 17:24:04 +00:00
parent 7934501811
commit 9396338c19
23 changed files with 642 additions and 91 deletions
@@ -125,6 +125,8 @@ struct AnalyticsPrompt: View {
.background(theme.colors.background.ignoresSafeArea())
.accentColor(theme.colors.accent)
}
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
@@ -20,6 +20,7 @@ import Foundation
@available(iOS 14.0, *)
enum MockAppScreens {
static let appScreens: [MockScreenState.Type] = [
MockOnboardingCongratulationsScreenState.self,
MockOnboardingUseCaseSelectionScreenState.self,
MockOnboardingSplashScreenScreenState.self,
MockLocationSharingScreenState.self,
@@ -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 OnboardingCongratulationsCoordinatorParameters {
let userId: String
}
final class OnboardingCongratulationsCoordinator: Coordinator, Presentable {
// MARK: - Properties
// MARK: Private
private let parameters: OnboardingCongratulationsCoordinatorParameters
private let onboardingCongratulationsHostingController: UIViewController
private var onboardingCongratulationsViewModel: OnboardingCongratulationsViewModelProtocol
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
var completion: ((OnboardingCongratulationsViewModelResult) -> Void)?
// MARK: - Setup
@available(iOS 14.0, *)
init(parameters: OnboardingCongratulationsCoordinatorParameters) {
self.parameters = parameters
let viewModel = OnboardingCongratulationsViewModel(userId: parameters.userId)
let view = OnboardingCongratulations(viewModel: viewModel.context)
onboardingCongratulationsViewModel = viewModel
onboardingCongratulationsHostingController = VectorHostingController(rootView: view)
}
// MARK: - Public
func start() {
MXLog.debug("[OnboardingCongratulationsCoordinator] did start.")
onboardingCongratulationsViewModel.completion = { [weak self] result in
MXLog.debug("[OnboardingCongratulationsCoordinator] OnboardingCongratulationsViewModel did complete with result: \(result).")
guard let self = self else { return }
self.completion?(result)
}
}
func toPresentable() -> UIViewController {
return self.onboardingCongratulationsHostingController
}
}
@@ -0,0 +1,46 @@
//
// 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 MockOnboardingCongratulationsScreenState: MockScreenState, CaseIterable {
// A case for each state you want to represent
// with specific, minimal associated data that will allow you
// mock that screen.
case congratulations
/// The associated screen
var screenType: Any.Type {
OnboardingCongratulationsScreen.self
}
/// Generate the view struct for the screen state.
var screenView: ([Any], AnyView) {
let viewModel = OnboardingCongratulationsViewModel(userId: "@testuser:example.com")
// can simulate service and viewModel actions here if needs be.
return (
[self, viewModel],
AnyView(OnboardingCongratulationsScreen(viewModel: viewModel.context)
.addDependency(MockAvatarService.example))
)
}
}
@@ -0,0 +1,37 @@
//
// 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: - Coordinator
// MARK: View model
enum OnboardingCongratulationsViewModelResult {
case personaliseProfile
case takeMeHome
}
// MARK: View
struct OnboardingCongratulationsViewState: BindableState {
var userId: String
}
enum OnboardingCongratulationsViewAction {
case personaliseProfile
case takeMeHome
}
@@ -0,0 +1,50 @@
//
// 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, *)
typealias OnboardingCongratulationsViewModelType = StateStoreViewModel<OnboardingCongratulationsViewState,
Never,
OnboardingCongratulationsViewAction>
@available(iOS 14, *)
class OnboardingCongratulationsViewModel: OnboardingCongratulationsViewModelType, OnboardingCongratulationsViewModelProtocol {
// MARK: - Properties
// MARK: Private
// MARK: Public
var completion: ((OnboardingCongratulationsViewModelResult) -> Void)?
// MARK: - Setup
init(userId: String, initialCount: Int = 0) {
super.init(initialViewState: OnboardingCongratulationsViewState(userId: userId))
}
// MARK: - Public
override func process(viewAction: OnboardingCongratulationsViewAction) {
switch viewAction {
case .personaliseProfile:
completion?(.personaliseProfile)
case .takeMeHome:
completion?(.takeMeHome)
}
}
}
@@ -0,0 +1,24 @@
//
// 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 OnboardingCongratulationsViewModelProtocol {
var completion: ((OnboardingCongratulationsViewModelResult) -> Void)? { get set }
@available(iOS 14, *)
var context: OnboardingCongratulationsViewModelType.Context { get }
}
@@ -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 XCTest
import RiotSwiftUI
@available(iOS 14.0, *)
class OnboardingCongratulationsUITests: MockScreenTest {
override class var screenType: MockScreenState.Type {
return MockOnboardingCongratulationsScreenState.self
}
override class func createTest() -> MockScreenTest {
return OnboardingCongratulationsUITests(selector: #selector(verifyOnboardingCongratulationsScreen))
}
func verifyOnboardingCongratulationsScreen() throws {
guard let screenState = screenState as? MockOnboardingCongratulationsScreenState else { fatalError("no screen") }
switch screenState {
case .congratulations:
// There isn't anything to test here
break
}
}
}
@@ -0,0 +1,24 @@
//
// 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
@testable import RiotSwiftUI
@available(iOS 14.0, *)
class OnboardingCongratulationsViewModelTests: XCTestCase {
// The view modal has minimal set up and no mutation so nothing to test.
}
@@ -0,0 +1,108 @@
//
// 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 OnboardingCongratulationsScreen: View {
// MARK: - Properties
// MARK: Private
@Environment(\.theme) private var theme
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
private var horizontalPadding: CGFloat {
horizontalSizeClass == .regular ? 50 : 16
}
// MARK: Public
@ObservedObject var viewModel: OnboardingCongratulationsViewModel.Context
// MARK: Views
var body: some View {
GeometryReader { geometry in
VStack {
mainContent
.padding(.top, 60)
.padding(.horizontal, horizontalPadding)
Spacer()
buttons
.padding(.horizontal, horizontalPadding)
.padding(.bottom, 24)
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
}
.frame(maxWidth: OnboardingConstants.maxContentWidth,
maxHeight: OnboardingConstants.maxContentHeight)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.background(theme.colors.accent.ignoresSafeArea())
.accentColor(.white)
.navigationBarHidden(true)
.preferredColorScheme(.dark) // make the status bar white
}
/// The main content of the view to be shown in a scroll view.
var mainContent: some View {
VStack(spacing: 62) {
Image(Asset.Images.onboardingCongratulationsIcon.name)
VStack(spacing: 8) {
Text(VectorL10n.onboardingCongratulationsTitle)
.font(theme.fonts.title2B)
.foregroundColor(.white)
Text(VectorL10n.onboardingCongratulationsMessage(viewModel.viewState.userId))
.font(theme.fonts.body)
.foregroundColor(.white)
.multilineTextAlignment(.center)
}
}
}
/// The action buttons shown at the bottom of the view.
var buttons: some View {
VStack(spacing: 12) {
Button { viewModel.send(viewAction: .personaliseProfile) } label: {
Text(VectorL10n.onboardingCongratulationsPersonaliseButton)
.font(theme.fonts.bodySB)
.foregroundColor(theme.colors.accent)
}
.buttonStyle(PrimaryActionButtonStyle(customColor: .white))
Button { viewModel.send(viewAction: .takeMeHome) } label: {
Text(VectorL10n.onboardingCongratulationsHomeButton)
.font(theme.fonts.body)
.padding(.vertical, 12)
}
}
}
}
// MARK: - Previews
@available(iOS 14.0, *)
struct OnboardingCongratulationsScreen_Previews: PreviewProvider {
static let stateRenderer = MockOnboardingCongratulationsScreenState.stateRenderer
static var previews: some View {
stateRenderer.screenGroup()
}
}