mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
fccad5fc30
Remove some redundant injections when passed alongside a user session too. # Conflicts: # ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift # ElementX/Sources/Screens/Timeline/TimelineViewModel.swift
322 lines
13 KiB
Swift
322 lines
13 KiB
Swift
//
|
|
// 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 SFSafeSymbols
|
|
import SwiftUI
|
|
|
|
struct SettingsScreen: View {
|
|
@Bindable var context: SettingsScreenViewModel.Context
|
|
|
|
private var shouldHideManageAccountSection: Bool {
|
|
context.viewState.accountProfileURL == nil &&
|
|
!context.viewState.showBlockedUsers &&
|
|
!context.viewState.showLinkNewDeviceButton
|
|
}
|
|
|
|
var body: some View {
|
|
Form {
|
|
userSection
|
|
|
|
if context.viewState.showUserStatusInput {
|
|
userStatusSection
|
|
}
|
|
|
|
if !shouldHideManageAccountSection {
|
|
manageAccountSection
|
|
}
|
|
|
|
manageMyAppSection
|
|
|
|
generalSection
|
|
|
|
signOutSection
|
|
|
|
if context.viewState.showDeveloperOptions {
|
|
developerOptionsSection
|
|
}
|
|
}
|
|
.compoundList()
|
|
.navigationTitle(L10n.commonSettings)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbarVisibility(context.viewState.navigationBarVisibility, for: .navigationBar)
|
|
.toolbar { toolbar }
|
|
.sheet(isPresented: $context.isPresentingStatusPicker) {
|
|
SettingsScreenUserStatusPickerView { action in
|
|
context.send(viewAction: .userStatus(action))
|
|
}
|
|
.presentationDetents([.medium])
|
|
.presentationBackground(.compound.bgCanvasDefault)
|
|
}
|
|
}
|
|
|
|
private var userSection: some View {
|
|
Section {
|
|
ListRow(kind: .custom {
|
|
Button {
|
|
context.send(viewAction: .userDetails)
|
|
} label: {
|
|
HStack(spacing: 12) {
|
|
LoadableAvatarImage(url: context.viewState.userProfile.avatarURL,
|
|
name: context.viewState.userProfile.displayName,
|
|
contentID: context.viewState.userProfile.id,
|
|
avatarSize: .user(on: .settings),
|
|
mediaProvider: context.mediaProvider)
|
|
.accessibilityHidden(true)
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
HStack(spacing: 6) {
|
|
Text(context.viewState.userProfile.displayName ?? "")
|
|
|
|
if let statusEmoji = context.viewState.userProfile.status.displayed?.emoji {
|
|
Text(String(statusEmoji))
|
|
}
|
|
}
|
|
.font(.compound.headingMD)
|
|
.foregroundColor(.compound.textPrimary)
|
|
|
|
Text(context.viewState.userProfile.id)
|
|
.font(.compound.bodySM)
|
|
.foregroundColor(.compound.textSecondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
ListRowAccessory.navigationLink
|
|
}
|
|
.padding(.horizontal, ListRowPadding.horizontal)
|
|
.padding(.vertical, 8)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
private var userStatusSection: some View {
|
|
Section {
|
|
SettingsScreenUserStatusRow(mode: context.viewState.userStatusRowMode) { action in
|
|
context.send(viewAction: .userStatus(action))
|
|
}
|
|
}
|
|
}
|
|
|
|
private var manageMyAppSection: some View {
|
|
Section {
|
|
ListRow(label: .default(title: L10n.screenNotificationSettingsTitle,
|
|
icon: \.notifications),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .notifications)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.notifications)
|
|
|
|
ListRow(label: .default(title: L10n.commonScreenLock,
|
|
icon: \.lock),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .appLock)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.screenLock)
|
|
|
|
switch context.viewState.securitySectionMode {
|
|
case .secureBackup:
|
|
ListRow(label: .default(title: L10n.commonEncryption,
|
|
icon: \.key),
|
|
details: context.viewState.showSecuritySectionBadge ? .icon(securitySectionBadge) : nil,
|
|
kind: .navigationLink { context.send(viewAction: .secureBackup) })
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.secureBackup)
|
|
default:
|
|
EmptyView()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var manageAccountSection: some View {
|
|
Section {
|
|
if let url = context.viewState.accountProfileURL {
|
|
ListRow(label: .default(title: L10n.actionManageAccountAndDevices,
|
|
icon: \.userProfile),
|
|
kind: .button {
|
|
context.send(viewAction: .manageAccount(url: url))
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.account)
|
|
}
|
|
|
|
if context.viewState.showLinkNewDeviceButton {
|
|
ListRow(label: .default(title: L10n.commonLinkNewDevice,
|
|
icon: \.devices),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .linkNewDevice)
|
|
})
|
|
}
|
|
|
|
if context.viewState.showBlockedUsers {
|
|
ListRow(label: .default(title: L10n.commonBlockedUsers,
|
|
icon: \.block),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .blockedUsers)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.blockedUsers)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var generalSection: some View {
|
|
Section {
|
|
ListRow(label: .default(title: L10n.commonAdvancedSettings,
|
|
icon: \.settings),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .advancedSettings)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.advancedSettings)
|
|
|
|
ListRow(label: .default(title: L10n.screenAdvancedSettingsLabs,
|
|
icon: \.labs),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .labs)
|
|
})
|
|
|
|
ListRow(label: .default(title: L10n.commonAbout,
|
|
icon: \.info),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .about)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.about)
|
|
|
|
if context.viewState.isBugReportServiceEnabled {
|
|
ListRow(label: .default(title: L10n.commonReportAProblem,
|
|
icon: \.chatProblem),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .reportBug)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.reportBug)
|
|
}
|
|
|
|
if context.viewState.showAnalyticsSettings {
|
|
ListRow(label: .default(title: L10n.commonAnalytics,
|
|
icon: \.chart),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .analytics)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.analytics)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var signOutSection: some View {
|
|
Section {
|
|
ListRow(label: .action(title: L10n.screenSignoutPreferenceItem,
|
|
icon: \.close,
|
|
role: .destructive),
|
|
kind: .button {
|
|
context.send(viewAction: .logout)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.logout)
|
|
|
|
if context.viewState.showAccountDeactivation {
|
|
ListRow(label: .action(title: L10n.actionDeleteAccount,
|
|
icon: \.delete,
|
|
role: .destructive),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .deactivateAccount)
|
|
})
|
|
}
|
|
} footer: {
|
|
if !context.viewState.showDeveloperOptions {
|
|
versionSection
|
|
}
|
|
}
|
|
}
|
|
|
|
private var developerOptionsSection: some View {
|
|
Section {
|
|
ListRow(label: .default(title: L10n.commonDeveloperOptions,
|
|
icon: \.code),
|
|
kind: .navigationLink {
|
|
context.send(viewAction: .developerOptions)
|
|
})
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.developerOptions)
|
|
} footer: {
|
|
versionSection
|
|
}
|
|
}
|
|
|
|
private var versionSection: some View {
|
|
VStack(spacing: 0) {
|
|
versionText
|
|
.frame(maxWidth: .infinity)
|
|
|
|
if let deviceID = context.viewState.deviceID {
|
|
Text(deviceID)
|
|
}
|
|
}
|
|
.compoundListSectionFooter()
|
|
.textSelection(.enabled)
|
|
.padding(.top, 24)
|
|
.onTapGesture(count: 7) {
|
|
context.send(viewAction: .enableDeveloperOptions)
|
|
}
|
|
}
|
|
|
|
private var versionText: Text {
|
|
// Let's not snapshot a changing version string.
|
|
let shortVersion = ProcessInfo.isRunningTests ? "0.0.0" : InfoPlistReader.main.bundleShortVersionString
|
|
let version = ProcessInfo.isRunningTests ? "1" : InfoPlistReader.main.bundleVersion
|
|
return Text(L10n.settingsVersionNumber(shortVersion, version))
|
|
}
|
|
|
|
private var toolbar: some ToolbarContent {
|
|
ToolbarItem(placement: .primaryAction) {
|
|
ToolbarButton(role: .close) { context.send(viewAction: .close) }
|
|
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.done)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var securitySectionBadge: some View {
|
|
if context.viewState.showSecuritySectionBadge {
|
|
BadgeView(size: 10)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Previews
|
|
|
|
@available(iOS 26.0, *)
|
|
struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
|
|
static let viewModel = makeViewModel()
|
|
static let bugReportDisabledViewModel = makeViewModel(isBugReportServiceEnabled: false)
|
|
|
|
static var previews: some View {
|
|
ElementNavigationStack {
|
|
SettingsScreen(context: viewModel.context)
|
|
}
|
|
.snapshotPreferences(expect: viewModel.context.observe(\.viewState.accountProfileURL).map { $0 != nil })
|
|
.frame(height: 1100)
|
|
.previewLayout(.sizeThatFits)
|
|
.previewDisplayName("Default")
|
|
|
|
ElementNavigationStack {
|
|
SettingsScreen(context: bugReportDisabledViewModel.context)
|
|
}
|
|
.snapshotPreferences(expect: bugReportDisabledViewModel.context.observe(\.viewState.accountProfileURL).map { $0 != nil })
|
|
.frame(height: 1050)
|
|
.previewLayout(.sizeThatFits)
|
|
.previewDisplayName("Bug report disabled")
|
|
}
|
|
|
|
static func makeViewModel(isBugReportServiceEnabled: Bool = true) -> SettingsScreenViewModel {
|
|
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: "@alice:example.com",
|
|
deviceID: "AAAAAAAAAAA",
|
|
displayName: "Alice Liddell",
|
|
status: .mockFocussing))))
|
|
return SettingsScreenViewModel(userSession: userSession,
|
|
userSettings: .volatile(),
|
|
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
|
isInSecondaryWindow: false,
|
|
userIndicatorController: UserIndicatorControllerMock())
|
|
}
|
|
}
|