Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,18 @@
/* Button, Allow Callouts */
"callouts.allow_callouts" = "Allow Callouts";

/* Toggle label for enabling/disabling callout sound effects */
"callouts.sound_effects" = "Sound Effects";

/* Toggle description for enabling/disabling callout sound effects */
"callouts.sound_effects.info" = "Sounds that play before spoken callouts";

/* Toggle label for enabling/disabling pauses between automatic callouts */
"callouts.delays" = "Delays";

/* Toggle description for enabling/disabling pauses between automatic callouts */
"callouts.delays.info" = "Short pauses between automatic callouts";

/* Callouts Title, Places and Landmarks */
"callouts.places_and_landmarks" = "Places and Landmarks";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

protocol AutoCalloutSettingsProvider: AnyObject {
var automaticCalloutsEnabled: Bool { get set }
var calloutSoundEffectsEnabled: Bool { get set }
var calloutDelaysEnabled: Bool { get set }
var placeSenseEnabled: Bool { get set }
var landmarkSenseEnabled: Bool { get set }
var mobilitySenseEnabled: Bool { get set }
Expand Down
22 changes: 22 additions & 0 deletions apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class SettingsContext {
fileprivate static let useOldBeacon = "GDASettingsUseOldBeacon"
fileprivate static let playBeaconStartEndMelody = "GDAPlayBeaconStartEndMelody"
fileprivate static let automaticCalloutsEnabled = "GDASettingsAutomaticCalloutsEnabled"
fileprivate static let calloutSoundEffectsEnabled = "GDASettingsCalloutSoundEffectsEnabled"
fileprivate static let calloutDelaysEnabled = "GDASettingsCalloutDelaysEnabled"
fileprivate static let shakeCalloutsEnabled = "GDASettingsShakeCalloutsEnabled"
fileprivate static let sensePlace = "GDASettingsPlaceSenseEnabled"
fileprivate static let senseLandmark = "GDASettingsLandmarkSenseEnabled"
Expand Down Expand Up @@ -99,6 +101,8 @@ class SettingsContext {
Keys.useOldBeacon: false,
Keys.playBeaconStartEndMelody: false,
Keys.automaticCalloutsEnabled: true,
Keys.calloutSoundEffectsEnabled: true,
Keys.calloutDelaysEnabled: true,
Keys.shakeCalloutsEnabled: false,
Keys.sensePlace: true,
Keys.senseLandmark: true,
Expand Down Expand Up @@ -425,6 +429,24 @@ extension SettingsContext: AutoCalloutSettingsProvider {
NotificationCenter.default.post(name: .automaticCalloutsEnabledChanged, object: self, userInfo: [Keys.enabled: newValue])
}
}

var calloutSoundEffectsEnabled: Bool {
get {
return userDefaults.bool(forKey: Keys.calloutSoundEffectsEnabled)
}
set(newValue) {
userDefaults.set(newValue, forKey: Keys.calloutSoundEffectsEnabled)
}
}

var calloutDelaysEnabled: Bool {
get {
return userDefaults.bool(forKey: Keys.calloutDelaysEnabled)
}
set(newValue) {
userDefaults.set(newValue, forKey: Keys.calloutDelaysEnabled)
}
}

var shakeCalloutsEnabled: Bool {
get {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ class AutoCalloutGenerator: AutomaticGenerator, ManualGenerator {
// MARK: - Private Constants

private let inVehicleBeaconUpdateDistance: CLLocationDistance = 1000.0 // meters
private let calloutDelay = 0.75
private let defaultCalloutDelay: TimeInterval = 0.75
private var calloutDelay: TimeInterval? {
return settings.calloutDelaysEnabled ? defaultCalloutDelay : nil
}

// MARK: - Private Properties

Expand Down Expand Up @@ -435,7 +438,7 @@ class AutoCalloutGenerator: AutomaticGenerator, ManualGenerator {

guard let dataView = spatialData.getDataView(for: location, searchDistance: context.searchDistance) else {
let callouts = filterAnnounceablePOIs(prioritizedPOIs, near: location, context: context) {
return POICallout(.auto, poi: $0, location: location)
return POICallout(.auto, poi: $0, location: location, includePrefixSound: settings.calloutSoundEffectsEnabled)
}

if callouts.isEmpty {
Expand All @@ -452,11 +455,11 @@ class AutoCalloutGenerator: AutomaticGenerator, ManualGenerator {

// Get the POIs sorted by distance, and filtered for duplicate names
let prioritizedCallouts = filterAnnounceablePOIs(prioritizedPOIs, near: location, context: context) {
return POICallout(.auto, poi: $0, location: location)
return POICallout(.auto, poi: $0, location: location, includePrefixSound: settings.calloutSoundEffectsEnabled)
}

let defaultCallouts = !settings.automaticCalloutsEnabled ? [] : filterAnnounceablePOIs(dataView.pois, near: location, context: context) {
return POICallout(.auto, key: $0.key, location: location)
return POICallout(.auto, key: $0.key, location: location, includePrefixSound: settings.calloutSoundEffectsEnabled)
}

if defaultCallouts.count + prioritizedCallouts.count == 0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ class BeaconCalloutGenerator: AutomaticGenerator, ManualGenerator {
// MARK: - Private Constants

private let inVehicleBeaconUpdateDistance: CLLocationDistance = 1000.0 // meters
private let calloutDelay: TimeInterval = 0.75
private let defaultCalloutDelay: TimeInterval = 0.75
private var calloutDelay: TimeInterval? {
return settings.calloutDelaysEnabled ? defaultCalloutDelay : nil
}

// MARK: - Private Properties

Expand Down Expand Up @@ -100,7 +103,7 @@ class BeaconCalloutGenerator: AutomaticGenerator, ManualGenerator {
func handle(event: UserInitiatedEvent, verbosity: Verbosity) -> HandledEventAction? {
switch event {
case let event as BeaconCalloutEvent:
return .playCallouts(CalloutGroup([DestinationCallout(.auto, event.beaconId)], action: .interruptAndClear, logContext: event.logContext))
return .playCallouts(CalloutGroup([DestinationCallout(.auto, event.beaconId, includePrefixSound: settings.calloutSoundEffectsEnabled)], action: .interruptAndClear, logContext: event.logContext))

case let event as BeaconChangedEvent:
guard settings.automaticCalloutsEnabled else {
Expand Down Expand Up @@ -302,7 +305,7 @@ class BeaconCalloutGenerator: AutomaticGenerator, ManualGenerator {
configureDestinationUpdates()

// Build the destination callout
return [DestinationCallout(origin, key, causedAudioDisable)]
return [DestinationCallout(origin, key, causedAudioDisable, includePrefixSound: settings.calloutSoundEffectsEnabled)]
}

// MARK: - Helper Methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct DestinationCallout: POICalloutProtocol {
return "destination"
}

let includePrefixSound = true
let includePrefixSound: Bool

var prefixSound: Sound? {
return GlyphSound(.startJourney)
Expand Down Expand Up @@ -52,10 +52,11 @@ struct DestinationCallout: POICalloutProtocol {
return marker?.getPOI()
}

init(_ calloutOrigin: CalloutOrigin, _ entityKey: String, _ causedAudioDisabled: Bool = false) {
init(_ calloutOrigin: CalloutOrigin, _ entityKey: String, _ causedAudioDisabled: Bool = false, includePrefixSound: Bool = true) {
self.origin = calloutOrigin
self.entityKey = entityKey
self.causedAudioDisabled = causedAudioDisabled
self.includePrefixSound = includePrefixSound
}
Comment on lines +55 to 60

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

Apply the sound-effects toggle to the beacon-geofence earcon too.

The new flag is stored on the callout, but Line 94 still plays GlyphSound(.beaconFound) unconditionally when causedAudioDisabled is true. With Sound Effects turned off, that automatic destination path will still emit an earcon.

Suggested fix
         if causedAudioDisabled {
-            let earcon = GlyphSound(.beaconFound)
             var text = GDLocalizedString("beacon.beacon_location_within_audio_beacon_muted", formattedDistance)
             // Append suggestion to launch NaviLens if available at location
             if (poi != nil) && LocationDetail(entity: poi!).source.hasNaviLens {
                 text += " " + GDLocalizedString("beacon.suggest_navilens")
             }
             let tts = TTSSound(text, at: markerLocation)
+
+            guard includePrefixSound else {
+                return Sounds(tts)
+            }
+
+            let earcon = GlyphSound(.beaconFound)
             
             guard let layered = LayeredSound(earcon, tts) else {
                 return Sounds([earcon, tts])
             }

Also applies to: 66-115

🤖 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`
around lines 55 - 60, The beacon/geofence earcon is still played
unconditionally; update the playback sites (within DestinationCallout where
GlyphSound(.beaconFound) is invoked, around the logic covering lines ~66–115) to
respect the callout flags by only playing the earcon when causedAudioDisabled is
false (and if applicable also respect includePrefixSound) — i.e., wrap the
GlyphSound(.beaconFound) playback call in a conditional that checks
!self.causedAudioDisabled (and includePrefixSound when that flag controls prefix
sounds) so the sound-effects toggle on the callout is honored.


func hasSameEntity(_ rhs: POICallout) -> Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct IntersectionCallout: CalloutProtocol {
return "intersection"
}

let includePrefixSound = true
let includePrefixSound: Bool

var prefixSound: Sound? {
return GlyphSound(SuperCategory.intersections.glyph)
Expand All @@ -38,11 +38,12 @@ struct IntersectionCallout: CalloutProtocol {
return SpatialDataCache.intersectionByKey(key)
}

init(_ calloutOrigin: CalloutOrigin, _ intersectionKey: String, _ isRoundabout: Bool = false, _ userHeading: CLLocationDirection) {
init(_ calloutOrigin: CalloutOrigin, _ intersectionKey: String, _ isRoundabout: Bool = false, _ userHeading: CLLocationDirection, includePrefixSound: Bool = true) {
self.origin = calloutOrigin
self.key = intersectionKey
self.isRoundabout = isRoundabout
self.heading = userHeading
self.includePrefixSound = includePrefixSound
}

func sounds(for location: CLLocation?, isRepeat: Bool, automotive: Bool = false) -> Sounds {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@ class IntersectionGenerator: AutomaticGenerator {
return .noAction

case let event as IntersectionArrivalEvent:
let callout = IntersectionCallout(.intersection, event.key, event.isRoundabout, event.heading)
let callout = IntersectionCallout(.intersection,
event.key,
event.isRoundabout,
event.heading,
includePrefixSound: SettingsContext.shared.calloutSoundEffectsEnabled)

GDATelemetry.track("callout", with: ["context": "intersection.arrival",
"type": callout.logCategory,
Expand All @@ -146,7 +150,9 @@ class IntersectionGenerator: AutomaticGenerator {
return .playCallouts(CalloutGroup([callout], action: .interruptAndClear, logContext: "intersections"))

case let event as IntersectionDepartureEvent:
let callout = event.geocodedLocation.buildCallout(origin: .intersection, sound: true, useClosestRoadIfAvailable: true)
let callout = event.geocodedLocation.buildCallout(origin: .intersection,
sound: SettingsContext.shared.calloutSoundEffectsEnabled,
useClosestRoadIfAvailable: true)
return .playCallouts(CalloutGroup([callout], action: .enqueue, logContext: "intersections"))

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ protocol CalloutSettingsCellViewDelegate: AnyObject {
}

internal enum CalloutSettingCellType {
<<<<<<< Updated upstream
case all, poi, mobility, beacon, shake
=======
case all, soundEffects, delays, poi, beacon, shake
case transportation
case intersection
case safety
>>>>>>> Stashed changes
Comment on lines 15 to +23

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e

file='apps/ios/GuideDogs/Code/Visual UI/Controls/Settings/CalloutSettingsCellView.swift'
controller='apps/ios/GuideDogs/Code/Visual UI/View Controllers/Settings/SettingsViewController.swift'

echo "Conflict markers in the cell view:"
rg -n '^(<<<<<<<|=======|>>>>>>>)' "$file"

echo
echo "Callout setting cases referenced by the cell view and controller:"
rg -n '\.(all|soundEffects|delays|poi|mobility|transportation|safety|intersection|beacon|shake)\b' "$file" "$controller"

Repository: soundscape-community/soundscape

Length of output: 6958


🏁 Script executed:

cat -n 'apps/ios/GuideDogs/Code/Visual UI/Controls/Settings/CalloutSettingsCellView.swift' | sed -n '10,130p'

Repository: soundscape-community/soundscape

Length of output: 5050


Finish the merge resolution and synchronize the enum with its switches.

The file contains unresolved merge conflicts at lines 16–23 (enum), 42–49 (read switch), and 88–98 (write switch). After resolution, line 53 references .mobility, while SettingsViewController.swift routes .transportation, .safety, and .intersection as separate cell types (lines 189–195). The enum definition and both switch statements must be aligned to match the controller's expectations. The file will not compile until conflicts are resolved and cases are synchronized.

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

In `@apps/ios/GuideDogs/Code/Visual`
UI/Controls/Settings/CalloutSettingsCellView.swift around lines 15 - 23, The
CalloutSettingCellType enum still contains merge markers and is out of sync with
SettingsViewController; replace the conflicted enum with a final set of cases
that matches the controller (include all, soundEffects, delays, poi, beacon,
shake, transportation, intersection, safety and remove/replace .mobility), then
update both switch statements that read and write settings (the "read" switch
and the "write" switch that reference CalloutSettingCellType) to handle every
new case and map any old .mobility usages to the appropriate new case (likely
.transportation) so all switch arms are exhaustive and compilation
warnings/errors are resolved; ensure switch defaults are removed or adjusted so
SettingsViewController’s routing (.transportation, .safety, .intersection) is
honored.

}

class CalloutSettingsCellView: UITableViewCell {
Expand All @@ -32,7 +39,14 @@ class CalloutSettingsCellView: UITableViewCell {
switch type {
case .all:
settingSwitch.isOn = SettingsContext.shared.automaticCalloutsEnabled
<<<<<<< Updated upstream
return
=======
case .soundEffects:
settingSwitch.isOn = SettingsContext.shared.calloutSoundEffectsEnabled
case .delays:
settingSwitch.isOn = SettingsContext.shared.calloutDelaysEnabled
>>>>>>> Stashed changes
case .poi:
settingSwitch.isOn = SettingsContext.shared.placeSenseEnabled
return
Expand Down Expand Up @@ -71,8 +85,17 @@ class CalloutSettingsCellView: UITableViewCell {
case .all:
SettingsContext.shared.automaticCalloutsEnabled = isOn
GDATelemetry.track("settings.allow_callouts", value: isOn.description)
<<<<<<< Updated upstream
return

=======
case .soundEffects:
SettingsContext.shared.calloutSoundEffectsEnabled = isOn
GDATelemetry.track("settings.callout_sound_effects", value: isOn.description)
case .delays:
SettingsContext.shared.calloutDelaysEnabled = isOn
GDATelemetry.track("settings.callout_delays", value: isOn.description)
>>>>>>> Stashed changes
case .poi:
// Places, Landmark, and Information Senses
SettingsContext.shared.placeSenseEnabled = isOn
Expand Down
Loading
Loading