Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Enter Vicinity Distance" setting #102

Merged
merged 5 commits into from
Sep 12, 2024
Merged
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
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
}
}
}
}
}
Loading