mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-04 23:17:43 +02:00
Polish for Onboarding Carousel
Add dark mode assets and colours. Improve layout for 12" iPad and 4" iPhone. Move isLockedToPortraitOnPhone to RiotNavigationController.
This commit is contained in:
-1
@@ -45,7 +45,6 @@ final class OnboardingSplashScreenCoordinator: Coordinator, Presentable {
|
||||
let view = OnboardingSplashScreen(viewModel: viewModel.context)
|
||||
onboardingSplashScreenViewModel = viewModel
|
||||
let hostingController = VectorHostingController(rootView: view)
|
||||
hostingController.isLockedToPortraitOnPhone = true
|
||||
onboardingSplashScreenHostingController = hostingController
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ struct OnboardingSplashScreenPageContent {
|
||||
let title: String
|
||||
let message: String
|
||||
let image: ImageAsset
|
||||
let darkImage: ImageAsset
|
||||
let gradient: Gradient
|
||||
}
|
||||
|
||||
@@ -45,7 +46,6 @@ struct OnboardingSplashScreenViewState: BindableState {
|
||||
var bindings: OnboardingSplashScreenBindings
|
||||
|
||||
init() {
|
||||
#warning("Check why LocaleProvider.locale always returns en")
|
||||
let locale = Locale.current
|
||||
let page4Title = locale.identifier.hasPrefix("en") ? "Cut the slack from teams." : VectorL10n.onboardingSplashPage4TitleNoPun
|
||||
|
||||
@@ -53,30 +53,34 @@ struct OnboardingSplashScreenViewState: BindableState {
|
||||
OnboardingSplashScreenPageContent(title: VectorL10n.onboardingSplashPage1Title,
|
||||
message: VectorL10n.onboardingSplashPage1Message,
|
||||
image: Asset.Images.onboardingSplashScreenPage1,
|
||||
darkImage: Asset.Images.onboardingSplashScreenPage1Dark,
|
||||
gradient: Gradient(colors: [
|
||||
Color(red: 0.73, green: 0.91, blue: 0.81),
|
||||
Color(red: 0.45, green: 0.78, blue: 0.85)
|
||||
Color(red: 0.95, green: 0.98, blue: 0.96),
|
||||
Color(red: 0.89, green: 0.96, blue: 0.97)
|
||||
])),
|
||||
OnboardingSplashScreenPageContent(title: VectorL10n.onboardingSplashPage2Title,
|
||||
message: VectorL10n.onboardingSplashPage2Message,
|
||||
image: Asset.Images.onboardingSplashScreenPage2,
|
||||
darkImage: Asset.Images.onboardingSplashScreenPage2Dark,
|
||||
gradient: Gradient(colors: [
|
||||
Color(red: 0.45, green: 0.78, blue: 0.85),
|
||||
Color(red: 0.72, green: 0.45, blue: 0.85)
|
||||
Color(red: 0.89, green: 0.96, blue: 0.97),
|
||||
Color(red: 0.95, green: 0.89, blue: 0.97)
|
||||
])),
|
||||
OnboardingSplashScreenPageContent(title: VectorL10n.onboardingSplashPage3Title,
|
||||
message: VectorL10n.onboardingSplashPage3Message,
|
||||
image: Asset.Images.onboardingSplashScreenPage3,
|
||||
darkImage: Asset.Images.onboardingSplashScreenPage3Dark,
|
||||
gradient: Gradient(colors: [
|
||||
Color(red: 0.72, green: 0.45, blue: 0.85),
|
||||
Color(red: 0.05, green: 0.74, blue: 0.55)
|
||||
Color(red: 0.95, green: 0.89, blue: 0.97),
|
||||
Color(red: 0.81, green: 0.95, blue: 0.91)
|
||||
])),
|
||||
OnboardingSplashScreenPageContent(title: page4Title,
|
||||
message: VectorL10n.onboardingSplashPage4Message,
|
||||
image: Asset.Images.onboardingSplashScreenPage4,
|
||||
darkImage: Asset.Images.onboardingSplashScreenPage4Dark,
|
||||
gradient: Gradient(colors: [
|
||||
Color(red: 0.05, green: 0.74, blue: 0.55),
|
||||
Color(red: 0.73, green: 0.91, blue: 0.81)
|
||||
Color(red: 0.81, green: 0.95, blue: 0.91),
|
||||
Color(red: 0.95, green: 0.98, blue: 0.96)
|
||||
])),
|
||||
]
|
||||
self.bindings = OnboardingSplashScreenBindings()
|
||||
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
//
|
||||
// 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 OnboardingSplashScreenUITests: MockScreenTest {
|
||||
|
||||
override class var screenType: MockScreenState.Type {
|
||||
return MockOnboardingSplashScreenScreenState.self
|
||||
}
|
||||
|
||||
override class func createTest() -> MockScreenTest {
|
||||
return OnboardingSplashScreenUITests(selector: #selector(verifyOnboardingSplashScreenScreen))
|
||||
}
|
||||
|
||||
func verifyOnboardingSplashScreenScreen() throws {
|
||||
guard let screenState = screenState as? MockOnboardingSplashScreenScreenState else { fatalError("no screen") }
|
||||
switch screenState {
|
||||
case .presence(let presence):
|
||||
verifyOnboardingSplashScreenPresence(presence: presence)
|
||||
case .longDisplayName(let name):
|
||||
verifyOnboardingSplashScreenLongName(name: name)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyOnboardingSplashScreenPresence(presence: OnboardingSplashScreenPresence) {
|
||||
let presenceText = app.staticTexts["presenceText"]
|
||||
XCTAssert(presenceText.exists)
|
||||
XCTAssertEqual(presenceText.label, presence.title)
|
||||
}
|
||||
|
||||
func verifyOnboardingSplashScreenLongName(name: String) {
|
||||
let displayNameText = app.staticTexts["displayNameText"]
|
||||
XCTAssert(displayNameText.exists)
|
||||
XCTAssertEqual(displayNameText.label, name)
|
||||
}
|
||||
|
||||
}
|
||||
+1
-33
@@ -21,37 +21,5 @@ import Combine
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
class OnboardingSplashScreenViewModelTests: XCTestCase {
|
||||
private enum Constants {
|
||||
static let presenceInitialValue: OnboardingSplashScreenPresence = .offline
|
||||
static let displayName = "Alice"
|
||||
}
|
||||
var service: MockOnboardingSplashScreenService!
|
||||
var viewModel: OnboardingSplashScreenViewModelProtocol!
|
||||
var context: OnboardingSplashScreenViewModelType.Context!
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
override func setUpWithError() throws {
|
||||
service = MockOnboardingSplashScreenService(displayName: Constants.displayName, presence: Constants.presenceInitialValue)
|
||||
viewModel = OnboardingSplashScreenViewModel.makeOnboardingSplashScreenViewModel(onboardingSplashScreenService: service)
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
XCTAssertEqual(context.viewState.displayName, Constants.displayName)
|
||||
XCTAssertEqual(context.viewState.presence, Constants.presenceInitialValue)
|
||||
}
|
||||
|
||||
func testFirstPresenceReceived() throws {
|
||||
let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(1).first()
|
||||
XCTAssertEqual(try xcAwait(presencePublisher), [Constants.presenceInitialValue])
|
||||
}
|
||||
|
||||
func testPresenceUpdatesReceived() throws {
|
||||
let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(3).first()
|
||||
let awaitDeferred = xcAwaitDeferred(presencePublisher)
|
||||
let newPresenceValue1: OnboardingSplashScreenPresence = .online
|
||||
let newPresenceValue2: OnboardingSplashScreenPresence = .idle
|
||||
service.simulateUpdate(presence: newPresenceValue1)
|
||||
service.simulateUpdate(presence: newPresenceValue2)
|
||||
XCTAssertEqual(try awaitDeferred(), [Constants.presenceInitialValue, newPresenceValue1, newPresenceValue2])
|
||||
}
|
||||
// TODO: Check for any useful tests when finished
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ struct OnboardingSplashScreen: View {
|
||||
@ObservedObject var viewModel: OnboardingSplashScreenViewModel.Context
|
||||
|
||||
var buttons: some View {
|
||||
VStack {
|
||||
VStack(spacing: 12) {
|
||||
Button { viewModel.send(viewAction: .register) } label: {
|
||||
Text(VectorL10n.onboardingSplashRegisterButtonTitle)
|
||||
}
|
||||
@@ -52,19 +52,27 @@ struct OnboardingSplashScreen: View {
|
||||
}
|
||||
|
||||
var overlay: some View {
|
||||
VStack {
|
||||
OnboardingSplashScreenPageIndicator(pageCount: pageCount,
|
||||
pageIndex: viewModel.pageIndex)
|
||||
.padding(.vertical, 20)
|
||||
VStack(spacing: 50) {
|
||||
Color.clear
|
||||
Color.clear
|
||||
|
||||
buttons
|
||||
.padding(.horizontal, 16)
|
||||
VStack {
|
||||
OnboardingSplashScreenPageIndicator(pageCount: pageCount,
|
||||
pageIndex: viewModel.pageIndex)
|
||||
Spacer()
|
||||
|
||||
buttons
|
||||
.padding(.horizontal, 16)
|
||||
.frame(maxWidth: 600)
|
||||
Spacer()
|
||||
}
|
||||
.background(ViewFrameReader(frame: $overlayFrame))
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack(alignment: .bottomLeading) {
|
||||
ZStack(alignment: .leading) {
|
||||
HStack(spacing: 0) {
|
||||
OnboardingSplashScreenPage(content: viewModel.viewState.content[pageCount - 1],
|
||||
overlayHeight: overlayFrame.height + geometry.safeAreaInsets.bottom)
|
||||
@@ -103,11 +111,9 @@ struct OnboardingSplashScreen: View {
|
||||
|
||||
overlay
|
||||
.frame(width: geometry.size.width)
|
||||
.background(ViewFrameReader(frame: $overlayFrame))
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
|
||||
}
|
||||
}
|
||||
.background(theme.colors.background.ignoresSafeArea()) // whilst gradients are transparent
|
||||
.background(theme.colors.background.ignoresSafeArea())
|
||||
.accentColor(theme.colors.accent)
|
||||
.navigationBarHidden(true)
|
||||
.onAppear { startTimer() }
|
||||
|
||||
@@ -28,44 +28,48 @@ struct OnboardingSplashScreenPage: View {
|
||||
let content: OnboardingSplashScreenPageContent
|
||||
let overlayHeight: CGFloat
|
||||
|
||||
var isDarkModeEnabled: Bool {
|
||||
theme.identifier != .light
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
@ViewBuilder
|
||||
var backgroundGradient: some View {
|
||||
LinearGradient(gradient: content.gradient, startPoint: .leading, endPoint: .trailing)
|
||||
.flipsForRightToLeftLayoutDirection(true)
|
||||
.opacity(0.2)
|
||||
if !isDarkModeEnabled {
|
||||
LinearGradient(gradient: content.gradient, startPoint: .leading, endPoint: .trailing)
|
||||
.flipsForRightToLeftLayoutDirection(true)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Color.clear
|
||||
.overlay(
|
||||
Image(content.image.name)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
)
|
||||
|
||||
Color.clear
|
||||
.overlay(
|
||||
VStack(spacing: 8) {
|
||||
Spacer()
|
||||
OnboardingSplashScreenTitleText(content.title)
|
||||
.font(theme.fonts.title2B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
Text(content.message)
|
||||
.font(theme.fonts.body)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.multilineTextAlignment(.center)
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
)
|
||||
|
||||
Spacer()
|
||||
.frame(height: overlayHeight)
|
||||
VStack {
|
||||
Image(isDarkModeEnabled ? content.darkImage.name : content.image.name)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: 300)
|
||||
.padding(20)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
OnboardingSplashScreenTitleText(content.title)
|
||||
.font(theme.fonts.title2B)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
Text(content.message)
|
||||
.font(theme.fonts.body)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding(.bottom)
|
||||
|
||||
Spacer()
|
||||
|
||||
Spacer()
|
||||
.frame(maxHeight: overlayHeight)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.frame(maxWidth: 600, maxHeight: 750)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
.padding(.horizontal, 16)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(backgroundGradient.ignoresSafeArea())
|
||||
}
|
||||
@@ -76,7 +80,7 @@ struct OnboardingSplashScreenPage_Previews: PreviewProvider {
|
||||
static let content = OnboardingSplashScreenViewState().content
|
||||
static var previews: some View {
|
||||
ForEach(0..<content.count, id:\.self) { index in
|
||||
OnboardingSplashScreenPage(content: content[index], overlayHeight: 55)
|
||||
OnboardingSplashScreenPage(content: content[index], overlayHeight: 200)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user