// // Copyright 2025 Element Creations Ltd. // Copyright 2022-2025 New Vector Ltd. // // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. // Please see LICENSE files in the repository root for full details. // import Compound import SwiftUI struct ServerConfirmationScreen: View { @Bindable var context: ServerConfirmationScreenViewModel.Context private var backgroundColor: Color { switch context.viewState.mode { case .confirmation: .compound.bgCanvasDefault case .picker: .compound.bgSubtleSecondaryLevel0 } } private var headerIcon: KeyPath { switch context.viewState.mode { case .confirmation: \.userProfileSolid case .picker: \.homeSolid } } private var headerIconStyle: BigIcon.Style { switch context.viewState.mode { case .confirmation: .defaultSolid case .picker: .default } } var body: some View { FullscreenDialog(topPadding: UIConstants.iconTopPaddingToNavigationBar) { VStack(spacing: 36) { header mainContent } } bottomContent: { buttons } .background() .backgroundStyle(backgroundColor) .alert(item: $context.alertInfo) .introspect(.window, on: .supportedVersions) { window in context.send(viewAction: .updateWindow(window)) } } /// The main content of the view to be shown in a scroll view. var header: some View { VStack(spacing: 8) { BigIcon(icon: headerIcon, style: headerIconStyle) .padding(.bottom, 8) Text(context.viewState.title) .font(.compound.headingMDBold) .multilineTextAlignment(.center) .foregroundColor(.compound.textPrimary) .fixedSize(horizontal: false, vertical: true) if let message = context.viewState.message { Text(message) .font(.compound.bodyMD) .multilineTextAlignment(.center) .foregroundColor(.compound.textSecondary) } } .padding(.horizontal, 16) } @ViewBuilder var mainContent: some View { if case .picker(let accountProviders) = context.viewState.mode { FakeInlinePicker(items: accountProviders, icon: \.host, selection: $context.pickerSelection) .accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.serverPicker) } } /// The action buttons shown at the bottom of the view. var buttons: some View { VStack(spacing: 16) { Button { context.send(viewAction: .confirm) } label: { Text(L10n.actionContinue) } .buttonStyle(.compound(.primary)) .accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.continue) if case .confirmation = context.viewState.mode { Button { context.send(viewAction: .changeServer) } label: { Text(L10n.screenServerConfirmationChangeServer) .font(.compound.bodyLGSemibold) .padding(14) } .accessibilityIdentifier(A11yIdentifiers.serverConfirmationScreen.changeServer) } } } } /// This is such a hack. I hate it! /// But… We're not in a List/Form, the compound picker doesn't /// support icons and this screen's design might change so 🤷‍♂️. private struct FakeInlinePicker: View { let items: [String] let icon: KeyPath @Binding var selection: String? var body: some View { VStack(spacing: 0) { ForEach(items, id: \.self) { item in ListRow(label: .default(title: item, icon: icon), kind: .selection(isSelected: selection == item) { selection = item }) .overlay(alignment: .bottom) { if item != items.last { Divider() .hidden() .overlay(Color.compound._borderInteractiveSecondaryAlpha) .padding(.leading, 54) } } } } .clipShape(RoundedRectangle(cornerRadius: 10)) } } // MARK: - Previews struct ServerConfirmationScreen_Previews: PreviewProvider, TestablePreview { static let loginViewModel = makeViewModel(mode: .confirmation("matrix.org"), flow: .login) static let registerViewModel = makeViewModel(mode: .confirmation("matrix.org"), flow: .register) static let pickerViewModel = makeViewModel(mode: .picker(["dept1.company.com", "dept2.company.com", "dept3.company.com"]), flow: .login) static var previews: some View { ElementNavigationStack { ServerConfirmationScreen(context: loginViewModel.context) .toolbar(.visible, for: .navigationBar) } .previewDisplayName("Login") ElementNavigationStack { ServerConfirmationScreen(context: registerViewModel.context) .toolbar(.visible, for: .navigationBar) } .previewDisplayName("Register") ElementNavigationStack { ServerConfirmationScreen(context: pickerViewModel.context) .toolbar(.visible, for: .navigationBar) } .previewDisplayName("Picker") } static func makeViewModel(mode: ServerConfirmationScreenMode, flow: AuthenticationFlow) -> ServerConfirmationScreenViewModel { ServerConfirmationScreenViewModel(authenticationService: AuthenticationService.mock, mode: mode, authenticationFlow: flow, appSettings: ServiceLocator.shared.settings, userIndicatorController: UserIndicatorControllerMock()) } }