mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-26 19:34:25 +02:00
Merge branch 'develop' into gil/SP1_space_creation
This commit is contained in:
@@ -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,
|
||||
|
||||
+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 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
|
||||
}
|
||||
}
|
||||
+46
@@ -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
|
||||
}
|
||||
+50
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -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 }
|
||||
}
|
||||
+40
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+24
@@ -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.
|
||||
}
|
||||
+109
@@ -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
|
||||
|
||||
+1
-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)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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?()
|
||||
|
||||
+1
-1
@@ -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?()
|
||||
|
||||
+1
-1
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user