Onboarding flow coordinator and FTUE changes (#2578)

Fixes #2595, fixes #2594, fixes #2593, fixes #2592, fixes #2591
This commit is contained in:
Stefan Ceriu
2024-03-21 14:01:23 +02:00
committed by GitHub
parent 9e6b6ba60a
commit a62c96f1c5
176 changed files with 1886 additions and 912 deletions
@@ -0,0 +1,59 @@
//
// 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 Combine
import SwiftUI
struct NotificationPermissionsScreenCoordinatorParameters {
let notificationManager: NotificationManagerProtocol
}
enum NotificationPermissionsScreenCoordinatorAction {
case done
}
final class NotificationPermissionsScreenCoordinator: CoordinatorProtocol {
private var viewModel: NotificationPermissionsScreenViewModelProtocol
private let actionsSubject: PassthroughSubject<NotificationPermissionsScreenCoordinatorAction, Never> = .init()
private var cancellables = Set<AnyCancellable>()
var actions: AnyPublisher<NotificationPermissionsScreenCoordinatorAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(parameters: NotificationPermissionsScreenCoordinatorParameters) {
viewModel = NotificationPermissionsScreenViewModel(notificationManager: parameters.notificationManager)
}
// MARK: - Public
func start() {
viewModel.actionsPublisher
.sink { [weak self] action in
guard let self else { return }
switch action {
case .done:
actionsSubject.send(.done)
}
}
.store(in: &cancellables)
}
func toPresentable() -> AnyView {
AnyView(NotificationPermissionsScreen(context: viewModel.context))
}
}
@@ -0,0 +1,28 @@
//
// 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
enum NotificationPermissionsScreenViewAction {
case enable
case notNow
}
enum NotificationPermissionsScreenViewModelAction {
case done
}
struct NotificationPermissionsScreenViewState: BindableState { }
@@ -0,0 +1,48 @@
//
// 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 Combine
import SwiftUI
typealias NotificationPermissionsScreenViewModelType = StateStoreViewModel<NotificationPermissionsScreenViewState, NotificationPermissionsScreenViewAction>
class NotificationPermissionsScreenViewModel: NotificationPermissionsScreenViewModelType, NotificationPermissionsScreenViewModelProtocol {
private let notificationManager: NotificationManagerProtocol
private var actionsSubject: PassthroughSubject<NotificationPermissionsScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<NotificationPermissionsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(notificationManager: NotificationManagerProtocol) {
self.notificationManager = notificationManager
super.init(initialViewState: .init())
}
// MARK: - Public
override func process(viewAction: NotificationPermissionsScreenViewAction) {
switch viewAction {
case .enable:
notificationManager.requestAuthorization()
actionsSubject.send(.done)
case .notNow:
actionsSubject.send(.done)
}
}
}
@@ -0,0 +1,23 @@
//
// 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 Combine
@MainActor
protocol NotificationPermissionsScreenViewModelProtocol {
var actionsPublisher: AnyPublisher<NotificationPermissionsScreenViewModelAction, Never> { get }
var context: NotificationPermissionsScreenViewModelType.Context { get }
}
@@ -0,0 +1,77 @@
//
// 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
/// A prompt that asks the user whether they would like to enable Analytics or not.
struct NotificationPermissionsScreen: View {
@ObservedObject var context: NotificationPermissionsScreenViewModel.Context
var body: some View {
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, showBackgroundGradient: true) {
mainContent
} bottomContent: {
buttons
}
.background()
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.interactiveDismissDisabled()
}
/// The main content of the screen that is shown inside the scroll view.
private var mainContent: some View {
VStack(spacing: 8) {
HeroImage(icon: \.notificationsSolid)
.padding(.bottom, 8)
Text(L10n.screenNotificationOptinTitle)
.font(.compound.headingMDBold)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textPrimary)
Text(L10n.screenNotificationOptinSubtitle)
.font(.compound.bodyMD)
.multilineTextAlignment(.center)
.foregroundColor(.compound.textSecondary)
Asset.Images.notificationsPromptGraphic.swiftUIImage.resizable().aspectRatio(contentMode: .fit)
}
}
private var buttons: some View {
VStack(spacing: 16) {
Button(L10n.actionOk) { context.send(viewAction: .enable) }
.buttonStyle(.compound(.primary))
Button { context.send(viewAction: .notNow) } label: {
Text(L10n.actionNotNow)
.font(.compound.bodyLGSemibold)
.padding(14)
}
}
}
}
// MARK: - Previews
struct NotificationPermissionsScreen_Previews: PreviewProvider, TestablePreview {
static let viewModel = NotificationPermissionsScreenViewModel(notificationManager: NotificationManagerMock())
static var previews: some View {
NotificationPermissionsScreen(context: viewModel.context)
}
}