Skip to content

Commit

Permalink
"Enter Vicinity Distance" setting (#102)
Browse files Browse the repository at this point in the history
* "Enter Vicinity Distance" setting

Adds a setting to configure the distance threshold for stopping beacon audio.

Fixes #78
  • Loading branch information
steinbro authored Sep 12, 2024
1 parent cd33f8c commit 6a6012e
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 12 deletions.
4 changes: 4 additions & 0 deletions apps/ios/GuideDogs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,7 @@
B9EE98081E3656B7007ADBED /* UIDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9EE98071E3656B7007ADBED /* UIDeviceManager.swift */; };
B9F0F96B1E10A40700F32F70 /* BaseTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F0F96A1E10A40700F32F70 /* BaseTableViewController.swift */; };
B9F749A21F16153900DC10C6 /* CoreLocation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9F749A11F16153900DC10C6 /* CoreLocation+Extensions.swift */; };
BDA963AE2C69483300261EF2 /* SettingStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA963AD2C69483300261EF2 /* SettingStepper.swift */; };
C3060705205C544E00C39489 /* AddressGeocoderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3060704205C544E00C39489 /* AddressGeocoderProtocol.swift */; };
C306DF8522F0D71300248E9F /* CompoundFilterPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C306DF8422F0D71300248E9F /* CompoundFilterPredicate.swift */; };
C30DBD2023A2C19400082B27 /* SearchResultsUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30DBD1F23A2C19400082B27 /* SearchResultsUpdater.swift */; };
Expand Down Expand Up @@ -1648,6 +1649,7 @@
B9EE98071E3656B7007ADBED /* UIDeviceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIDeviceManager.swift; path = Code/Devices/UIDeviceManager.swift; sourceTree = "<group>"; };
B9F0F96A1E10A40700F32F70 /* BaseTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseTableViewController.swift; path = "Code/Visual UI/View Controllers/BaseTableViewController.swift"; sourceTree = "<group>"; };
B9F749A11F16153900DC10C6 /* CoreLocation+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "CoreLocation+Extensions.swift"; path = "Code/App/Framework Extensions/Geo Extensions/CoreLocation+Extensions.swift"; sourceTree = "<group>"; };
BDA963AD2C69483300261EF2 /* SettingStepper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingStepper.swift; sourceTree = "<group>"; };
C3060704205C544E00C39489 /* AddressGeocoderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AddressGeocoderProtocol.swift; path = Code/Generators/Geocoding/Protocols/AddressGeocoderProtocol.swift; sourceTree = "<group>"; };
C306DF8422F0D71300248E9F /* CompoundFilterPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CompoundFilterPredicate.swift; path = Code/Data/Models/Helpers/Filter/CompoundFilterPredicate.swift; sourceTree = "<group>"; };
C30DBD1F23A2C19400082B27 /* SearchResultsUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsUpdater.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2605,6 +2607,7 @@
2896378326D7099B001694C0 /* Beacon */ = {
isa = PBXGroup;
children = (
BDA963AD2C69483300261EF2 /* SettingStepper.swift */,
2832D36526D54DE60052EE47 /* BeaconSelectionView.swift */,
2832D36726D54E0B0052EE47 /* BeaconSelectionHostViewController.swift */,
2896378826D70D3F001694C0 /* BeaconOptionCell.swift */,
Expand Down Expand Up @@ -5722,6 +5725,7 @@
62CE14CF25C0DEC3001CBC0B /* HeadphoneCalibration.swift in Sources */,
C37E33D52368DBA60033D640 /* NotificationManager.swift in Sources */,
C317F26523722ECF000579BA /* NotificationObserver.swift in Sources */,
BDA963AE2C69483300261EF2 /* SettingStepper.swift in Sources */,
C317F26923722F1A000579BA /* NotificationObserverDelegate.swift in Sources */,
62B11A3727ACAAC50094FE66 /* RoundedBackground.swift in Sources */,
C37E33C423677EC00033D640 /* AlertType.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,8 @@
/* */
"beacon.settings_title" = "Beacon Settings";

/* */
"beacon.settings.vicinity" = "Enter Vicinity Distance";

/* */
"beacon.settings.style" = "Audio Styles";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,10 @@
/* Title of the settings screen for beacons. This screen allows users to configure the way the audio beacon sounds. See Terms for the definition of "beacon". */
"beacon.settings_title" = "Beacon Settings";


/* Title of a section in the beacon settings page where the user can select the distance threshold when a beacon automatically stops playing. */
"beacon.settings.vicinity" = "Enter Vicinity Distance";

/* Title of a section in the beacon settings page where the user can select a style for their beacon. The various styles mainly differ in the sounds that are played for the beacon, though there are two styles that include some haptics as well. */
"beacon.settings.style" = "Audio Styles";

Expand Down
28 changes: 25 additions & 3 deletions apps/ios/GuideDogs/Code/App/Settings/SettingsContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import Foundation
import AVFoundation
import CoreLocation

extension Notification.Name {
static let automaticCalloutsEnabledChanged = Notification.Name("GDAAutomaticCalloutsChanged")
Expand Down Expand Up @@ -53,6 +54,8 @@ class SettingsContext {
fileprivate static let previewIntersectionsIncludeUnnamedRoads = "GDASettingsPreviewIntersectionsIncludeUnnamedRoads"
fileprivate static let audioSessionMixesWithOthers = "GDAAudioSessionMixesWithOthers"
fileprivate static let markerSortStyle = "GDAMarkerSortStyle"
fileprivate static let leaveImmediateVicinityDistance = "GDALeaveImmediateVicinityDistance"
fileprivate static let enterImmediateVicinityDistance = "GDAEnterImmediateVicinityDistance"

fileprivate static let ttsGain = "GDATTSAudioGain"
fileprivate static let beaconGain = "GDABeaconAudioGain"
Expand Down Expand Up @@ -102,7 +105,9 @@ class SettingsContext {
Keys.senseDestination: true,
Keys.previewIntersectionsIncludeUnnamedRoads: false,
Keys.audioSessionMixesWithOthers: true,
Keys.markerSortStyle: SortStyle.distance.rawValue
Keys.markerSortStyle: SortStyle.distance.rawValue,
Keys.leaveImmediateVicinityDistance: 30.0,
Keys.enterImmediateVicinityDistance: 15.0
])

resetLocaleIfNeeded()
Expand All @@ -117,7 +122,7 @@ class SettingsContext {
}

// MARK: Properties

var appUseCount: Int {
get {
return userDefaults.integer(forKey: Keys.appUseCount)
Expand Down Expand Up @@ -315,7 +320,7 @@ class SettingsContext {
}

// MARK: Push Notifications

var apnsDeviceToken: Data? {
get {
return userDefaults.data(forKey: Keys.apnsDeviceToken)
Expand Down Expand Up @@ -366,6 +371,23 @@ class SettingsContext {
}
}

var leaveImmediateVicinityDistance: CLLocationDistance {
get {
return userDefaults.double(forKey: Keys.leaveImmediateVicinityDistance) as CLLocationDistance
}
set {
userDefaults.set(newValue, forKey: Keys.leaveImmediateVicinityDistance)
}
}

var enterImmediateVicinityDistance: CLLocationDistance {
get {
return userDefaults.double(forKey: Keys.enterImmediateVicinityDistance) as CLLocationDistance
}
set {
userDefaults.set(newValue, forKey: Keys.enterImmediateVicinityDistance)
}
}
}

extension SettingsContext: AutoCalloutSettingsProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class BeaconCalloutGenerator: AutomaticGenerator, ManualGenerator {
}

// Don't do a location update for the destination if we have already entered the immediate vicinity
guard destination.distanceToClosestLocation(from: location) > DestinationManager.EnterImmediateVicinityDistance else {
guard destination.distanceToClosestLocation(from: location) > SettingsContext.shared.enterImmediateVicinityDistance else {
return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ struct DestinationCallout: POICalloutProtocol {
return Sounds(sounds)

case .beaconGeofence:
let formattedDistance = LanguageFormatter.formattedDistance(from: DestinationManager.EnterImmediateVicinityDistance)
let formattedDistance = LanguageFormatter.formattedDistance(from: SettingsContext.shared.enterImmediateVicinityDistance)

// Inform the user why the audio beacon has stopped
if causedAudioDisabled {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ struct PreviewGenerator<DecisionPoint: RootedPreviewGraph>: ManualGenerator {
var callouts: [CalloutProtocol] = []

if event.arrived {
let formattedDistance = LanguageFormatter.formattedDistance(from: DestinationManager.EnterImmediateVicinityDistance)
let formattedDistance = LanguageFormatter.formattedDistance(from: SettingsContext.shared.enterImmediateVicinityDistance)

callouts.append(GenericCallout(.preview, description: "arrived at beacon (in preview)") { (_, _, _) -> [Sound] in
let earcon = GlyphSound(.beaconFound)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ enum DestinationManagerError: Error {

class DestinationManager: DestinationManagerProtocol {

static let LeaveImmediateVicinityDistance: CLLocationDistance = 30.0
static let EnterImmediateVicinityDistance: CLLocationDistance = 15.0

// MARK: Notification Keys

struct Keys {
Expand Down Expand Up @@ -614,10 +611,10 @@ class DestinationManager: DestinationManagerProtocol {

let distance = origin.distanceToClosestLocation(from: location)

if isWithinGeofence && distance >= DestinationManager.LeaveImmediateVicinityDistance {
if isWithinGeofence && distance >= SettingsContext.shared.leaveImmediateVicinityDistance {
// Left immediate vicinity
return false
} else if !isWithinGeofence && distance <= DestinationManager.EnterImmediateVicinityDistance {
} else if !isWithinGeofence && distance <= SettingsContext.shared.enterImmediateVicinityDistance {
// Entered immediate vicinity
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ struct BeaconSelectionView: View {
@State var isPresented: Bool = false
@State var selectedBeaconKey: String
@State var areMelodiesEnabled: Bool
@State var enterImmediateVicinityDistance: Double

let initialBeacon: String
let initialMelodies: Bool

init() {
_selectedBeaconKey = State(initialValue: SettingsContext.shared.selectedBeacon)
_areMelodiesEnabled = State(initialValue: SettingsContext.shared.playBeaconStartAndEndMelodies)
_enterImmediateVicinityDistance = State(initialValue: SettingsContext.shared.enterImmediateVicinityDistance)
initialBeacon = SettingsContext.shared.selectedBeacon
initialMelodies = SettingsContext.shared.playBeaconStartAndEndMelodies
}
Expand Down Expand Up @@ -52,7 +54,20 @@ struct BeaconSelectionView: View {
SettingsContext.shared.playBeaconStartAndEndMelodies = areMelodiesEnabled
beaconDemo.play(styleChanged: true)
})


TableHeaderCell(text: GDLocalizedString("beacon.settings.vicinity"))

SettingStepper(
value: $enterImmediateVicinityDistance,
unitsLocalization: "distance.format.meters",
stepSize: 5.0,
minValue: 0.0,
maxValue: 50.0
)
.onChange(of: enterImmediateVicinityDistance, perform: { _ in
SettingsContext.shared.enterImmediateVicinityDistance = enterImmediateVicinityDistance
})

TableHeaderCell(text: GDLocalizedString("beacon.settings.style"))

ForEach(BeaconOption.allAvailableCases(for: .standard)) { details in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// SettingStepper.swift
// Soundscape
//
// Created by Daniel W. Steinbrook on 8/11/24.
// Copyright © 2024 Soundscape community. All rights reserved.
//

import SwiftUI

/// Defines a stepper (increment/decrement buttons) that can be used for settings like Enter Vicinity Distance.
/// Takes a step size, min, max, and localization key for printing the value with units..
/// `unitsLocalization` should be a localization key like "distance.format.meters".
struct SettingStepper: View {
@Binding var value: Double
private let unitsLocalization: String
private let stepSize: Double
private let minValue: Double
private let maxValue: Double

init(value: Binding<Double>, unitsLocalization: String, stepSize: Double, minValue: Double, maxValue: Double) {
self._value = value
self.unitsLocalization = unitsLocalization
self.stepSize = stepSize
self.minValue = minValue
self.maxValue = maxValue
}

// Increment and Decrement actions
private func increment() {
let newValue = value + stepSize
value = min(max(newValue, minValue), maxValue)
}

private func decrement() {
let newValue = value - stepSize
value = min(max(newValue, minValue), maxValue)
}

var body: some View {
VStack {
/// We don't use the native `Stepper` because the increment/decrement
/// controls can't be styled, and the defaults are low contrast.
HStack {
// truncate `value` at the decimal point
Text(GDLocalizedString(unitsLocalization, String(format: "%.0f", value)))
.foregroundColor(.primaryForeground)
.font(.body)
.lineLimit(nil)

Spacer()

Button(action: decrement) {
Text("-")
.font(.title)
.frame(width: 44, height: 30)
.background(Color.gray.opacity(0.3))
.foregroundColor(.white)
.cornerRadius(8)
}
.accessibilityLabel(Text("Decrease"))
.disabled(value <= self.minValue)

Button(action: increment) {
Text("+")
.font(.title)
.frame(width: 44, height: 30)
.background(Color.gray.opacity(0.3))
.foregroundColor(.white)
.cornerRadius(8)
}
.accessibilityLabel(Text("Increase"))
.disabled(value >= self.maxValue)
}
.padding()
.background(Color.primaryBackground)
.accessibilityElement(children: .ignore)
.accessibilityValue(GDLocalizedString(unitsLocalization, String(format: "%.0f", value)))
.accessibilityAdjustableAction { direction in
switch direction {
case .increment:
increment()
case .decrement:
decrement()
@unknown default:
break
}
}
}
}
}

0 comments on commit 6a6012e

Please sign in to comment.