mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-25 19:10:49 +02:00
App Layout: Release Experience
- First iteration before final design
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// 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 UIKit
|
||||
|
||||
// MARK: - Coordinator
|
||||
|
||||
// MARK: View model
|
||||
|
||||
enum AllChatsOnboardingViewModelResult {
|
||||
case cancel
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct AllChatsOnboardingPage: Identifiable {
|
||||
let id = UUID().uuidString
|
||||
let image: UIImage
|
||||
let title: String
|
||||
let message: String
|
||||
}
|
||||
|
||||
struct AllChatsOnboardingViewState: BindableState {
|
||||
let pages: [AllChatsOnboardingPage]
|
||||
}
|
||||
|
||||
enum AllChatsOnboardingViewAction {
|
||||
case cancel
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// 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
|
||||
import Combine
|
||||
|
||||
typealias AllChatsOnboardingViewModelType = StateStoreViewModel<AllChatsOnboardingViewState,
|
||||
Never,
|
||||
AllChatsOnboardingViewAction>
|
||||
|
||||
class AllChatsOnboardingViewModel: AllChatsOnboardingViewModelType, AllChatsOnboardingViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: ((AllChatsOnboardingViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
static func makeAllChatsOnboardingViewModel() -> AllChatsOnboardingViewModelProtocol {
|
||||
return AllChatsOnboardingViewModel()
|
||||
}
|
||||
|
||||
private init() {
|
||||
super.init(initialViewState: Self.defaultState())
|
||||
}
|
||||
|
||||
private static func defaultState() -> AllChatsOnboardingViewState {
|
||||
return AllChatsOnboardingViewState(pages: [
|
||||
AllChatsOnboardingPage(image: Asset.Images.allChatsOnboardingDark1.image,
|
||||
title: VectorL10n.allChatsOnboardingPageTitle1,
|
||||
message: VectorL10n.allChatsOnboardingPageMessage1),
|
||||
AllChatsOnboardingPage(image: Asset.Images.allChatsOnboardingDark2.image,
|
||||
title: VectorL10n.allChatsOnboardingPageTitle2,
|
||||
message: VectorL10n.allChatsOnboardingPageMessage2),
|
||||
AllChatsOnboardingPage(image: Asset.Images.allChatsOnboardingDark3.image,
|
||||
title: VectorL10n.allChatsOnboardingPageTitle3,
|
||||
message: VectorL10n.allChatsOnboardingPageMessage3),
|
||||
AllChatsOnboardingPage(image: Asset.Images.allChatsOnboardingDark4.image,
|
||||
title: VectorL10n.allChatsOnboardingPageTitle4,
|
||||
message: VectorL10n.allChatsOnboardingPageMessage4),
|
||||
])
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: AllChatsOnboardingViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
completion?(.cancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 AllChatsOnboardingViewModelProtocol {
|
||||
|
||||
var completion: ((AllChatsOnboardingViewModelResult) -> Void)? { get set }
|
||||
static func makeAllChatsOnboardingViewModel() -> AllChatsOnboardingViewModelProtocol
|
||||
var context: AllChatsOnboardingViewModelType.Context { get }
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// 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
|
||||
import CommonKit
|
||||
|
||||
/// All Chats onboarding screen
|
||||
final class AllChatsOnboardingCoordinator: NSObject, Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let hostingController: UIViewController
|
||||
private var viewModel: AllChatsOnboardingViewModelProtocol
|
||||
|
||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||
private var loadingIndicator: UserIndicator?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: (() -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
override init() {
|
||||
let viewModel = AllChatsOnboardingViewModel.makeAllChatsOnboardingViewModel()
|
||||
let view = AllChatsOnboarding(viewModel: viewModel.context)
|
||||
self.viewModel = viewModel
|
||||
self.hostingController = VectorHostingController(rootView: view)
|
||||
self.indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingController)
|
||||
|
||||
super.init()
|
||||
|
||||
hostingController.presentationController?.delegate = self
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
MXLog.debug("[AllChatsOnboardingCoordinator] did start.")
|
||||
viewModel.completion = { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
MXLog.debug("[AllChatsOnboardingCoordinator] AllChatsOnboardingViewModel did complete with result: \(result).")
|
||||
switch result {
|
||||
case .cancel:
|
||||
self.completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.hostingController
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Show an activity indicator whilst loading.
|
||||
/// - Parameters:
|
||||
/// - label: The label to show on the indicator.
|
||||
/// - isInteractionBlocking: Whether the indicator should block any user interaction.
|
||||
private func startLoading(label: String = VectorL10n.loading, isInteractionBlocking: Bool = true) {
|
||||
loadingIndicator = indicatorPresenter.present(.loading(label: label, isInteractionBlocking: isInteractionBlocking))
|
||||
}
|
||||
|
||||
/// Hide the currently displayed activity indicator.
|
||||
private func stopLoading() {
|
||||
loadingIndicator = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
|
||||
extension AllChatsOnboardingCoordinator: UIAdaptivePresentationControllerDelegate {
|
||||
|
||||
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
|
||||
completion?()
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// Copyright 2022 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
|
||||
|
||||
@objc protocol AllChatsOnboardingCoordinatorBridgePresenterDelegate {
|
||||
func allChatsOnboardingCoordinatorBridgePresenterDidCancel(_ coordinatorBridgePresenter: AllChatsOnboardingCoordinatorBridgePresenter)
|
||||
}
|
||||
|
||||
/// `AllChatsOnboardingCoordinatorBridgePresenter` enables to start `AllChatsOnboardingCoordinator` from a view controller.
|
||||
/// This bridge is used while waiting for global usage of coordinator pattern.
|
||||
/// It breaks the Coordinator abstraction and it has been introduced for Objective-C compatibility (mainly for integration in legacy view controllers).
|
||||
/// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator.
|
||||
@objcMembers
|
||||
final class AllChatsOnboardingCoordinatorBridgePresenter: NSObject {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var coordinator: AllChatsOnboardingCoordinator?
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: (() -> Void)?
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func present(from viewController: UIViewController, animated: Bool) {
|
||||
let coordinator = AllChatsOnboardingCoordinator()
|
||||
coordinator.completion = { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.completion?()
|
||||
}
|
||||
let presentable = coordinator.toPresentable()
|
||||
viewController.present(presentable, animated: animated, completion: nil)
|
||||
coordinator.start()
|
||||
|
||||
self.coordinator = coordinator
|
||||
}
|
||||
|
||||
func dismiss(animated: Bool, completion: (() -> Void)?) {
|
||||
guard let coordinator = self.coordinator else {
|
||||
return
|
||||
}
|
||||
coordinator.toPresentable().dismiss(animated: animated) {
|
||||
self.coordinator = nil
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// 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 AllChatsOnboarding: View {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme: ThemeSwiftUI
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@ObservedObject var viewModel: AllChatsOnboardingViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(VectorL10n.allChatsOnboardingTitle)
|
||||
.font(theme.fonts.title3SB)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
.padding()
|
||||
TabView {
|
||||
ForEach(viewModel.viewState.pages) { page in
|
||||
pageView(image: page.image,
|
||||
title: page.title,
|
||||
message: page.message)
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle())
|
||||
|
||||
Button { viewModel.send(viewAction: .cancel) } label: {
|
||||
Text(VectorL10n.allChatsOnboardingTryIt)
|
||||
}
|
||||
.buttonStyle(PrimaryActionButtonStyle())
|
||||
.padding()
|
||||
}
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.frame(maxHeight: .infinity)
|
||||
.onAppear {
|
||||
self.setupAppearance()
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func pageView(image: UIImage, title: String, message: String) -> some View {
|
||||
VStack {
|
||||
Spacer()
|
||||
Image(uiImage: image)
|
||||
Spacer()
|
||||
Text(title)
|
||||
.font(theme.fonts.title2B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
.padding(.bottom, 16)
|
||||
Text(message)
|
||||
.multilineTextAlignment(.center)
|
||||
.font(theme.fonts.callout)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
private func setupAppearance() {
|
||||
let tintColor: UIColor = theme.isDark ? .white : .black
|
||||
UIPageControl.appearance().currentPageIndicatorTintColor = tintColor
|
||||
UIPageControl.appearance().pageIndicatorTintColor = tintColor.withAlphaComponent(0.2)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct AllChatsOnboarding_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AllChatsOnboarding(viewModel: AllChatsOnboardingViewModel.makeAllChatsOnboardingViewModel().context).theme(.light).preferredColorScheme(.light)
|
||||
AllChatsOnboarding(viewModel: AllChatsOnboardingViewModel.makeAllChatsOnboardingViewModel().context).theme(.dark).preferredColorScheme(.dark)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user