Skip to content

Add Manage Callouts toggles for sound effects and delays#258

Open
MBtheOtaku wants to merge 1 commit into
soundscape-community:mainfrom
MBtheOtaku:setting_delay_callouts_226
Open

Add Manage Callouts toggles for sound effects and delays#258
MBtheOtaku wants to merge 1 commit into
soundscape-community:mainfrom
MBtheOtaku:setting_delay_callouts_226

Conversation

@MBtheOtaku

@MBtheOtaku MBtheOtaku commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Add new Manage Callouts settings:
Sound Effects
Delays
Place the new toggles directly after Allow Callouts in the callouts section. Add and persist new settings in SettingsContext and expose them via AutoCalloutSettingsProvider: calloutSoundEffectsEnabled
calloutPausesEnabled
Wire callout runtime behavior to respect these settings: Disable prefix/effect sounds when Sound Effects is off. Remove inter-callout delay when Delays is off.
Apply behavior updates across automatic, beacon, and intersection callout flows. Add localization keys and updated accessible hint text: Sounds that play before the spoken messages
Short pauses between callouts
Update storyboard fallback text to match the final localized wording.

Summary by CodeRabbit

  • New Features
    • Added two new toggles in Settings to customize callout behavior: Sound Effects (controls whether audio plays before callout messages) and Delays (controls pauses between callouts).

Add new Manage Callouts settings:
Sound Effects
Delays
Place the new toggles directly after Allow Callouts in the callouts section.
Add and persist new settings in SettingsContext and expose them via AutoCalloutSettingsProvider:
calloutSoundEffectsEnabled
calloutPausesEnabled
Wire callout runtime behavior to respect these settings:
Disable prefix/effect sounds when Sound Effects is off.
Remove inter-callout delay when Delays is off.
Apply behavior updates across automatic, beacon, and intersection callout flows.
Add localization keys and updated accessible hint text:
Sounds that play before the spoken messages
Short pauses between callouts
Update storyboard fallback text to match the final localized wording.
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces two new user-configurable callout settings for the iOS app: sound effects (controlling whether callouts include a leading sound) and delays (controlling pauses between announcements). The implementation spans protocol definitions, persistent settings storage, UI controls, callout generation logic, and storyboard layouts.

Changes

Cohort / File(s) Summary
Localization
Localizable.strings
Added four new localization keys for "Sound Effects" and "Delays" settings with descriptive text.
Settings Protocol & Storage
AutoCalloutSettingsProvider.swift, SettingsContext.swift
Added calloutSoundEffectsEnabled and calloutPausesEnabled boolean properties to the protocol and implemented persistent storage via UserDefaults (default value: true for both).
Callout Models
DestinationCallout.swift, IntersectionCallout.swift
Extended initializers to accept configurable includePrefixSound parameter (default: true), replacing hardcoded constants.
Callout Generators
AutoCalloutGenerator.swift, BeaconCalloutGenerator.swift, IntersectionGenerator.swift
Updated callout creation to pass settings.calloutSoundEffectsEnabled to callout constructors; replaced fixed calloutDelay constant with computed property that returns 0.75 when settings.calloutPausesEnabled is enabled, otherwise 0.0.
UI Components
CalloutSettingsCellView.swift, SettingsViewController.swift, Settings.storyboard
Added two new table cell cases (soundEffects, delays) with corresponding UI initialization, toggle persistence, telemetry events, and two new storyboard prototypes with outlet/action wiring.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SettingsVC as SettingsViewController
    participant Toggle as UISwitch
    participant Settings as SettingsContext
    participant UDefaults as UserDefaults
    participant Generator as CalloutGenerator
    participant Callout as POICallout

    User->>SettingsVC: Taps sound effects toggle
    SettingsVC->>Toggle: Captures value change
    Toggle->>SettingsVC: onSettingValueChanged
    SettingsVC->>Settings: Set calloutSoundEffectsEnabled
    Settings->>UDefaults: Persist to UserDefaults
    UDefaults-->>Settings: ✓ Saved

    Note over Generator: When generating callout...
    Generator->>Settings: Read calloutSoundEffectsEnabled
    Settings-->>Generator: Return setting value
    Generator->>Callout: Initialize with includePrefixSound
    Callout-->>Generator: Callout created
    Generator-->>User: Play callout with/without prefix sound
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • RDMurray

Poem

🐰 With chirps and pauses, soft and sound,
Our callouts now make sense around!
A tick, a pause, a gentle ring—
Together they make everything sing! 🔊✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding two new toggles (sound effects and delays) to the Manage Callouts settings, which aligns perfectly with the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
apps/ios/GuideDogs/Code/Behaviors/Default/IntersectionGenerator.swift (1)

140-156: Consider using dependency injection for settings access.

Unlike AutoCalloutGenerator which receives settings: AutoCalloutSettingsProvider via its initializer, IntersectionGenerator directly accesses SettingsContext.shared. This inconsistency makes unit testing harder since you can't inject a mock settings provider.

The initializer already accepts other dependencies (geoManager, data, geocoder), so adding a settings parameter would be consistent.

♻️ Suggested refactor for dependency injection
 class IntersectionGenerator: AutomaticGenerator {
+    private unowned let settings: AutoCalloutSettingsProvider
     // ... existing properties ...
     
-    init(_ owner: SoundscapeBehavior, geoManager: GeolocationManagerProtocol, data: SpatialDataProtocol, geocoder: ReverseGeocoder) {
+    init(_ owner: SoundscapeBehavior, geoManager: GeolocationManagerProtocol, data: SpatialDataProtocol, geocoder: ReverseGeocoder, settings: AutoCalloutSettingsProvider = SettingsContext.shared) {
+        self.settings = settings
         self.owner = owner
         // ...
     }

Then replace SettingsContext.shared.calloutSoundEffectsEnabled with settings.calloutSoundEffectsEnabled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ios/GuideDogs/Code/Behaviors/Default/IntersectionGenerator.swift` around
lines 140 - 156, IntersectionGenerator should accept a settings provider like
AutoCalloutGenerator does: add a property (e.g., settings:
AutoCalloutSettingsProvider) initialized via the IntersectionGenerator
initializer and replace any direct uses of
SettingsContext.shared.calloutSoundEffectsEnabled in methods (notably where
callout is created and where event.geocodedLocation.buildCallout(...) is called)
with settings.calloutSoundEffectsEnabled; update any callers to pass the
concrete settings provider (or a mock in tests) so behavior remains the same but
becomes testable.
apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift (1)

434-450: Consider adding notifications for consistency with other settings.

Several similar settings (e.g., automaticCalloutsEnabled, shakeCalloutsEnabled, sense settings) post notifications when changed. The new calloutSoundEffectsEnabled and calloutPausesEnabled properties don't follow this pattern.

While this isn't a functional issue (generators read these values on-demand during callout generation), adding notifications would improve consistency and allow other UI components to react to changes if needed in the future.

♻️ Optional: Add notifications for new settings
 // In Notification.Name extension at the top of the file:
+    static let calloutSoundEffectsEnabledChanged = Notification.Name("GDACalloutSoundEffectsChanged")
+    static let calloutPausesEnabledChanged = Notification.Name("GDACalloutPausesChanged")

 // In the setters:
     var calloutSoundEffectsEnabled: Bool {
         get {
             return userDefaults.bool(forKey: Keys.calloutSoundEffectsEnabled)
         }
         set(newValue) {
             userDefaults.set(newValue, forKey: Keys.calloutSoundEffectsEnabled)
+            NotificationCenter.default.post(name: .calloutSoundEffectsEnabledChanged, object: self, userInfo: [Keys.enabled: newValue])
         }
     }

     var calloutPausesEnabled: Bool {
         get {
             return userDefaults.bool(forKey: Keys.calloutPausesEnabled)
         }
         set(newValue) {
             userDefaults.set(newValue, forKey: Keys.calloutPausesEnabled)
+            NotificationCenter.default.post(name: .calloutPausesEnabledChanged, object: self, userInfo: [Keys.enabled: newValue])
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift` around lines 434
- 450, Add notifications in the setters for calloutSoundEffectsEnabled and
calloutPausesEnabled similar to the pattern used by automaticCalloutsEnabled and
shakeCalloutsEnabled: after calling userDefaults.set(...), post the same
NotificationCenter notification those other settings use so observers can react
to changes (use the same Notification.Name constant/pattern those properties
use).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/DestinationCallout.swift`:
- Line 56: The initializer for DestinationCallout currently has a defaulted
parameter includePrefixSound = true allowing callers to omit it; remove the
default so the init signature becomes init(_ calloutOrigin: CalloutOrigin, _
entityKey: String, _ causedAudioDisabled: Bool = false, _ includePrefixSound:
Bool) to force callers to pass the setting explicitly, then update all call
sites (notably where PreviewGenerator constructs DestinationCallout) to pass the
correct includePrefixSound value based on the Sound Effects toggle or preview
behavior.

In
`@apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/IntersectionCallout.swift`:
- Line 42: The initializer for IntersectionCallout currently has a default true
for includePrefixSound which lets callers omit the argument and bypass user
settings; change the init signature in IntersectionCallout (init(_: _:_:_:_:_))
to remove the default so includePrefixSound is a required Bool, then update the
call sites in RouteGuidanceGenerator and TourGenerator (the places using the
4-arg initializer) to pass the explicit user preference (the Sound Effects
setting) when constructing IntersectionCallout so prefix sounds respect the
user's setting.

In `@apps/ios/GuideDogs/Code/Visual` UI/Views/Settings.storyboard:
- Line 438: Two storyboard accessibility entries still use the generic label
"Streets and Places" for callout rows; find the XML elements with the
accessibilityConfiguration attribute whose label is "Streets and Places" and
replace each label with a concise, descriptive VoiceOver string that matches the
actual control purpose (e.g., "Enable street callouts" or "Enable place
callouts" or whatever the control toggles). Update both occurrences so VoiceOver
announces the specific function for those switches instead of the incorrect
generic label.

---

Nitpick comments:
In `@apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift`:
- Around line 434-450: Add notifications in the setters for
calloutSoundEffectsEnabled and calloutPausesEnabled similar to the pattern used
by automaticCalloutsEnabled and shakeCalloutsEnabled: after calling
userDefaults.set(...), post the same NotificationCenter notification those other
settings use so observers can react to changes (use the same Notification.Name
constant/pattern those properties use).

In `@apps/ios/GuideDogs/Code/Behaviors/Default/IntersectionGenerator.swift`:
- Around line 140-156: IntersectionGenerator should accept a settings provider
like AutoCalloutGenerator does: add a property (e.g., settings:
AutoCalloutSettingsProvider) initialized via the IntersectionGenerator
initializer and replace any direct uses of
SettingsContext.shared.calloutSoundEffectsEnabled in methods (notably where
callout is created and where event.geocodedLocation.buildCallout(...) is called)
with settings.calloutSoundEffectsEnabled; update any callers to pass the
concrete settings provider (or a mock in tests) so behavior remains the same but
becomes testable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5a04826d-af66-4823-bc4b-f18992143891

📥 Commits

Reviewing files that changed from the base of the PR and between 1130855 and 84a7d41.

📒 Files selected for processing (11)
  • apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings
  • apps/ios/GuideDogs/Code/App/Settings/AutoCalloutSettingsProvider.swift
  • apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift
  • apps/ios/GuideDogs/Code/Behaviors/Default/AutoCalloutGenerator.swift
  • apps/ios/GuideDogs/Code/Behaviors/Default/BeaconCalloutGenerator.swift
  • apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/DestinationCallout.swift
  • apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/IntersectionCallout.swift
  • apps/ios/GuideDogs/Code/Behaviors/Default/IntersectionGenerator.swift
  • apps/ios/GuideDogs/Code/Visual UI/Controls/Settings/CalloutSettingsCellView.swift
  • apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift
  • apps/ios/GuideDogs/Code/Visual UI/Views/Settings.storyboard

}

init(_ calloutOrigin: CalloutOrigin, _ entityKey: String, _ causedAudioDisabled: Bool = false) {
init(_ calloutOrigin: CalloutOrigin, _ entityKey: String, _ causedAudioDisabled: Bool = false, _ includePrefixSound: Bool = true) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Require explicit includePrefixSound for destination callouts.

Line 56 defaults to true, so callers can omit the new setting. PreviewGenerator.swift (Line 100) currently does that, which keeps preview prefix sounds enabled regardless of the Sound Effects toggle.

Suggested fix
-    init(_ calloutOrigin: CalloutOrigin, _ entityKey: String, _ causedAudioDisabled: Bool = false, _ includePrefixSound: Bool = true) {
+    init(_ calloutOrigin: CalloutOrigin, _ entityKey: String, _ causedAudioDisabled: Bool = false, _ includePrefixSound: Bool) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/DestinationCallout.swift`
at line 56, The initializer for DestinationCallout currently has a defaulted
parameter includePrefixSound = true allowing callers to omit it; remove the
default so the init signature becomes init(_ calloutOrigin: CalloutOrigin, _
entityKey: String, _ causedAudioDisabled: Bool = false, _ includePrefixSound:
Bool) to force callers to pass the setting explicitly, then update all call
sites (notably where PreviewGenerator constructs DestinationCallout) to pass the
correct includePrefixSound value based on the Sound Effects toggle or preview
behavior.

}

init(_ calloutOrigin: CalloutOrigin, _ intersectionKey: String, _ isRoundabout: Bool = false, _ userHeading: CLLocationDirection) {
init(_ calloutOrigin: CalloutOrigin, _ intersectionKey: String, _ isRoundabout: Bool = false, _ userHeading: CLLocationDirection, _ includePrefixSound: Bool = true) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make includePrefixSound explicit to prevent settings bypass.

Line 42 sets a default true, which lets call sites skip passing the user preference. RouteGuidanceGenerator.swift (Line 209) and TourGenerator.swift (Line 217) still use the 4-arg initializer, so intersection prefix sounds stay enabled there even when Sound Effects is off.

Suggested fix
-    init(_ calloutOrigin: CalloutOrigin, _ intersectionKey: String, _ isRoundabout: Bool = false, _ userHeading: CLLocationDirection, _ includePrefixSound: Bool = true) {
+    init(_ calloutOrigin: CalloutOrigin, _ intersectionKey: String, _ isRoundabout: Bool = false, _ userHeading: CLLocationDirection, _ includePrefixSound: Bool) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ios/GuideDogs/Code/Behaviors/Default/Callouts/IntersectionCallout.swift`
at line 42, The initializer for IntersectionCallout currently has a default true
for includePrefixSound which lets callers omit the argument and bypass user
settings; change the init signature in IntersectionCallout (init(_: _:_:_:_:_))
to remove the default so includePrefixSound is a required Bool, then update the
call sites in RouteGuidanceGenerator and TourGenerator (the places using the
4-arg initializer) to pass the explicit user preference (the Sound Effects
setting) when constructing IntersectionCallout so prefix sounds respect the
user's setting.

<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="aCR-Yf-X9R">
<rect key="frame" x="318" y="6" width="59" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Streets and Places"/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix switch accessibility labels for the new callout rows.

Lines 438 and 486 still use "Streets and Places", which doesn’t describe these controls. This will read incorrectly in VoiceOver.

Suggested fix
-                                            <accessibility key="accessibilityConfiguration" label="Streets and Places"/>
+                                            <accessibility key="accessibilityConfiguration" label="Sound Effects"/>
...
-                                            <accessibility key="accessibilityConfiguration" label="Streets and Places"/>
+                                            <accessibility key="accessibilityConfiguration" label="Delays"/>

Also applies to: 486-486

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ios/GuideDogs/Code/Visual` UI/Views/Settings.storyboard at line 438, Two
storyboard accessibility entries still use the generic label "Streets and
Places" for callout rows; find the XML elements with the
accessibilityConfiguration attribute whose label is "Streets and Places" and
replace each label with a concise, descriptive VoiceOver string that matches the
actual control purpose (e.g., "Enable street callouts" or "Enable place
callouts" or whatever the control toggles). Update both occurrences so VoiceOver
announces the specific function for those switches instead of the incorrect
generic label.

@RDMurray

RDMurray commented May 2, 2026

Copy link
Copy Markdown
Contributor

@MBtheOtaku I take it this replaces #247 ? Please could you close #247 if so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants