mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Add OnboardingCelebrationScreen and EffectsSceneView.
This commit is contained in:
parent
d10c387460
commit
78435972e3
18 changed files with 549 additions and 7 deletions
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_celebration_icon.imageset/Contents.json
vendored
Normal file
15
Riot/Assets/Images.xcassets/Onboarding/onboarding_celebration_icon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "onboarding_celebration_icon.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 70 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M35,70C54.33,70 70,54.33 70,35C70,15.67 54.33,0 35,0C15.67,0 0,15.67 0,35C0,54.33 15.67,70 35,70ZM34.863,42.213L37.589,50.392C37.725,50.801 38.107,51.076 38.538,51.076C38.968,51.076 39.35,50.801 39.487,50.392L42.213,42.213C42.213,42.213 50.392,39.487 50.392,39.487C50.8,39.351 51.076,38.969 51.076,38.538C51.076,38.108 50.8,37.726 50.392,37.589L42.213,34.863C42.213,34.863 39.487,26.684 39.487,26.684C39.35,26.276 38.968,26 38.538,26C38.107,26 37.725,26.276 37.589,26.684L34.863,34.863C34.863,34.863 26.684,37.589 26.684,37.589C26.275,37.726 26,38.108 26,38.538C26,38.969 26.275,39.351 26.684,39.487L34.863,42.213ZM19.882,23L18.106,26.553C17.913,26.938 17.989,27.403 18.293,27.707C18.597,28.011 19.062,28.087 19.447,27.894L23,26.118C23,26.118 26.553,27.894 26.553,27.894C26.938,28.087 27.403,28.011 27.707,27.707C28.011,27.403 28.087,26.938 27.894,26.553L26.118,23C26.118,23 27.894,19.447 27.894,19.447C28.087,19.062 28.011,18.597 27.707,18.293C27.403,17.989 26.938,17.913 26.553,18.106L23,19.882C23,19.882 19.447,18.106 19.447,18.106C19.062,17.913 18.597,17.989 18.293,18.293C17.989,18.597 17.913,19.062 18.106,19.447L19.882,23Z" style="fill:rgb(13,189,139);"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -35,4 +35,8 @@
|
|||
"onboarding_avatar_message" = "You can change this anytime.";
|
||||
"onboarding_avatar_accessibility_label" = "Profile picture";
|
||||
|
||||
"onboarding_celebration_title" = "You’re all set!";
|
||||
"onboarding_celebration_message" = "Your preferences have been saved.";
|
||||
"onboarding_celebration_button" = "Let's go";
|
||||
|
||||
"image_picker_action_files" = "Choose from files";
|
||||
|
|
|
@ -126,6 +126,7 @@ internal class Asset: NSObject {
|
|||
internal static let onboardingSplashScreenPage4Dark = ImageAsset(name: "OnboardingSplashScreenPage4Dark")
|
||||
internal static let onboardingAvatarCamera = ImageAsset(name: "onboarding_avatar_camera")
|
||||
internal static let onboardingAvatarEdit = ImageAsset(name: "onboarding_avatar_edit")
|
||||
internal static let onboardingCelebrationIcon = ImageAsset(name: "onboarding_celebration_icon")
|
||||
internal static let onboardingCongratulationsIcon = ImageAsset(name: "onboarding_congratulations_icon")
|
||||
internal static let onboardingUseCaseCommunity = ImageAsset(name: "onboarding_use_case_community")
|
||||
internal static let onboardingUseCaseCommunityDark = ImageAsset(name: "onboarding_use_case_community_dark")
|
||||
|
|
|
@ -26,6 +26,18 @@ public extension VectorL10n {
|
|||
static var onboardingAvatarTitle: String {
|
||||
return VectorL10n.tr("Untranslated", "onboarding_avatar_title")
|
||||
}
|
||||
/// Let's go
|
||||
static var onboardingCelebrationButton: String {
|
||||
return VectorL10n.tr("Untranslated", "onboarding_celebration_button")
|
||||
}
|
||||
/// Your preferences have been saved.
|
||||
static var onboardingCelebrationMessage: String {
|
||||
return VectorL10n.tr("Untranslated", "onboarding_celebration_message")
|
||||
}
|
||||
/// You’re all set!
|
||||
static var onboardingCelebrationTitle: String {
|
||||
return VectorL10n.tr("Untranslated", "onboarding_celebration_title")
|
||||
}
|
||||
/// Take me home
|
||||
static var onboardingCongratulationsHomeButton: String {
|
||||
return VectorL10n.tr("Untranslated", "onboarding_congratulations_home_button")
|
||||
|
|
|
@ -391,14 +391,9 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
private func displayNameCoordinator(_ coordinator: OnboardingDisplayNameCoordinator, didCompleteWith userSession: UserSession) {
|
||||
if shouldShowAvatarScreen {
|
||||
showAvatarScreen(for: userSession)
|
||||
return
|
||||
} else if Analytics.shared.shouldShowAnalyticsPrompt {
|
||||
showAnalyticsPrompt(for: userSession.matrixSession)
|
||||
return
|
||||
} else {
|
||||
showCelebrationScreen(for: userSession)
|
||||
}
|
||||
|
||||
onboardingFinished = true
|
||||
completeIfReady()
|
||||
}
|
||||
|
||||
/// Show the avatar personalization screen for new users using the supplied user session.
|
||||
|
@ -431,6 +426,31 @@ final class OnboardingCoordinator: NSObject, OnboardingCoordinatorProtocol {
|
|||
/// Displays the next view in the flow after the avatar screen.
|
||||
@available(iOS 14.0, *)
|
||||
private func avatarCoordinator(_ coordinator: OnboardingAvatarCoordinator, didCompleteWith userSession: UserSession) {
|
||||
showCelebrationScreen(for: userSession)
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
private func showCelebrationScreen(for userSession: UserSession) {
|
||||
MXLog.debug("[OnboardingCoordinator] showCelebrationScreen")
|
||||
|
||||
let parameters = OnboardingCelebrationCoordinatorParameters(userSession: userSession)
|
||||
let coordinator = OnboardingCelebrationCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.completion = { [weak self, weak coordinator] userSession in
|
||||
guard let self = self, let coordinator = coordinator else { return }
|
||||
self.celebrationCoordinator(coordinator, didCompleteWith: userSession)
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
coordinator.start()
|
||||
|
||||
navigationRouter.setRootModule(coordinator, hideNavigationBar: true, animated: true) { [weak self] in
|
||||
self?.remove(childCoordinator: coordinator)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
private func celebrationCoordinator(_ coordinator: OnboardingCelebrationCoordinator, didCompleteWith userSession: UserSession) {
|
||||
if Analytics.shared.shouldShowAnalyticsPrompt {
|
||||
showAnalyticsPrompt(for: userSession.matrixSession)
|
||||
return
|
||||
|
|
|
@ -20,6 +20,7 @@ import Foundation
|
|||
@available(iOS 14.0, *)
|
||||
enum MockAppScreens {
|
||||
static let appScreens: [MockScreenState.Type] = [
|
||||
MockOnboardingCelebrationScreenState.self,
|
||||
MockOnboardingAvatarScreenState.self,
|
||||
MockOnboardingDisplayNameScreenState.self,
|
||||
MockOnboardingCongratulationsScreenState.self,
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// 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 OnboardingCelebrationCoordinatorParameters {
|
||||
let userSession: UserSession
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
final class OnboardingCelebrationCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let parameters: OnboardingCelebrationCoordinatorParameters
|
||||
private let onboardingCelebrationHostingController: VectorHostingController
|
||||
private var onboardingCelebrationViewModel: OnboardingCelebrationViewModelProtocol
|
||||
|
||||
// MARK: Public
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var completion: ((UserSession) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: OnboardingCelebrationCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
let viewModel = OnboardingCelebrationViewModel()
|
||||
let view = OnboardingCelebrationScreen(viewModel: viewModel.context)
|
||||
onboardingCelebrationViewModel = viewModel
|
||||
onboardingCelebrationHostingController = VectorHostingController(rootView: view)
|
||||
onboardingCelebrationHostingController.enableNavigationBarScrollEdgeAppearance = true
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
func start() {
|
||||
MXLog.debug("[OnboardingCelebrationCoordinator] did start.")
|
||||
onboardingCelebrationViewModel.completion = { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
MXLog.debug("[OnboardingCelebrationCoordinator] OnboardingCelebrationViewModel did complete with result: \(result).")
|
||||
self.completion?(self.parameters.userSession)
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> UIViewController {
|
||||
return self.onboardingCelebrationHostingController
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// 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 SceneKit
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class EffectsScene: SCNScene {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
private enum Constants {
|
||||
static let confettiSceneName = "ConfettiScene.scn"
|
||||
static let particlesNodeName = "particles"
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
static func confetti(with theme: ThemeSwiftUI) -> EffectsScene? {
|
||||
guard let scene = EffectsScene(named: Constants.confettiSceneName) else { return nil }
|
||||
|
||||
let colors: [[Float]] = theme.colors.namesAndAvatars.compactMap { $0.floatComponents }
|
||||
|
||||
if let particles = scene.rootNode.childNode(withName: Constants.particlesNodeName, recursively: false)?.particleSystems?.first {
|
||||
// The particles need a non-zero color variation for the handler to affect the color
|
||||
particles.particleColorVariation = SCNVector4(x: 0, y: 0, z: 0, w: 0.1)
|
||||
|
||||
// Add a handler to customize the color of the particles.
|
||||
particles.handle(.birth, forProperties: [.color]) { data, dataStride, indices, count in
|
||||
for index in 0..<count {
|
||||
// Pick a random color to apply to the particle.
|
||||
guard let color = colors.randomElement() else { continue }
|
||||
|
||||
// Get the particle's color pointer.
|
||||
let colorPointer = data[0] + dataStride[0] * index
|
||||
let rgbaPointer = colorPointer.bindMemory(to: Float.self, capacity: dataStride[0])
|
||||
|
||||
// Update the color for the particle.
|
||||
rgbaPointer[0] = color[0]
|
||||
rgbaPointer[1] = color[1]
|
||||
rgbaPointer[2] = color[2]
|
||||
rgbaPointer[3] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scene
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
fileprivate extension Color {
|
||||
/// The color's components as an array of floats in the extended linear sRGB colorspace.
|
||||
///
|
||||
/// SceneKit works in a colorspace with a linear gamma, which is why this conversion is necessary.
|
||||
var floatComponents: [Float]? {
|
||||
guard
|
||||
let colorSpace = CGColorSpace(name: CGColorSpace.extendedLinearSRGB),
|
||||
let linearColor = cgColor?.converted(to: colorSpace, intent: .defaultIntent, options: nil),
|
||||
let components = linearColor.components
|
||||
else { return nil }
|
||||
|
||||
return components.map { Float($0) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// 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 SwiftUI
|
||||
import SceneKit
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
/// A SwiftUI wrapper around `SCNView`, that unlike `SceneView` allows the
|
||||
/// scene to have a transparent background and be rendered on top of other views.
|
||||
struct EffectsSceneView: UIViewRepresentable {
|
||||
let scene: SCNScene?
|
||||
|
||||
func makeUIView(context: Context) -> SCNView {
|
||||
SCNView(frame: .zero)
|
||||
}
|
||||
|
||||
func updateUIView(_ sceneView: SCNView, context: Context) {
|
||||
sceneView.scene = scene
|
||||
sceneView.backgroundColor = .clear
|
||||
}
|
||||
}
|
|
@ -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 MockOnboardingCelebrationScreenState: MockScreenState, CaseIterable {
|
||||
// A case for each state you want to represent
|
||||
// with specific, minimal associated data that will allow you
|
||||
// mock that screen.
|
||||
case confetti
|
||||
|
||||
/// The associated screen
|
||||
var screenType: Any.Type {
|
||||
OnboardingCelebrationScreen.self
|
||||
}
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
var screenView: ([Any], AnyView) {
|
||||
let viewModel = OnboardingCelebrationViewModel()
|
||||
|
||||
// can simulate service and viewModel actions here if needs be.
|
||||
|
||||
return (
|
||||
[self, viewModel],
|
||||
AnyView(OnboardingCelebrationScreen(viewModel: viewModel.context)
|
||||
.addDependency(MockAvatarService.example))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// 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: View model
|
||||
|
||||
enum OnboardingCelebrationViewModelResult {
|
||||
case complete
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct OnboardingCelebrationViewState: BindableState { }
|
||||
|
||||
enum OnboardingCelebrationViewAction {
|
||||
case complete
|
||||
}
|
|
@ -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 SwiftUI
|
||||
|
||||
@available(iOS 14, *)
|
||||
typealias OnboardingCelebrationViewModelType = StateStoreViewModel<OnboardingCelebrationViewState,
|
||||
Never,
|
||||
OnboardingCelebrationViewAction>
|
||||
@available(iOS 14, *)
|
||||
class OnboardingCelebrationViewModel: OnboardingCelebrationViewModelType, OnboardingCelebrationViewModelProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var completion: ((OnboardingCelebrationViewModelResult) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init() {
|
||||
super.init(initialViewState: OnboardingCelebrationViewState())
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: OnboardingCelebrationViewAction) {
|
||||
switch viewAction {
|
||||
case .complete:
|
||||
completion?(.complete)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 OnboardingCelebrationViewModelProtocol {
|
||||
|
||||
var completion: ((OnboardingCelebrationViewModelResult) -> Void)? { get set }
|
||||
@available(iOS 14, *)
|
||||
var context: OnboardingCelebrationViewModelType.Context { get }
|
||||
}
|
|
@ -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 XCTest
|
||||
import RiotSwiftUI
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingCelebrationUITests: MockScreenTest {
|
||||
// Nothing to test as the view is completely static
|
||||
}
|
|
@ -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 OnboardingCelebrationViewModelTests: XCTestCase {
|
||||
// Nothing to test as there is no mutable state
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
//
|
||||
// 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 SceneKit
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
struct OnboardingCelebrationScreen: 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: OnboardingCelebrationViewModel.Context
|
||||
|
||||
// MARK: Views
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
ScrollView(showsIndicators: false) {
|
||||
mainContent
|
||||
.padding(.top, 106)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
.frame(maxWidth: OnboardingConstants.maxContentWidth)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
buttons
|
||||
.frame(maxWidth: OnboardingConstants.maxContentWidth)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
.padding(.bottom, 24)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.overlay(effects.ignoresSafeArea())
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.accentColor(theme.colors.accent)
|
||||
}
|
||||
|
||||
/// The main content of the view to be shown in a scroll view.
|
||||
var mainContent: some View {
|
||||
VStack(spacing: 8) {
|
||||
Image(Asset.Images.onboardingCelebrationIcon.name)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 90)
|
||||
.foregroundColor(theme.colors.accent)
|
||||
.background(Circle().foregroundColor(.white).padding(2))
|
||||
.padding(.bottom, 42)
|
||||
|
||||
Text(VectorL10n.onboardingCelebrationTitle)
|
||||
.font(theme.fonts.title2B)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
|
||||
Text(VectorL10n.onboardingCelebrationMessage)
|
||||
.font(theme.fonts.subheadline)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
}
|
||||
}
|
||||
|
||||
/// The action buttons shown at the bottom of the view.
|
||||
var buttons: some View {
|
||||
VStack {
|
||||
Button { viewModel.send(viewAction: .complete) } label: {
|
||||
Text(VectorL10n.onboardingCelebrationButton)
|
||||
.font(theme.fonts.body)
|
||||
}
|
||||
.buttonStyle(PrimaryActionButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
var effects: some View {
|
||||
EffectsSceneView(scene: EffectsScene.confetti(with: theme))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
struct OnboardingCelebration_Previews: PreviewProvider {
|
||||
static let stateRenderer = MockOnboardingCelebrationScreenState.stateRenderer
|
||||
static var previews: some View {
|
||||
stateRenderer.screenGroup()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue