From 905cb9634116c0efc8a025ab7d37670c6b07e05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Wallner-G=C3=A9hri?= <68284225+mpwg@users.noreply.github.com> Date: Tue, 28 Apr 2026 19:26:29 +0200 Subject: [PATCH] Refactor multiselect grids --- .../Capture/EntryFlowCoordinatorView.swift | 65 +++---------------- .../Capture/NewEntryDesignSystem.swift | 10 +-- .../Components/InputFlowLayout.swift | 36 ++++++++++ Symi/Sources/Shared/SetToggleMembership.swift | 9 +++ SymiTests/NewEntryDesignSystemTests.swift | 11 ++++ 5 files changed, 65 insertions(+), 66 deletions(-) create mode 100644 Symi/Sources/Shared/SetToggleMembership.swift diff --git a/Symi/Sources/Features/Capture/EntryFlowCoordinatorView.swift b/Symi/Sources/Features/Capture/EntryFlowCoordinatorView.swift index 7bcec34..d7d2319 100644 --- a/Symi/Sources/Features/Capture/EntryFlowCoordinatorView.swift +++ b/Symi/Sources/Features/Capture/EntryFlowCoordinatorView.swift @@ -162,7 +162,7 @@ private struct EntryHeadacheStepView: View { PainGaugeView(value: $coordinator.draft.intensity) InputFlowFieldGroup(title: "Wo spürst du den Schmerz?") { - HeadacheLocationGrid { + HeadacheOptionGrid { ForEach(visiblePainLocations) { location in PainLocationSelectionTile( option: location, @@ -170,14 +170,14 @@ private struct EntryHeadacheStepView: View { theme: .pain, accessibilityIdentifier: "entry-location-\(location.title)" ) { - toggle(location.title, in: &coordinator.draft.selectedPainLocations) + coordinator.draft.selectedPainLocations.toggleMembership(location.title) } } } } InputFlowFieldGroup(title: "Tagesbereich") { - HeadacheDayPartGrid { + HeadacheOptionGrid { ForEach(EntryDayPartPreset.allCases) { preset in InputFlowSelectionTile( title: preset.title, @@ -225,14 +225,6 @@ private struct EntryHeadacheStepView: View { coordinator.draft.selectedPainLocations = ["Schläfen"] } - - private func toggle(_ option: String, in selection: inout Set) { - if selection.contains(option) { - selection.remove(option) - } else { - selection.insert(option) - } - } } private extension EntryDayPartPreset { @@ -324,7 +316,7 @@ private struct PainLocationSelectionTile: View { return SymiColors.subtleSeparator(for: colorScheme).opacity(SymiOpacity.strongSurface) } } -private struct HeadacheLocationGrid: View { +private struct HeadacheOptionGrid: View { let content: Content init(@ViewBuilder content: () -> Content) { @@ -332,42 +324,9 @@ private struct HeadacheLocationGrid: View { } var body: some View { - LazyVGrid( - columns: Array( - repeating: GridItem( - .flexible(minimum: SymiSize.headacheOptionGridMinWidth), - spacing: SymiSpacing.xs, - alignment: .top - ), - count: SymiSize.headacheOptionGridColumnCount - ), - alignment: .leading, - spacing: SymiSpacing.xs - ) { - content - } - } -} - -private struct HeadacheDayPartGrid: View { - let content: Content - - init(@ViewBuilder content: () -> Content) { - self.content = content() - } - - var body: some View { - LazyVGrid( - columns: Array( - repeating: GridItem( - .flexible(minimum: SymiSize.headacheOptionGridMinWidth), - spacing: SymiSpacing.xs, - alignment: .top - ), - count: SymiSize.headacheOptionGridColumnCount - ), - alignment: .leading, - spacing: SymiSpacing.xs + InputFlowFixedTileGrid( + minimumColumnWidth: SymiSize.headacheOptionGridMinWidth, + columnCount: SymiSize.headacheOptionGridColumnCount ) { content } @@ -569,7 +528,7 @@ private struct EntryTriggersStepView: View { theme: .trigger, accessibilityIdentifier: "entry-trigger-\(option.title)" ) { - toggle(option.title, in: &coordinator.draft.selectedTriggers) + coordinator.draft.selectedTriggers.toggleMembership(option.title) } } } @@ -589,14 +548,6 @@ private struct EntryTriggersStepView: View { ) } } - - private func toggle(_ option: String, in selection: inout Set) { - if selection.contains(option) { - selection.remove(option) - } else { - selection.insert(option) - } - } } private struct EntryNoteStepView: View { diff --git a/Symi/Sources/Features/Capture/NewEntryDesignSystem.swift b/Symi/Sources/Features/Capture/NewEntryDesignSystem.swift index 4c69c6c..c533efe 100644 --- a/Symi/Sources/Features/Capture/NewEntryDesignSystem.swift +++ b/Symi/Sources/Features/Capture/NewEntryDesignSystem.swift @@ -126,7 +126,7 @@ struct MultiSelectGrid: View { isSelected: isSelected, colorToken: colorToken ) { - toggle(option) + selection.toggleMembership(option) } .accessibilityLabel("\(accessibilityPrefix): \(option)") .accessibilityValue(isSelected ? "Ausgewählt" : "Nicht ausgewählt") @@ -134,12 +134,4 @@ struct MultiSelectGrid: View { } } } - - private func toggle(_ option: String) { - if selection.contains(option) { - selection.remove(option) - } else { - selection.insert(option) - } - } } diff --git a/Symi/Sources/Features/InputFlow/Components/InputFlowLayout.swift b/Symi/Sources/Features/InputFlow/Components/InputFlowLayout.swift index 451e574..1290a1e 100644 --- a/Symi/Sources/Features/InputFlow/Components/InputFlowLayout.swift +++ b/Symi/Sources/Features/InputFlow/Components/InputFlowLayout.swift @@ -42,6 +42,42 @@ struct InputFlowTileGrid: View { } } +struct InputFlowFixedTileGrid: View { + let minimumColumnWidth: CGFloat + let columnCount: Int + let spacing: CGFloat + let content: Content + + init( + minimumColumnWidth: CGFloat, + columnCount: Int, + spacing: CGFloat = SymiSpacing.xs, + @ViewBuilder content: () -> Content + ) { + self.minimumColumnWidth = minimumColumnWidth + self.columnCount = columnCount + self.spacing = spacing + self.content = content() + } + + var body: some View { + LazyVGrid( + columns: Array( + repeating: GridItem( + .flexible(minimum: minimumColumnWidth), + spacing: spacing, + alignment: .top + ), + count: columnCount + ), + alignment: .leading, + spacing: spacing + ) { + content + } + } +} + struct InputFlowPillGrid: View { let minimumColumnWidth: CGFloat let content: Content diff --git a/Symi/Sources/Shared/SetToggleMembership.swift b/Symi/Sources/Shared/SetToggleMembership.swift new file mode 100644 index 0000000..a0b4aea --- /dev/null +++ b/Symi/Sources/Shared/SetToggleMembership.swift @@ -0,0 +1,9 @@ +extension Set { + mutating func toggleMembership(_ member: Element) { + if contains(member) { + remove(member) + } else { + insert(member) + } + } +} diff --git a/SymiTests/NewEntryDesignSystemTests.swift b/SymiTests/NewEntryDesignSystemTests.swift index f7eacfe..710825e 100644 --- a/SymiTests/NewEntryDesignSystemTests.swift +++ b/SymiTests/NewEntryDesignSystemTests.swift @@ -96,4 +96,15 @@ struct NewEntryDesignSystemTests { #expect(token.lightColorValue.hexString == expected[token, default: ""]) } } + + @Test + func setToggleMembershipAddsAndRemovesMembers() { + var selection: Set = ["Stress"] + + selection.toggleMembership("Wetter") + #expect(selection == ["Stress", "Wetter"]) + + selection.toggleMembership("Stress") + #expect(selection == ["Wetter"]) + } }