Refactor expandable sections to use enum and set pattern for cleaner state management

This commit is contained in:
Beingpax 2025-12-31 18:48:45 +05:45
parent a6c47240e7
commit 9d0fc241ee
3 changed files with 43 additions and 21 deletions

View File

@ -1,26 +1,42 @@
import SwiftUI
enum ExpandableSection: Hashable {
case soundFeedback
case systemMute
case pauseMedia
case clipboardRestore
case customCancel
case middleClick
}
struct ExpandableToggleSection<Content: View>: View {
let section: ExpandableSection
let title: String
let helpText: String
@Binding var isEnabled: Bool
@Binding var isExpanded: Bool
@Binding var expandedSections: Set<ExpandableSection>
let content: Content
init(
section: ExpandableSection,
title: String,
helpText: String,
isEnabled: Binding<Bool>,
isExpanded: Binding<Bool>,
expandedSections: Binding<Set<ExpandableSection>>,
@ViewBuilder content: () -> Content
) {
self.section = section
self.title = title
self.helpText = helpText
self._isEnabled = isEnabled
self._isExpanded = isExpanded
self._expandedSections = expandedSections
self.content = content()
}
private var isExpanded: Bool {
expandedSections.contains(section)
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
@ -30,12 +46,12 @@ struct ExpandableToggleSection<Content: View>: View {
.toggleStyle(.switch)
.help(helpText)
.onChange(of: isEnabled) { _, newValue in
if newValue {
withAnimation(.easeInOut(duration: 0.2)) {
isExpanded = true
withAnimation(.easeInOut(duration: 0.2)) {
if newValue {
_ = expandedSections.insert(section)
} else {
expandedSections.remove(section)
}
} else {
isExpanded = false
}
}
@ -53,7 +69,11 @@ struct ExpandableToggleSection<Content: View>: View {
.onTapGesture {
if isEnabled {
withAnimation(.easeInOut(duration: 0.2)) {
isExpanded.toggle()
if isExpanded {
expandedSections.remove(section)
} else {
_ = expandedSections.insert(section)
}
}
}
}

View File

@ -4,7 +4,7 @@ struct ExperimentalFeaturesSection: View {
@AppStorage("isExperimentalFeaturesEnabled") private var isExperimentalFeaturesEnabled = false
@ObservedObject private var playbackController = PlaybackController.shared
@ObservedObject private var mediaController = MediaController.shared
@State private var isPauseMediaExpanded = false
@State private var expandedSections: Set<ExpandableSection> = []
var body: some View {
VStack(alignment: .leading, spacing: 12) {
@ -40,10 +40,11 @@ struct ExperimentalFeaturesSection: View {
.transition(.opacity.combined(with: .move(edge: .top)))
ExpandableToggleSection(
section: .pauseMedia,
title: "Pause Media during recording",
helpText: "Automatically pause active media playback during recordings and resume afterward.",
isEnabled: $playbackController.isPauseMediaEnabled,
isExpanded: $isPauseMediaExpanded
expandedSections: $expandedSections
) {
HStack(spacing: 8) {
Text("Resumption Delay")

View File

@ -22,11 +22,7 @@ struct SettingsView: View {
@State private var showResetOnboardingAlert = false
@State private var currentShortcut = KeyboardShortcuts.getShortcut(for: .toggleMiniRecorder)
@State private var isCustomCancelEnabled = false
@State private var isCustomSoundsExpanded = false
@State private var isSystemMuteExpanded = false
@State private var isClipboardRestoreExpanded = false
@State private var isCustomCancelExpanded = false
@State private var isMiddleClickExpanded = false
@State private var expandedSections: Set<ExpandableSection> = []
var body: some View {
@ -141,10 +137,11 @@ struct SettingsView: View {
ExpandableToggleSection(
section: .customCancel,
title: "Custom Cancel Shortcut",
helpText: "Shortcut for cancelling the current recording session. Default: double-tap Escape.",
isEnabled: $isCustomCancelEnabled,
isExpanded: $isCustomCancelExpanded
expandedSections: $expandedSections
) {
HStack(spacing: 12) {
Text("Cancel Shortcut")
@ -166,10 +163,11 @@ struct SettingsView: View {
Divider()
ExpandableToggleSection(
section: .middleClick,
title: "Enable Middle-Click Toggle",
helpText: "Use middle mouse button to toggle VoiceInk recording.",
isEnabled: $hotkeyManager.isMiddleClickToggleEnabled,
isExpanded: $isMiddleClickExpanded
expandedSections: $expandedSections
) {
HStack(spacing: 8) {
Text("Activation Delay")
@ -204,10 +202,11 @@ struct SettingsView: View {
) {
VStack(alignment: .leading, spacing: 12) {
ExpandableToggleSection(
section: .soundFeedback,
title: "Sound feedback",
helpText: "Play sounds when recording starts and stops",
isEnabled: $soundManager.isEnabled,
isExpanded: $isCustomSoundsExpanded
expandedSections: $expandedSections
) {
CustomSoundSettingsView()
}
@ -215,10 +214,11 @@ struct SettingsView: View {
Divider()
ExpandableToggleSection(
section: .systemMute,
title: "Mute system audio during recording",
helpText: "Automatically mute system audio when recording starts and restore when recording stops",
isEnabled: $mediaController.isSystemMuteEnabled,
isExpanded: $isSystemMuteExpanded
expandedSections: $expandedSections
) {
HStack(spacing: 8) {
Text("Resumption Delay")
@ -247,10 +247,11 @@ struct SettingsView: View {
Divider()
ExpandableToggleSection(
section: .clipboardRestore,
title: "Restore clipboard after paste",
helpText: "When enabled, VoiceInk will restore your original clipboard content after pasting the transcription.",
isEnabled: $restoreClipboardAfterPaste,
isExpanded: $isClipboardRestoreExpanded
expandedSections: $expandedSections
) {
HStack(spacing: 8) {
Text("Restore Delay")