* Read and import the secrets from ClassicAppAccounts. * Record snapshots. * Add some documentation, tidy up tests and fix the dismissal of the backup instructions. * Workaround flakey tests (the fulfilments weren't always firing). * Allow a custom Classic App deep link URL to be configured.
158 lines
5.8 KiB
Swift
158 lines
5.8 KiB
Swift
//
|
||
// Copyright 2025 Element Creations 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 AuthenticationClassicAppAccountView: View {
|
||
@Bindable var context: AuthenticationStartScreenViewModel.Context
|
||
|
||
let classicAppAccount: ClassicAppAccount
|
||
|
||
var isLoadingAccount: Bool {
|
||
classicAppAccount.state.isServerSupported == nil || classicAppAccount.state.availableSecrets == nil
|
||
}
|
||
|
||
var body: some View {
|
||
FullscreenDialog(topPadding: 25, background: .gradient) {
|
||
VStack(spacing: 38) {
|
||
header
|
||
.padding(.bottom, 20)
|
||
|
||
profile
|
||
|
||
buttons
|
||
}
|
||
} bottomContent: {
|
||
// Buttons are intentionally shown inline on this screen.
|
||
}
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.alert(item: $context.alertInfo)
|
||
.sheet(isPresented: $context.showClassicAppBackupInstructions) {
|
||
AuthenticationClassicAppBackupInstructionsView(classicAppAccount: classicAppAccount) {
|
||
context.send(viewAction: .openClassicApp)
|
||
}
|
||
}
|
||
.introspect(.window, on: .supportedVersions) { window in
|
||
context.send(viewAction: .updateWindow(window))
|
||
}
|
||
}
|
||
|
||
var header: some View {
|
||
VStack(spacing: 8) {
|
||
AuthenticationStartLogo(size: 54, hideBrandChrome: false, isOnGradient: false)
|
||
|
||
Text(L10n.screenOnboardingWelcomeTitle)
|
||
.font(.compound.headingMDBold)
|
||
.foregroundStyle(.compound.textPrimary)
|
||
.multilineTextAlignment(.center)
|
||
}
|
||
}
|
||
|
||
var profile: some View {
|
||
VStack(spacing: 16) {
|
||
LoadableAvatarImage(url: classicAppAccount.avatarURL,
|
||
name: classicAppAccount.displayName,
|
||
contentID: classicAppAccount.userID,
|
||
avatarSize: .user(on: .classicAppAccount),
|
||
mediaProvider: context.mediaProvider)
|
||
|
||
VStack(spacing: 0) {
|
||
Text(L10n.screenOnboardingWelcomeBack)
|
||
.font(.compound.bodyMD)
|
||
.foregroundStyle(.compound.textSecondary)
|
||
.multilineTextAlignment(.center)
|
||
|
||
Text(classicAppAccount.displayableName)
|
||
.font(.compound.headingLGBold)
|
||
.foregroundStyle(.compound.textPrimary)
|
||
.multilineTextAlignment(.center)
|
||
}
|
||
|
||
Text(classicAppAccount.userID)
|
||
.font(.compound.bodyLGSemibold)
|
||
.foregroundStyle(.compound.textPrimary)
|
||
.multilineTextAlignment(.center)
|
||
}
|
||
}
|
||
|
||
var buttons: some View {
|
||
VStack(spacing: 16) {
|
||
if isLoadingAccount {
|
||
Button {
|
||
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
||
} label: {
|
||
Label {
|
||
Text(L10n.screenOnboardingCheckingAccount)
|
||
} icon: {
|
||
ProgressView()
|
||
.tint(.compound.iconOnSolidPrimary)
|
||
}
|
||
}
|
||
.buttonStyle(.compound(.primary))
|
||
.disabled(true)
|
||
} else {
|
||
Button(L10n.actionContinue) {
|
||
context.send(viewAction: .continueWithClassic(classicAppAccount))
|
||
}
|
||
.buttonStyle(.compound(.primary))
|
||
|
||
Button(L10n.commonOtherOptions) {
|
||
context.send(viewAction: .otherOptions(classicAppAccount))
|
||
}
|
||
.buttonStyle(.compound(.secondary))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private extension ClassicAppAccount {
|
||
var displayableName: String {
|
||
if let displayName, !displayName.isEmpty {
|
||
displayName
|
||
} else if let localPart = userID.dropFirst().split(separator: ":").first, !localPart.isEmpty {
|
||
String(localPart)
|
||
} else {
|
||
userID
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Previews
|
||
|
||
struct AuthenticationClassicAppAccountView_Previews: PreviewProvider { // Not Testable – snapshots generated by main screen.
|
||
static let viewModel = makeViewModel()
|
||
static let classicAppAccount = {
|
||
let account = ClassicAppAccount.mockDan
|
||
account.state.isServerSupported = true
|
||
account.state.availableSecrets = .complete
|
||
return account
|
||
}()
|
||
|
||
static var previews: some View {
|
||
ElementNavigationStack {
|
||
AuthenticationClassicAppAccountView(context: viewModel.context, classicAppAccount: classicAppAccount)
|
||
}
|
||
.previewDisplayName("Ready")
|
||
|
||
ElementNavigationStack {
|
||
AuthenticationClassicAppAccountView(context: viewModel.context, classicAppAccount: .mockDan)
|
||
}
|
||
.previewDisplayName("Loading")
|
||
}
|
||
|
||
static func makeViewModel() -> AuthenticationStartScreenViewModel {
|
||
AuthenticationStartScreenViewModel(authenticationService: AuthenticationService.mock,
|
||
provisioningParameters: nil,
|
||
isBugReportServiceEnabled: false,
|
||
appMediator: AppMediatorMock(),
|
||
appSettings: ServiceLocator.shared.settings,
|
||
mediaProvider: MediaProviderMock(configuration: .init()),
|
||
userIndicatorController: UserIndicatorControllerMock())
|
||
}
|
||
}
|