Skip to content
Draft
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
40 changes: 38 additions & 2 deletions apps/ios/GuideDogs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
objects = {

/* Begin PBXBuildFile section */
E200000131C0000100C0FFEE /* InternalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E200000031C0000100C0FFEE /* InternalStorage.swift */; };
A100000931B3000100C0FFEE /* GPXRecordingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A100000831B3000100C0FFEE /* GPXRecordingTests.swift */; };
A100000231B3000100C0FFEE /* GPXRecordingModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A100000131B3000100C0FFEE /* GPXRecordingModels.swift */; };
A100000431B3000100C0FFEE /* GPXRecordingServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = A100000331B3000100C0FFEE /* GPXRecordingServices.swift */; };
A100000631B3000100C0FFEE /* GPXRecordingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A100000531B3000100C0FFEE /* GPXRecordingController.swift */; };
2809B80C2379E95B00F6C9AC /* calibration_success.wav in Resources */ = {isa = PBXBuildFile; fileRef = 2809B80B2379E95B00F6C9AC /* calibration_success.wav */; };
2809B80E2379E9A000F6C9AC /* connection_success.wav in Resources */ = {isa = PBXBuildFile; fileRef = 2809B80D2379E9A000F6C9AC /* connection_success.wav */; };
2809B8102379E9C700F6C9AC /* low_confidence.wav in Resources */ = {isa = PBXBuildFile; fileRef = 2809B80F2379E9C700F6C9AC /* low_confidence.wav */; };
Expand Down Expand Up @@ -775,6 +780,11 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
E200000031C0000100C0FFEE /* InternalStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InternalStorage.swift; path = "Code/App/Framework Extensions/Base Type Extensions/InternalStorage.swift"; sourceTree = "<group>"; };
A100000831B3000100C0FFEE /* GPXRecordingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPXRecordingTests.swift; sourceTree = "<group>"; };
A100000131B3000100C0FFEE /* GPXRecordingModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GPXRecordingModels.swift; path = "Code/GPX Recording/GPXRecordingModels.swift"; sourceTree = "<group>"; };
A100000331B3000100C0FFEE /* GPXRecordingServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GPXRecordingServices.swift; path = "Code/GPX Recording/GPXRecordingServices.swift"; sourceTree = "<group>"; };
A100000531B3000100C0FFEE /* GPXRecordingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = GPXRecordingController.swift; path = "Code/GPX Recording/GPXRecordingController.swift"; sourceTree = "<group>"; };
20EB7956C9AA4CB8A103128F /* NaviLensWakeOnForegroundTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NaviLensWakeOnForegroundTest.swift; sourceTree = "<group>"; };
2809B80B2379E95B00F6C9AC /* calibration_success.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = calibration_success.wav; path = Assets/Sounds/calibration_success.wav; sourceTree = "<group>"; };
2809B80D2379E9A000F6C9AC /* connection_success.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = connection_success.wav; path = Assets/Sounds/connection_success.wav; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1598,6 +1608,24 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
A100000731B3000100C0FFEE /* GPX Recording */ = {
isa = PBXGroup;
children = (
A100000831B3000100C0FFEE /* GPXRecordingTests.swift */,
);
path = "GPX Recording";
sourceTree = "<group>";
};
A100000031B3000100C0FFEE /* GPX Recording */ = {
isa = PBXGroup;
children = (
A100000131B3000100C0FFEE /* GPXRecordingModels.swift */,
A100000331B3000100C0FFEE /* GPXRecordingServices.swift */,
A100000531B3000100C0FFEE /* GPXRecordingController.swift */,
);
name = "GPX Recording";
sourceTree = "<group>";
};
2809B80A2379E8F100F6C9AC /* Calibration */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -3530,6 +3558,7 @@
914DEBD92A3CE7E6007B161C /* App */ = {
isa = PBXGroup;
children = (
A100000731B3000100C0FFEE /* GPX Recording */,
914DEBDB2A3CE85D007B161C /* Helpers */,
);
path = App;
Expand Down Expand Up @@ -4032,6 +4061,7 @@
C36E562A22A87E3B00E89C6D /* Base Type Extensions */ = {
isa = PBXGroup;
children = (
E200000031C0000100C0FFEE /* InternalStorage.swift */,
31CA500D21715F7700049ADF /* Array+Extensions.swift */,
C36E562B22A87E5300E89C6D /* Array+POI.swift */,
286A2AEF2256813400A74724 /* Data+Extensions.swift */,
Expand Down Expand Up @@ -4289,6 +4319,7 @@
D378E0DF1B3D97E200909E29 /* Code */ = {
isa = PBXGroup;
children = (
A100000031B3000100C0FFEE /* GPX Recording */,
D378E0E01B3D97E900909E29 /* App */,
568480871CB73E90004B4227 /* Visual UI */,
C3A5847322BAF8260014C55E /* Data */,
Expand Down Expand Up @@ -4732,6 +4763,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A100000931B3000100C0FFEE /* GPXRecordingTests.swift in Sources */,
915FF9F62ADE4BAF002B3690 /* AuthoredActivityContentTest.swift in Sources */,
914BAAFD2AD7483300CB2171 /* AudioEngineTest.swift in Sources */,
F100000131A2000100C0FFEE /* VoiceCatalogueTests.swift in Sources */,
Expand All @@ -4751,6 +4783,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E200000131C0000100C0FFEE /* InternalStorage.swift in Sources */,
A100000231B3000100C0FFEE /* GPXRecordingModels.swift in Sources */,
A100000431B3000100C0FFEE /* GPXRecordingServices.swift in Sources */,
A100000631B3000100C0FFEE /* GPXRecordingController.swift in Sources */,
C3A5847A22BAF9070014C55E /* DeviceHeadingProvider.swift in Sources */,
28BD33D723D8C98800566523 /* AudioEngineProtocol.swift in Sources */,
28A16136283D870D0081CFA4 /* GuidedTourAction.swift in Sources */,
Expand Down Expand Up @@ -5502,7 +5538,7 @@
BUNDLE_SPOKEN_NAME = "Soundscape Adhoc";
CLANG_ENABLE_MODULES = YES;
CLANG_STATIC_ANALYZER_MODE = deep;
CODE_SIGN_ENTITLEMENTS = GuideDogs/Assets/PropertyLists/SoundscapeDF.entitlements;
CODE_SIGN_ENTITLEMENTS = GuideDogs/Assets/PropertyLists/Soundscape.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2;
Expand Down Expand Up @@ -5864,7 +5900,7 @@
DEVELOPMENT_TEAM = X4H33NKGKY;
EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = YES;
ENABLE_BITCODE = NO;
FILE_SHARING_ENABLED = NO;
FILE_SHARING_ENABLED = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3808,3 +3808,43 @@

/* Describe feature for automatically reversing a route. Quotes written as \" */
"faq.tip.reverse_route" = "Selecting \"Start Reversed Route\" from the \"Route Details\" screen allows you to take a route in reverse, and it saves a copy of the route with the waypoints in reverse order. Note that \"Start Reversed Route\" will not save another copy of the same route if it's already saved.";

// MARK: GPX Recording

"gpx_recording.title" = "GPX Recording";
"gpx_recording.explanation" = "Record your journey as a GPX file. Recording continues while you use other parts of Soundscape and while the app is in the background. It pauses during Sleep or Snooze and resumes when Soundscape wakes.";
"gpx_recording.status" = "Status";
"gpx_recording.status.loading" = "Loading";
"gpx_recording.status.idle" = "Not recording";
"gpx_recording.status.starting" = "Starting";
"gpx_recording.status.recording" = "Recording";
"gpx_recording.status.paused" = "Paused during Sleep or Snooze";
"gpx_recording.status.awaiting_name" = "Ready to save";
"gpx_recording.status.saving" = "Saving";
"gpx_recording.status.recovered" = "Interrupted recording recovered";
"gpx_recording.point_count" = "%d recorded points";
"gpx_recording.start" = "Start recording";
"gpx_recording.stop" = "Stop recording";
"gpx_recording.saved" = "Saved recordings";
"gpx_recording.saved.empty" = "No saved recordings";
"gpx_recording.storage.icloud" = "iCloud Drive";
"gpx_recording.storage.local" = "On My iPhone";
"gpx_recording.share.hint" = "Shares this GPX recording";
"gpx_recording.name.title" = "Name Recording";
"gpx_recording.name.prompt" = "Enter a unique file name";
"gpx_recording.name" = "Recording name";
"gpx_recording.save" = "Save";
"gpx_recording.discard" = "Discard";
"gpx_recording.discard.confirm" = "Discard this recording? This cannot be undone.";
"gpx_recording.recover.save" = "Name and save recording";
"gpx_recording.save_options" = "iCloud save failed";
"gpx_recording.retry_icloud" = "Retry iCloud Drive";
"gpx_recording.save_local" = "Save On My iPhone";
"gpx_recording.error.no_points" = "No location points have been recorded yet.";
"gpx_recording.error.invalid_name" = "Enter a safe file name without slashes, colons, or a trailing period.";
"gpx_recording.error.duplicate_name" = "A recording with this name already exists.";
"gpx_recording.error.draft_unavailable" = "The protected recording draft could not be opened.";
"gpx_recording.error.icloud_unavailable" = "iCloud Drive is currently unavailable.";
"gpx_recording.error.download_timeout" = "The iCloud recording could not be downloaded in time. Please try again.";
"gpx_recording.error.share_unavailable" = "The sharing screen is currently unavailable.";
"gpx_recording.error.accessibility" = "Storage error: %@";
16 changes: 15 additions & 1 deletion apps/ios/GuideDogs/Assets/PropertyLists/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,21 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<true/>
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.services.soundscape</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>Soundscape</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>Any</string>
</dict>
</dict>
<key>UIFileSharingEnabled</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Soundscape uses Bluetooth LE to connect to accessories.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
Expand Down
12 changes: 11 additions & 1 deletion apps/ios/GuideDogs/Assets/PropertyLists/Soundscape.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@
<string>applinks:share.soundscape.services</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<array>
<string>iCloud.services.soundscape</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudDocuments</string>
</array>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>iCloud.services.soundscape</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>$(TeamIdentifierPrefix)$(CFBundleIdentifier)</string>
</dict>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//

Expand Down Expand Up @@ -39,7 +40,12 @@ class ExperimentManager {
weak var delegate: ExperimentManagerDelegate?

private lazy var directory: URL? = {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("Experiments")
do {
return try InternalStorage.directory(named: "Experiments")
} catch {
GDLogAppError("Unable to access experiment storage: \(error.localizedDescription)")
return nil
}
}()

private lazy var url: URL? = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//
// InternalStorage.swift
// Soundscape
//
// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//

import Foundation

enum InternalStorage {
private static let migrationQueue = DispatchQueue(label: "services.soundscape.internal-storage-migration")

static func directory(named name: String, fileManager: FileManager = .default) throws -> URL {
try migrationQueue.sync {
let destination = try applicationSupportDirectory(fileManager: fileManager)
.appendingPathComponent(name, isDirectory: true)
try migrateLegacyItem(named: name, to: destination, fileManager: fileManager)
try fileManager.createDirectory(at: destination, withIntermediateDirectories: true)
return destination
}
}

static func file(named name: String, fileManager: FileManager = .default) throws -> URL {
try migrationQueue.sync {
let destination = try applicationSupportDirectory(fileManager: fileManager)
.appendingPathComponent(name)
try migrateLegacyItem(named: name, to: destination, fileManager: fileManager)
return destination
}
}

private static func applicationSupportDirectory(fileManager: FileManager) throws -> URL {
guard let directory = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
throw CocoaError(.fileNoSuchFile)
}
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
return directory
}

private static func migrateLegacyItem(named name: String,
to destination: URL,
fileManager: FileManager) throws {
guard let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
return
}
let source = documents.appendingPathComponent(name)
guard fileManager.fileExists(atPath: source.path) else {
return
}
try mergeOrMove(source: source, destination: destination, fileManager: fileManager)
}

private static func mergeOrMove(source: URL,
destination: URL,
fileManager: FileManager) throws {
guard fileManager.fileExists(atPath: destination.path) else {
try fileManager.moveItem(at: source, to: destination)
return
}

let sourceIsDirectory = try source.resourceValues(forKeys: [.isDirectoryKey]).isDirectory == true
let destinationIsDirectory = try destination.resourceValues(forKeys: [.isDirectoryKey]).isDirectory == true
guard sourceIsDirectory, destinationIsDirectory else {
try fileManager.removeItem(at: source)
return
}

for child in try fileManager.contentsOfDirectory(at: source,
includingPropertiesForKeys: [.isDirectoryKey]) {
try mergeOrMove(source: child,
destination: destination.appendingPathComponent(child.lastPathComponent),
fileManager: fileManager)
}
try fileManager.removeItem(at: source)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Soundscape
//
// Copyright (c) Microsoft Corporation.
// Copyright (c) Soundscape Community Contributors.
// Licensed under the MIT License.
//

Expand Down Expand Up @@ -39,10 +40,10 @@ extension GPXBounds {
if location.coordinate.latitude > maxLatitude {
maxLatitude = location.coordinate.latitude
}
if location.coordinate.latitude < minLongitude {
if location.coordinate.longitude < minLongitude {
minLongitude = location.coordinate.longitude
}
if location.coordinate.latitude > maxLongitude {
if location.coordinate.longitude > maxLongitude {
maxLongitude = location.coordinate.longitude
}
}
Expand Down Expand Up @@ -72,19 +73,24 @@ extension GPXRoot {
}

class func createGPX(withTrackLocations trackLocations: [GPXLocation]) -> GPXRoot {
return createGPX(withTrackLocationSegments: [trackLocations])
}

class func createGPX(withTrackLocationSegments trackLocationSegments: [[GPXLocation]]) -> GPXRoot {
let root = GPXRoot.defaultRoot()
root.metadata?.bounds = GPXBounds(with: trackLocations)

let trackSegment = GPXTrackSegment()
for gpxLocation in trackLocations {
trackSegment.add(trackpoint: GPXTrackPoint(with: gpxLocation))
}

let allLocations = trackLocationSegments.flatMap { $0 }
root.metadata?.bounds = GPXBounds(with: allLocations)

let track = GPXTrack()
track.add(trackSegment: trackSegment)

for locations in trackLocationSegments where !locations.isEmpty {
let trackSegment = GPXTrackSegment()
for gpxLocation in locations {
trackSegment.add(trackpoint: GPXTrackPoint(with: gpxLocation))
}
track.add(trackSegment: trackSegment)
}
root.add(track: track)

return root
}
}
Expand All @@ -104,7 +110,7 @@ extension GPXExtensionsElement {
return
}

let new_element = GPXExtensionsElement(name: value)
let new_element = GPXExtensionsElement(name: name)
new_element.text = value
children.append(new_element)
}
Expand Down
Loading