Files
yattee/Yattee/Views/Home/HomeSettingsView.swift
Arkadiusz Fal d7df883d6f Remove media sources as Home sections (shortcuts-only)
A media-source "section" only rendered as a full-width "Browse {name}"
link, identical for all source types and redundant with the shortcut
card. Remove it from the Available Sections add-list and clean up any
already-added media-source sections on load.

Keep HomeSectionItem.mediaSource as a decode-only case so existing saved
homeSectionOrder data (decoded all-or-nothing) still parses; strip those
entries via removeAllHomeMediaSourceSections(). Media-source shortcuts
are unchanged.
2026-06-11 20:16:00 +02:00

844 lines
31 KiB
Swift

//
// HomeSettingsView.swift
// Yattee
//
// Settings sheet for customizing the Home view layout.
//
import SwiftUI
struct HomeSettingsView: View {
@Environment(\.appEnvironment) private var appEnvironment
// Local state for editing (copied from settings on appear, saved on dismiss)
@State private var shortcutLayout: HomeShortcutLayout = .cards
@State private var shortcutCardStyle: HomeShortcutCardStyle = .regular
@State private var shortcutCardColor: HomeShortcutCardColor = .soft
@State private var shortcutColorfulPalette: HomeShortcutColorfulPalette = .accent
@State private var shortcutCustomPaletteColors: [String] = []
@State private var shortcutOrder: [HomeShortcutItem] = []
@State private var shortcutVisibility: [HomeShortcutItem: Bool] = [:]
@State private var sectionOrder: [HomeSectionItem] = []
@State private var sectionVisibility: [HomeSectionItem: Bool] = [:]
@State private var sectionItemsLimit: Int = 5
@State private var sectionLayout: HomeSectionLayout = HomeSectionLayout.platformDefault
// Available items (not yet added to Home)
@State private var availableShortcutsByInstance: [(instance: Instance, cards: [HomeShortcutItem])] = []
@State private var availableSectionsByInstance: [(instance: Instance, sections: [HomeSectionItem])] = []
@State private var availableShortcutsByMediaSource: [(source: MediaSource, cards: [HomeShortcutItem])] = []
// Edit mode for delete functionality
@State private var isEditMode = false
// Load settings only once per presentation: pushing a child page (e.g. the
// card style view) fires onDisappear/onAppear on this view, and re-running
// loadSettings on pop-back would clobber edits made through bindings.
@State private var hasLoadedSettings = false
#if os(macOS)
// macOS: drive the style page programmatically. A List-embedded NavigationLink
// gets stuck in the selected (blue) state after popping back and can't be
// re-activated, so navigate via navigationDestination(isPresented:) instead.
@State private var showingStyleSettings = false
#endif
private var settingsManager: SettingsManager? { appEnvironment?.settingsManager }
var body: some View {
List {
#if !os(tvOS)
shortcutsSection
availableShortcutsSection
#endif
sectionsSection
availableSectionsSection
itemsLimitSection
}
#if os(iOS)
.environment(\.editMode, isEditMode ? .constant(.active) : .constant(.inactive))
#endif
#if os(macOS)
.listStyle(.inset)
.navigationDestination(isPresented: $showingStyleSettings) {
HomeShortcutStyleView(
style: $shortcutCardStyle,
color: $shortcutCardColor,
palette: $shortcutColorfulPalette,
customColors: $shortcutCustomPaletteColors,
onSave: saveSettingsIfLoaded
)
}
#endif
#if !os(tvOS)
.navigationTitle(String(localized: "home.settings.title"))
#endif
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.onAppear {
guard !hasLoadedSettings else { return }
hasLoadedSettings = true
loadSettings()
}
.onDisappear {
saveSettings()
}
}
// MARK: - Sections
private var shortcutsSection: some View {
Section {
#if !os(tvOS)
// Layout picker (List vs Cards)
Picker(String(localized: "home.settings.shortcuts.layout"), selection: $shortcutLayout) {
ForEach(HomeShortcutLayout.allCases, id: \.self) { layout in
Label(layout.displayName, systemImage: layout.systemImage)
.tag(layout)
}
}
.pickerStyle(.segmented)
// Card style (Compact / Regular) navigates to a page with a
// selector and a live preview. Only for cards layout.
if shortcutLayout == .cards {
#if os(macOS)
Button {
showingStyleSettings = true
} label: {
HStack(spacing: 8) {
Label(String(localized: "home.settings.shortcuts.style"), systemImage: "paintpalette")
Spacer()
Text(shortcutCardStyle.displayName)
.foregroundStyle(.secondary)
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(.tertiary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
#else
SettingsNavigationRow(
"home.settings.shortcuts.style",
systemImage: "paintpalette",
trailing: { Text(shortcutCardStyle.displayName) }
) {
HomeShortcutStyleView(
style: $shortcutCardStyle,
color: $shortcutCardColor,
palette: $shortcutColorfulPalette,
customColors: $shortcutCustomPaletteColors,
onSave: saveSettingsIfLoaded
)
}
#endif
}
#endif
#if os(tvOS)
ForEach(Array(shortcutOrder.enumerated()), id: \.element.id) { index, card in
if card != .downloads {
TVHomeItemRow(
icon: card.icon,
title: card.localizedTitle,
isVisible: shortcutBinding(for: card),
canMoveUp: index > 0 && shortcutOrder[index - 1] != .downloads,
canMoveDown: index < shortcutOrder.count - 1,
onMoveUp: { moveShortcut(at: index, direction: -1) },
onMoveDown: { moveShortcut(at: index, direction: 1) },
canDelete: canDelete(shortcut: card),
onDelete: { removeShortcut(card) }
)
}
}
#else
ForEach(shortcutOrder) { card in
shortcutRowView(for: card)
}
.onMove { from, to in
shortcutOrder.move(fromOffsets: from, toOffset: to)
}
#endif
} header: {
Text(String(localized: "home.settings.shortcuts.header"))
}
}
private var availableShortcutsSection: some View {
Section {
if availableShortcutsByInstance.isEmpty && availableShortcutsByMediaSource.isEmpty {
Text(String(localized: "home.settings.availableShortcuts.empty"))
.foregroundStyle(.secondary)
.italic()
} else {
ForEach(availableShortcutsByInstance, id: \.instance.id) { item in
ForEach(item.cards) { card in
availableShortcutRow(for: card, instance: item.instance)
}
}
ForEach(availableShortcutsByMediaSource, id: \.source.id) { item in
ForEach(item.cards) { card in
availableMediaSourceShortcutRow(for: card, source: item.source)
}
}
}
} header: {
Text(String(localized: "home.settings.availableShortcuts.header"))
} footer: {
Text(String(localized: "home.settings.availableShortcuts.footer"))
}
}
private var sectionsSection: some View {
Section {
Picker(String(localized: "home.settings.sections.layout"), selection: $sectionLayout) {
ForEach(HomeSectionLayout.allCases, id: \.self) { layout in
Label(layout.displayName, systemImage: layout.systemImage)
.tag(layout)
}
}
#if !os(tvOS)
.pickerStyle(.segmented)
#endif
#if os(tvOS)
ForEach(Array(sectionOrder.enumerated()), id: \.element.id) { index, section in
if section != .downloads {
TVHomeItemRow(
icon: section.icon,
title: section.localizedTitle,
isVisible: sectionBinding(for: section),
canMoveUp: index > 0 && sectionOrder[index - 1] != .downloads,
canMoveDown: index < sectionOrder.count - 1,
onMoveUp: { moveSection(at: index, direction: -1) },
onMoveDown: { moveSection(at: index, direction: 1) },
canDelete: canDelete(section: section),
onDelete: { removeSection(section) }
)
}
}
#else
ForEach(sectionOrder) { section in
sectionRowView(for: section)
}
.onMove { from, to in
sectionOrder.move(fromOffsets: from, toOffset: to)
}
#endif
} header: {
Text(String(localized: "home.settings.sections.header"))
} footer: {
Text(String(localized: "home.settings.sections.footer"))
}
}
private var availableSectionsSection: some View {
Section {
if availableSectionsByInstance.isEmpty {
Text(String(localized: "home.settings.availableSections.empty"))
.foregroundStyle(.secondary)
.italic()
} else {
ForEach(availableSectionsByInstance, id: \.instance.id) { item in
ForEach(item.sections) { section in
availableSectionRow(for: section, instance: item.instance)
}
}
}
} header: {
Text(String(localized: "home.settings.availableSections.header"))
} footer: {
Text(String(localized: "home.settings.availableSections.footer"))
}
}
#if os(tvOS)
private func moveShortcut(at index: Int, direction: Int) {
let newIndex = index + direction
guard newIndex >= 0, newIndex < shortcutOrder.count else { return }
shortcutOrder.swapAt(index, newIndex)
}
private func moveSection(at index: Int, direction: Int) {
let newIndex = index + direction
guard newIndex >= 0, newIndex < sectionOrder.count else { return }
sectionOrder.swapAt(index, newIndex)
}
#endif
private var itemsLimitSection: some View {
Section {
#if os(tvOS)
HStack {
Text(String(localized: "home.settings.itemsLimit"))
Spacer()
Button {
if sectionItemsLimit > 1 { sectionItemsLimit -= 1 }
} label: {
Image(systemName: "minus.circle")
}
.buttonStyle(TVSettingsButtonStyle())
Text("\(sectionItemsLimit)")
.monospacedDigit()
Button {
if sectionItemsLimit < 20 { sectionItemsLimit += 1 }
} label: {
Image(systemName: "plus.circle")
}
.buttonStyle(TVSettingsButtonStyle())
}
#else
Stepper(value: $sectionItemsLimit, in: 1...20) {
HStack {
Text(String(localized: "home.settings.itemsLimit"))
Spacer()
Text("\(sectionItemsLimit)")
.foregroundStyle(.secondary)
}
}
#endif
}
}
// MARK: - Bindings
private func shortcutBinding(for card: HomeShortcutItem) -> Binding<Bool> {
Binding(
get: { shortcutVisibility[card] ?? true },
set: { shortcutVisibility[card] = $0 }
)
}
private func sectionBinding(for section: HomeSectionItem) -> Binding<Bool> {
Binding(
get: { sectionVisibility[section] ?? false },
set: { sectionVisibility[section] = $0 }
)
}
// MARK: - Data Management
private func loadSettings() {
guard let settings = settingsManager,
let env = appEnvironment else { return }
shortcutLayout = settings.homeShortcutLayout
shortcutCardStyle = settings.homeShortcutCardStyle
shortcutCardColor = settings.homeShortcutCardColor
shortcutColorfulPalette = settings.homeShortcutColorfulPalette
shortcutCustomPaletteColors = settings.homeShortcutCustomPaletteColors
shortcutOrder = settings.homeShortcutOrder
shortcutVisibility = settings.homeShortcutVisibility
sectionOrder = settings.homeSectionOrder
sectionVisibility = settings.homeSectionVisibility
sectionItemsLimit = settings.homeSectionItemsLimit
sectionLayout = settings.homeSectionLayout
// Load available items
let instances = env.instancesManager.instances
availableShortcutsByInstance = settings.allAvailableShortcuts(instances: instances)
availableSectionsByInstance = settings.allAvailableSections(instances: instances)
let sources = env.mediaSourcesManager.sources
availableShortcutsByMediaSource = settings.allAvailableMediaSourceShortcuts(sources: sources)
}
private func saveSettingsIfLoaded() {
guard hasLoadedSettings else { return }
saveSettings()
}
private func saveSettings() {
guard let settings = settingsManager else { return }
settings.homeShortcutLayout = shortcutLayout
settings.homeShortcutCardStyle = shortcutCardStyle
settings.homeShortcutCardColor = shortcutCardColor
settings.homeShortcutColorfulPalette = shortcutColorfulPalette
settings.homeShortcutCustomPaletteColors = shortcutCustomPaletteColors
settings.homeShortcutOrder = shortcutOrder
settings.homeShortcutVisibility = shortcutVisibility
settings.homeSectionOrder = sectionOrder
settings.homeSectionVisibility = sectionVisibility
settings.homeSectionItemsLimit = sectionItemsLimit
settings.homeSectionLayout = sectionLayout
}
// MARK: - Available Item Management
private func addShortcut(_ card: HomeShortcutItem) {
// Add to local state
if !shortcutOrder.contains(where: { $0.id == card.id }) {
shortcutOrder.append(card)
shortcutVisibility[card] = true // Visible by default
}
// Persist to settings
switch card {
case .instanceContent(let instanceID, let contentType):
settingsManager?.addToHome(instanceID: instanceID, contentType: contentType, asCard: true)
case .mediaSource(let sourceID):
settingsManager?.addToHome(sourceID: sourceID, asCard: true)
default:
break
}
// Reload available items
loadSettings()
}
private func addSection(_ section: HomeSectionItem) {
// Add to local state
if !sectionOrder.contains(where: { $0.id == section.id }) {
sectionOrder.append(section)
sectionVisibility[section] = true // Visible by default
}
// Persist to settings
switch section {
case .instanceContent(let instanceID, let contentType):
settingsManager?.addToHome(instanceID: instanceID, contentType: contentType, asCard: false)
default:
break
}
// Reload available items
loadSettings()
}
private func removeShortcut(_ card: HomeShortcutItem) {
// Remove from local state
shortcutOrder.removeAll { $0.id == card.id }
shortcutVisibility.removeValue(forKey: card)
// Persist to settings
switch card {
case .instanceContent(let instanceID, let contentType):
settingsManager?.removeFromHome(instanceID: instanceID, contentType: contentType)
case .mediaSource(let sourceID):
settingsManager?.removeFromHome(sourceID: sourceID)
default:
break
}
// Reload available items
loadSettings()
}
private func removeSection(_ section: HomeSectionItem) {
// Remove from local state
sectionOrder.removeAll { $0.id == section.id }
sectionVisibility.removeValue(forKey: section)
// Persist to settings
switch section {
case .instanceContent(let instanceID, let contentType):
settingsManager?.removeFromHome(instanceID: instanceID, contentType: contentType)
default:
break
}
// Reload available items
loadSettings()
}
private func canDelete(shortcut: HomeShortcutItem) -> Bool {
if case .instanceContent = shortcut {
return true
}
if case .mediaSource = shortcut {
return true
}
return false
}
private func canDelete(section: HomeSectionItem) -> Bool {
if case .instanceContent = section {
return true
}
return false
}
// MARK: - Card and Section Row Views
@ViewBuilder
private func shortcutRowView(for card: HomeShortcutItem) -> some View {
switch card {
case .instanceContent(let instanceID, let contentType):
if let instance = instanceFromID(instanceID) {
let isDisabled = !instance.isEnabled || (contentType == .feed && !isLoggedIn(instance))
HomeItemRow(
icon: contentType.icon,
title: "\(instance.displayName) - \(contentType.localizedTitle)",
isVisible: shortcutBinding(for: card),
isDisabled: isDisabled,
disabledReason: disabledReason(instance: instance, contentType: contentType)
)
#if os(iOS)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
removeShortcut(card)
} label: {
Label(String(localized: "home.settings.remove"), systemImage: "trash")
}
.tint(.red)
}
#endif
.contextMenu {
Button(role: .destructive) {
removeShortcut(card)
} label: {
Label(String(localized: "home.settings.remove"), systemImage: "trash")
}
}
}
case .mediaSource(let sourceID):
if let source = appEnvironment?.mediaSourcesManager.sources.first(where: { $0.id == sourceID }) {
let isDisabled = !source.isEnabled
HomeItemRow(
icon: source.type.systemImage,
title: "\(source.name) (\(source.type.displayName))",
isVisible: shortcutBinding(for: card),
isDisabled: isDisabled,
disabledReason: isDisabled ? String(localized: "home.settings.sourceDisabled") : nil
)
#if os(iOS)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
removeShortcut(card)
} label: {
Label(String(localized: "home.settings.remove"), systemImage: "trash")
}
.tint(.red)
}
#endif
.contextMenu {
Button(role: .destructive) {
removeShortcut(card)
} label: {
Label(String(localized: "home.settings.remove"), systemImage: "trash")
}
}
}
default:
HomeItemRow(
icon: card.icon,
title: card.localizedTitle,
isVisible: shortcutBinding(for: card)
)
}
}
@ViewBuilder
private func sectionRowView(for section: HomeSectionItem) -> some View {
switch section {
case .instanceContent(let instanceID, let contentType):
if let instance = instanceFromID(instanceID) {
let isDisabled = !instance.isEnabled || (contentType == .feed && !isLoggedIn(instance))
HomeItemRow(
icon: contentType.icon,
title: "\(instance.displayName) - \(contentType.localizedTitle)",
isVisible: sectionBinding(for: section),
isDisabled: isDisabled,
disabledReason: disabledReason(instance: instance, contentType: contentType)
)
#if os(iOS)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
removeSection(section)
} label: {
Label(String(localized: "home.settings.remove"), systemImage: "trash")
}
.tint(.red)
}
#endif
.contextMenu {
Button(role: .destructive) {
removeSection(section)
} label: {
Label(String(localized: "home.settings.remove"), systemImage: "trash")
}
}
}
default:
HomeItemRow(
icon: section.icon,
title: section.localizedTitle,
isVisible: sectionBinding(for: section)
)
}
}
@ViewBuilder
private func availableShortcutRow(for card: HomeShortcutItem, instance: Instance) -> some View {
if case .instanceContent(_, let contentType) = card {
let isDisabled = !instance.isEnabled || (contentType == .feed && !isLoggedIn(instance))
HStack {
Image(systemName: contentType.icon)
.frame(width: 24)
.foregroundStyle(isDisabled ? .tertiary : .secondary)
Text("\(instance.displayName) - \(contentType.localizedTitle)")
.foregroundStyle(isDisabled ? .tertiary : .primary)
Spacer()
Button {
addShortcut(card)
} label: {
Image(systemName: "plus.circle.fill")
.foregroundStyle(.green)
.imageScale(.large)
}
.buttonStyle(.plain)
.disabled(isDisabled)
}
#if !os(tvOS)
.help(isDisabled ? (disabledReason(instance: instance, contentType: contentType) ?? "") : "")
#endif
}
}
@ViewBuilder
private func availableSectionRow(for section: HomeSectionItem, instance: Instance) -> some View {
if case .instanceContent(_, let contentType) = section {
let isDisabled = !instance.isEnabled || (contentType == .feed && !isLoggedIn(instance))
HStack {
Image(systemName: contentType.icon)
.frame(width: 24)
.foregroundStyle(isDisabled ? .tertiary : .secondary)
Text("\(instance.displayName) - \(contentType.localizedTitle)")
.foregroundStyle(isDisabled ? .tertiary : .primary)
Spacer()
Button {
addSection(section)
} label: {
Image(systemName: "plus.circle.fill")
.foregroundStyle(.green)
.imageScale(.large)
}
.buttonStyle(.plain)
.disabled(isDisabled)
}
#if !os(tvOS)
.help(isDisabled ? (disabledReason(instance: instance, contentType: contentType) ?? "") : "")
#endif
}
}
private func instanceFromID(_ id: UUID) -> Instance? {
appEnvironment?.instancesManager.instances.first(where: { $0.id == id })
}
private func isLoggedIn(_ instance: Instance) -> Bool {
guard instance.supportsFeed else { return false }
return appEnvironment?.credentialsManager(for: instance)?.isLoggedIn(for: instance) ?? false
}
private func disabledReason(instance: Instance, contentType: InstanceContentType) -> String? {
if !instance.isEnabled {
return String(localized: "home.settings.instanceDisabled")
}
if contentType == .feed && !isLoggedIn(instance) {
return String(localized: "home.settings.feedRequiresLogin")
}
return nil
}
@ViewBuilder
private func availableMediaSourceShortcutRow(for card: HomeShortcutItem, source: MediaSource) -> some View {
if case .mediaSource = card {
let isDisabled = !source.isEnabled
HStack {
Image(systemName: source.type.systemImage)
.frame(width: 24)
.foregroundStyle(isDisabled ? .tertiary : .secondary)
Text("\(source.name) (\(source.type.displayName))")
.foregroundStyle(isDisabled ? .tertiary : .primary)
Spacer()
Button {
addShortcut(card)
} label: {
Image(systemName: "plus.circle.fill")
.foregroundStyle(.green)
.imageScale(.large)
}
.buttonStyle(.plain)
.disabled(isDisabled)
}
#if !os(tvOS)
.help(isDisabled ? String(localized: "home.settings.sourceDisabled") : "")
#endif
}
}
}
// MARK: - Home Item Row
#if os(tvOS)
private struct TVHomeItemRow: View {
let icon: String
let title: String
@Binding var isVisible: Bool
let canMoveUp: Bool
let canMoveDown: Bool
let onMoveUp: () -> Void
let onMoveDown: () -> Void
var canDelete: Bool = false
var onDelete: (() -> Void)? = nil
@State private var showingDeleteConfirmation = false
var body: some View {
HStack(spacing: 12) {
// Move buttons - compact style
VStack(spacing: 4) {
Button {
onMoveUp()
} label: {
Image(systemName: "chevron.up")
.font(.caption)
.foregroundStyle(canMoveUp ? .primary : .tertiary)
.frame(width: 30, height: 24)
}
.buttonStyle(TVCompactButtonStyle())
.disabled(!canMoveUp)
Button {
onMoveDown()
} label: {
Image(systemName: "chevron.down")
.font(.caption)
.foregroundStyle(canMoveDown ? .primary : .tertiary)
.frame(width: 30, height: 24)
}
.buttonStyle(TVCompactButtonStyle())
.disabled(!canMoveDown)
}
// Main content - toggle visibility
Button {
isVisible.toggle()
} label: {
HStack {
Image(systemName: icon)
.frame(width: 24)
.foregroundStyle(.secondary)
Text(title)
.foregroundStyle(.primary)
Spacer()
Image(systemName: isVisible ? "checkmark.circle.fill" : "circle")
.foregroundColor(isVisible ? .green : .secondary)
.font(.title3)
}
.frame(maxWidth: .infinity)
}
.buttonStyle(TVFormRowButtonStyle())
// Delete button (only for instance content)
if canDelete {
Button {
showingDeleteConfirmation = true
} label: {
Image(systemName: "trash")
.font(.caption)
.foregroundStyle(.red)
.frame(width: 30, height: 24)
}
.buttonStyle(TVCompactButtonStyle())
.alert(String(localized: "home.removeConfirmation.title"), isPresented: $showingDeleteConfirmation) {
Button(String(localized: "common.cancel"), role: .cancel) { }
Button("common.remove", role: .destructive) {
onDelete?()
}
}
}
}
.padding(.vertical, 4)
}
}
/// Compact button style for small controls like up/down arrows
private struct TVCompactButtonStyle: ButtonStyle {
@Environment(\.isFocused) private var isFocused
func makeBody(configuration: Configuration) -> some View {
configuration.label
.background(
RoundedRectangle(cornerRadius: 6)
.fill(isFocused ? Color.white.opacity(0.2) : Color.clear)
)
.scaleEffect(configuration.isPressed ? 0.9 : (isFocused ? 1.1 : 1.0))
.animation(.easeInOut(duration: 0.1), value: isFocused)
.animation(.easeInOut(duration: 0.1), value: configuration.isPressed)
}
}
#endif
private struct HomeItemRow: View {
let icon: String
let title: String
@Binding var isVisible: Bool
var isDisabled: Bool = false
var disabledReason: String? = nil
var body: some View {
#if os(tvOS)
Button {
if !isDisabled {
isVisible.toggle()
}
} label: {
HStack {
Image(systemName: icon)
.frame(width: 24)
.foregroundStyle(isDisabled ? .tertiary : .secondary)
Text(title)
.foregroundStyle(isDisabled ? .tertiary : .primary)
Spacer()
Image(systemName: isVisible ? "checkmark.circle.fill" : "circle")
.foregroundColor(isDisabled ? .secondary.opacity(0.5) : (isVisible ? .green : .secondary))
.font(.title3)
}
.contentShape(Rectangle())
}
.buttonStyle(TVFormRowButtonStyle())
.disabled(isDisabled)
#else
HStack {
Image(systemName: icon)
.frame(width: 24)
.foregroundStyle(isDisabled ? .tertiary : .secondary)
Text(title)
.foregroundStyle(isDisabled ? .tertiary : .primary)
Spacer()
Toggle("", isOn: $isVisible)
.labelsHidden()
.disabled(isDisabled)
}
.help(disabledReason ?? "")
#endif
}
}
// MARK: - Preview
#Preview {
HomeSettingsView()
.appEnvironment(.preview)
}