3 Commits

Author SHA1 Message Date
98244b3d65 Merge pull request 'fix: completed tasks reappearing as overdue (#2)' (#4) from fix/issue-2-task-completion-persistence into master
Reviewed-on: #4
2026-04-12 10:14:26 -05:00
42084b6601 Merge pull request 'fix: Save to Collection button not responding (#1)' (#3) from fix/issue-1-save-to-collection into master
Reviewed-on: #3
2026-04-12 10:14:14 -05:00
Trey t
987ebf9690 fix: Save to Collection button not responding without user interaction
The Save to Collection button on the identification screen would remain
disabled after plant identification completed, requiring the user to tap
a prediction row before it would enable. This was caused by an @Observable
+ @State tracking issue where computed properties in SwiftUI view modifiers
don't always trigger re-renders when the underlying observable changes.

Replaced the empty .onChange workaround with a local @State property
(saveEnabled) that is explicitly updated when selectedPrediction or
saveState changes, ensuring the button state always reflects the current
ViewModel state.

Fixes #1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:08:30 -05:00

View File

@@ -19,6 +19,11 @@ struct IdentificationView: View {
/// Tracks whether we've announced results to avoid duplicate announcements /// Tracks whether we've announced results to avoid duplicate announcements
@State private var hasAnnouncedResults = false @State private var hasAnnouncedResults = false
/// Local state to reliably drive the save button's enabled/disabled state.
/// Works around an @Observable + @State tracking issue where computed
/// properties in view modifiers don't always trigger re-renders.
@State private var saveEnabled = false
// MARK: - Scaled Metrics for Dynamic Type // MARK: - Scaled Metrics for Dynamic Type
@ScaledMetric(relativeTo: .body) private var closeIconSize: CGFloat = 16 @ScaledMetric(relativeTo: .body) private var closeIconSize: CGFloat = 16
@@ -76,8 +81,10 @@ struct IdentificationView: View {
announceStateChange(from: oldValue, to: newValue) announceStateChange(from: oldValue, to: newValue)
} }
.onChange(of: viewModel.selectedPrediction?.id) { _, _ in .onChange(of: viewModel.selectedPrediction?.id) { _, _ in
// Force view update when selection changes saveEnabled = viewModel.canSaveToCollection
// This ensures SwiftUI tracks @Observable property changes correctly }
.onChange(of: viewModel.saveState) { _, _ in
saveEnabled = viewModel.canSaveToCollection
} }
.accessibilityIdentifier(AccessibilityIdentifiers.Identification.identificationView) .accessibilityIdentifier(AccessibilityIdentifiers.Identification.identificationView)
.alert("Plant Saved!", isPresented: .init( .alert("Plant Saved!", isPresented: .init(
@@ -406,12 +413,12 @@ struct IdentificationView: View {
.padding(.vertical, 14) .padding(.vertical, 14)
.background( .background(
RoundedRectangle(cornerRadius: 12) RoundedRectangle(cornerRadius: 12)
.fill(viewModel.canSaveToCollection ? Color.accentColor : Color.gray) .fill(saveEnabled ? Color.accentColor : Color.gray)
) )
} }
.disabled(!viewModel.canSaveToCollection) .disabled(!saveEnabled)
.accessibilityLabel(viewModel.saveState == .saving ? "Saving plant" : "Save to Collection") .accessibilityLabel(viewModel.saveState == .saving ? "Saving plant" : "Save to Collection")
.accessibilityHint(viewModel.canSaveToCollection ? "Saves the selected plant to your collection" : "Select a plant first") .accessibilityHint(saveEnabled ? "Saves the selected plant to your collection" : "Select a plant first")
.accessibilityIdentifier(AccessibilityIdentifiers.Identification.saveToCollectionButton) .accessibilityIdentifier(AccessibilityIdentifiers.Identification.saveToCollectionButton)
} }
.padding(.horizontal, 20) .padding(.horizontal, 20)