Merge branch 'develop' into gil/SP1_space_creation

This commit is contained in:
Gil Eluard
2022-03-01 10:15:13 +01:00
164 changed files with 3643 additions and 1650 deletions
@@ -57,7 +57,7 @@ final class AnalyticsPromptCoordinator: Coordinator, Presentable {
promptType = .newUser
}
let viewModel = AnalyticsPromptViewModel(promptType: promptType, strings: strings, termsURL: BuildSettings.analyticsTermsURL)
let viewModel = AnalyticsPromptViewModel(promptType: promptType, strings: strings, termsURL: BuildSettings.analyticsConfiguration.termsURL)
let view = AnalyticsPrompt(viewModel: viewModel.context)
_analyticsPromptViewModel = viewModel
@@ -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 = OnboardingCongratulationsScreen(viewModel: viewModel.context)
onboardingCongratulationsViewModel = viewModel
onboardingCongratulationsHostingController = VectorHostingController(rootView: view)
}
// MARK: - Public
func start() {
MXLog.debug("[OnboardingCongratulationsCoordinator] did start.")
onboardingCongratulationsViewModel.completion = { [weak self] result in
guard let self = self else { return }
MXLog.debug("[OnboardingCongratulationsCoordinator] OnboardingCongratulationsViewModel did complete with result: \(result).")
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,109 @@
//
// 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)
.accessibilityHidden(true)
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()
}
}
@@ -81,11 +81,8 @@ class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelPro
state.poll.answerOptions.updateEach { answerOption in
if answerOption.selected {
answerOption.selected = false
if(state.poll.answerOptions.count > 0) {
answerOption.count = answerOption.count - 1
state.poll.totalAnswerCount -= 1
}
answerOption.count = UInt(max(0, Int(answerOption.count) - 1))
state.poll.totalAnswerCount = UInt(max(0, Int(state.poll.totalAnswerCount) - 1))
}
if answerOption.id == selectedAnswerIdentifier {
@@ -114,8 +111,8 @@ class TimelinePollViewModel: TimelinePollViewModelType, TimelinePollViewModelPro
if answerOption.selected {
answerOption.selected = false
answerOption.count -= 1
state.poll.totalAnswerCount -= 1
answerOption.count = UInt(max(0, Int(answerOption.count) - 1))
state.poll.totalAnswerCount = UInt(max(0, Int(state.poll.totalAnswerCount) - 1))
} else {
answerOption.selected = true
answerOption.count += 1
@@ -52,8 +52,8 @@ final class TemplateSimpleScreenCoordinator: Coordinator, Presentable {
func start() {
MXLog.debug("[TemplateSimpleScreenCoordinator] did start.")
templateSimpleScreenViewModel.completion = { [weak self] result in
MXLog.debug("[TemplateSimpleScreenCoordinator] TemplateSimpleScreenViewModel did complete with result: \(result).")
guard let self = self else { return }
MXLog.debug("[TemplateSimpleScreenCoordinator] TemplateSimpleScreenViewModel did complete with result: \(result).")
self.completion?(result)
}
}
@@ -52,8 +52,8 @@ final class TemplateUserProfileCoordinator: Coordinator, Presentable {
func start() {
MXLog.debug("[TemplateUserProfileCoordinator] did start.")
templateUserProfileViewModel.completion = { [weak self] result in
MXLog.debug("[TemplateUserProfileCoordinator] TemplateUserProfileViewModel did complete with result: \(result).")
guard let self = self else { return }
MXLog.debug("[TemplateUserProfileCoordinator] TemplateUserProfileViewModel did complete with result: \(result).")
switch result {
case .cancel, .done:
self.completion?()
@@ -52,8 +52,8 @@ final class TemplateRoomChatCoordinator: Coordinator, Presentable {
func start() {
MXLog.debug("[TemplateRoomChatCoordinator] did start.")
templateRoomChatViewModel.callback = { [weak self] result in
MXLog.debug("[TemplateRoomChatCoordinator] TemplateRoomChatViewModel did complete with result: \(result).")
guard let self = self else { return }
MXLog.debug("[TemplateRoomChatCoordinator] TemplateRoomChatViewModel did complete with result: \(result).")
switch result {
case .done:
self.callback?()
@@ -53,8 +53,8 @@ final class TemplateRoomListCoordinator: Coordinator, Presentable {
func start() {
MXLog.debug("[TemplateRoomListCoordinator] did start.")
templateRoomListViewModel.callback = { [weak self] result in
MXLog.debug("[TemplateRoomListCoordinator] TemplateRoomListViewModel did complete with result \(result).")
guard let self = self else { return }
MXLog.debug("[TemplateRoomListCoordinator] TemplateRoomListViewModel did complete with result \(result).")
switch result {
case .didSelectRoom(let roomId):
self.callback?(.didSelectRoom(roomId))