mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:44:05 +00:00
Migration Screen (#1224)
- Hide bottom bar when in skeletons. - Use the new design for the Waitlist screen too.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
//
|
||||
// Copyright 2023 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 standardised layout for waiting dialogs shown during the onboarding flow.
|
||||
struct WaitingDialog<Content: View, BottomContent: View>: View {
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
|
||||
/// Not using the default to allow the gradient to fill edge to edge.
|
||||
private let horizontalPadding = 16.0
|
||||
/// The top padding of the content, used to keep the text below the image.
|
||||
private var contentTopPadding: CGFloat {
|
||||
if verticalSizeClass == .compact {
|
||||
// Reduced value for iPhones in landscape.
|
||||
return UIConstants.onboardingBreakerScreenTopPadding
|
||||
} else if horizontalSizeClass == .compact {
|
||||
// The default value for portrait iPhones.
|
||||
return 2 * UIConstants.onboardingBreakerScreenTopPadding
|
||||
} else {
|
||||
// Larger on iPad specifically for 11" in Landscape.
|
||||
return 2.7 * UIConstants.onboardingBreakerScreenTopPadding
|
||||
}
|
||||
}
|
||||
|
||||
/// The main content shown at the top of the layout.
|
||||
@ViewBuilder var content: () -> Content
|
||||
/// The content shown at the bottom of the layout.
|
||||
@ViewBuilder var bottomContent: () -> BottomContent
|
||||
|
||||
var body: some View {
|
||||
FullscreenDialog(topPadding: UIConstants.onboardingBreakerScreenTopPadding, horizontalPadding: 0) {
|
||||
content()
|
||||
.padding(.top, contentTopPadding)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
} bottomContent: {
|
||||
bottomContent()
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
}
|
||||
.background {
|
||||
background
|
||||
.ignoresSafeArea(edges: [.horizontal, .bottom])
|
||||
}
|
||||
.ignoresSafeArea(edges: [.horizontal, .bottom])
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.environment(\.colorScheme, .dark)
|
||||
.toolbar(.visible, for: .navigationBar) // Layout consistency in all states.
|
||||
.toolbarBackground(.hidden, for: .navigationBar)
|
||||
.preferredColorScheme(.light) // Has no effect on Previews, works fine in-app.
|
||||
}
|
||||
|
||||
var background: some View {
|
||||
// Negative spacing to hide a rendering bug that shows the white background.
|
||||
GeometryReader { geometry in
|
||||
VStack(spacing: -5) {
|
||||
Color.white
|
||||
.frame(maxHeight: UIConstants.onboardingBreakerScreenTopPadding)
|
||||
|
||||
Image(asset: Asset.Images.waitingGradient)
|
||||
.resizable()
|
||||
.frame(width: horizontalSizeClass == .compact ? nil : geometry.size.width) // Forced for landscape iPhone and nicer iPad.
|
||||
.scaledToFit()
|
||||
.layoutPriority(1) // Force for all landscape layouts.
|
||||
|
||||
Color.compound.bgCanvasDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WaitingDialog_Previews: PreviewProvider {
|
||||
static let viewModel = WaitlistScreenViewModel(homeserver: .mockMatrixDotOrg)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
WaitlistScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,42 +20,29 @@ struct WaitlistScreen: View {
|
||||
@ObservedObject var context: WaitlistScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
FullscreenDialog(topPadding: UIConstants.iconTopPaddingToNavigationBar) {
|
||||
header
|
||||
WaitingDialog {
|
||||
content
|
||||
} bottomContent: {
|
||||
buttons
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.navigationBarBackButtonHidden()
|
||||
.toolbar { toolbar }
|
||||
.toolbar(.visible, for: .navigationBar) // Layout consistency in all states.
|
||||
.overlay {
|
||||
EffectsView(effect: context.viewState.isWaiting ? .none : .confetti)
|
||||
.ignoresSafeArea()
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// The main content of the view to be shown in a scroll view.
|
||||
var header: some View {
|
||||
VStack(spacing: 8) {
|
||||
AuthenticationIconImage(image: Image(systemName: context.viewState.iconSymbolName))
|
||||
.fontWeight(.semibold)
|
||||
.padding(.bottom, 8)
|
||||
|
||||
Text(context.viewState.title)
|
||||
.font(.compound.headingMDBold)
|
||||
var content: some View {
|
||||
VStack(spacing: 16) {
|
||||
Text(context.viewState.title.tinting(".", color: .element.brand))
|
||||
.font(.compound.headingXLBold)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Text(context.viewState.message)
|
||||
.font(.compound.bodyMD)
|
||||
.font(.compound.bodyLG)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
/// The action buttons shown at the bottom of the view.
|
||||
|
||||
@@ -26,9 +26,17 @@ struct HomeScreen: View {
|
||||
@ObservedObject var context: HomeScreenViewModel.Context
|
||||
|
||||
@State private var scrollViewAdapter = ScrollViewAdapter()
|
||||
@State private var showingBottomToolbar = true
|
||||
@State private var lastScrollDirection: ScrollViewAdapter.ScrollDirection = .up
|
||||
@State private var isSearching = false
|
||||
|
||||
var bottomBarVisibility: Visibility {
|
||||
if lastScrollDirection == .up, context.viewState.roomListMode == .rooms {
|
||||
return .automatic
|
||||
} else {
|
||||
return .hidden
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
if context.viewState.showSessionVerificationBanner {
|
||||
@@ -81,7 +89,7 @@ struct HomeScreen: View {
|
||||
}
|
||||
.onReceive(scrollViewAdapter.scrollDirection) { direction in
|
||||
withAnimation(.elementDefault) {
|
||||
showingBottomToolbar = (direction == .down)
|
||||
lastScrollDirection = direction
|
||||
}
|
||||
}
|
||||
.onChange(of: context.viewState.visibleRooms) { _ in
|
||||
@@ -100,7 +108,7 @@ struct HomeScreen: View {
|
||||
actions: leaveRoomAlertActions,
|
||||
message: leaveRoomAlertMessage)
|
||||
.navigationTitle(L10n.screenRoomlistMainSpaceTitle)
|
||||
.toolbar(showingBottomToolbar ? .automatic : .hidden, for: .bottomBar)
|
||||
.toolbar(bottomBarVisibility, for: .bottomBar)
|
||||
.toolbar { toolbar }
|
||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||
.track(screen: .home)
|
||||
@@ -117,6 +125,8 @@ struct HomeScreen: View {
|
||||
ToolbarItemGroup(placement: .bottomBar) {
|
||||
Spacer()
|
||||
newRoomButton
|
||||
// Fix position animating on loop by getting caught up in the shimmer effect somehow.
|
||||
.animation(.noAnimation, value: context.viewState.roomListMode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// 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 Combine
|
||||
import SwiftUI
|
||||
|
||||
final class MigrationScreenCoordinator: CoordinatorProtocol {
|
||||
private var viewModel: MigrationScreenViewModelProtocol
|
||||
private var cancellables: Set<AnyCancellable> = .init()
|
||||
|
||||
init() {
|
||||
viewModel = MigrationScreenViewModel()
|
||||
}
|
||||
|
||||
func start() { }
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(MigrationScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
enum MigrationScreenViewModelAction { }
|
||||
|
||||
struct MigrationScreenViewState: BindableState { }
|
||||
|
||||
enum MigrationScreenViewAction { }
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// 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 Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias MigrationScreenViewModelType = StateStoreViewModel<MigrationScreenViewState, MigrationScreenViewAction>
|
||||
|
||||
class MigrationScreenViewModel: MigrationScreenViewModelType, MigrationScreenViewModelProtocol {
|
||||
private var actionsSubject: PassthroughSubject<MigrationScreenViewModelAction, Never> = .init()
|
||||
|
||||
var actions: AnyPublisher<MigrationScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(initialViewState: MigrationScreenViewState())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// 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 Combine
|
||||
|
||||
@MainActor
|
||||
protocol MigrationScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<MigrationScreenViewModelAction, Never> { get }
|
||||
var context: MigrationScreenViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
struct MigrationScreen: View {
|
||||
@ObservedObject var context: MigrationScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
WaitingDialog {
|
||||
content
|
||||
} bottomContent: {
|
||||
EmptyView()
|
||||
}
|
||||
.navigationBarBackButtonHidden()
|
||||
}
|
||||
|
||||
var content: some View {
|
||||
VStack(spacing: 16) {
|
||||
ProgressView()
|
||||
.tint(.compound.iconPrimary)
|
||||
.padding(.bottom, 4)
|
||||
|
||||
Text(L10n.screenMigrationTitle.tinting(".", color: .element.brand))
|
||||
.font(.compound.headingXLBold)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
|
||||
Text(L10n.screenMigrationMessage)
|
||||
.font(.compound.bodyLG)
|
||||
.multilineTextAlignment(.center)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct MigrationScreen_Previews: PreviewProvider {
|
||||
static let viewModel = MigrationScreenViewModel()
|
||||
static var previews: some View {
|
||||
MigrationScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user