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

Sets up location services and map #4

Merged
merged 15 commits into from
Apr 4, 2024
23 changes: 1 addition & 22 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,4 @@ jobs:
name: Markdown Link Check
uses: StanfordBDHG/.github/.github/workflows/markdown-link-check.yml@v2
permissions:
contents: read
buildandtest:
name: Build and Test
uses: StanfordBDHG/.github/.github/workflows/xcodebuild-or-fastlane.yml@v2
permissions:
contents: read
with:
artifactname: StrokeCog.xcresult
runsonlabels: '["macOS", "self-hosted"]'
setupSimulators: true
setupfirebaseemulator: true
customcommand: "firebase emulators:exec 'fastlane test'"
uploadcoveragereport:
name: Upload Coverage Report
needs: buildandtest
uses: StanfordBDHG/.github/.github/workflows/create-and-upload-coverage-report.yml@v2
permissions:
contents: read
with:
coveragereports: StrokeCog.xcresult
secrets:
token: ${{ secrets.CODECOV_TOKEN }}
contents: read
2 changes: 0 additions & 2 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,6 @@ only_rules:
- switch_case_alignment
# Shorthand syntactic sugar should be used, i.e. [Int] instead of Array.
- syntactic_sugar
# TODOs and FIXMEs should be resolved.
- todo
# Prefer someBool.toggle() over someBool = !someBool.
- toggle_bool
# Trailing closure syntax should be used whenever possible.
Expand Down
84 changes: 74 additions & 10 deletions StrokeCog.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "9ddbf125e828f8f258514b9b7b616777823852827ae6b79035a44b66b986b5e0",
"originHash" : "743bf7cd89ddbade5c78d5f9c6554803b47ff2ebf7a758e70d2c9c4b9167c70f",
"pins" : [
{
"identity" : "abseil-cpp-binary",
Expand Down Expand Up @@ -109,6 +109,33 @@
"version" : "1.22.4"
}
},
{
"identity" : "mapbox-common-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mapbox/mapbox-common-ios.git",
"state" : {
"revision" : "2af5c688a58d8019af1376a0a76f505a5f36a27b",
"version" : "24.3.0-rc.1"
}
},
{
"identity" : "mapbox-core-maps-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mapbox/mapbox-core-maps-ios.git",
"state" : {
"revision" : "a85b77f312ecd430245c618d107050fa42d5afc2",
"version" : "11.3.0-rc.1"
}
},
{
"identity" : "mapbox-maps-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mapbox/mapbox-maps-ios.git",
"state" : {
"branch" : "main",
"revision" : "9d1d35636f1cbbb9e0027ba205f32f12c75820b2"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -289,6 +316,15 @@
"version" : "1.26.0"
}
},
{
"identity" : "turf-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mapbox/turf-swift.git",
"state" : {
"revision" : "213050191cfcb3d5aa76e1fa90c6ff1e182a42ca",
"version" : "2.8.0"
}
},
{
"identity" : "xctestextensions",
"kind" : "remoteSourceControl",
Expand Down
28 changes: 0 additions & 28 deletions StrokeCog/Helper/CodableArray+RawRepresentable.swift

This file was deleted.

14 changes: 14 additions & 0 deletions StrokeCog/Helper/Date+Helpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Date+Helpers.swift
// StrokeCog
//
// Created by Vishnu Ravi on 4/2/24.

import Foundation

extension Date {
/// Returns the start of the day for the Date
var startOfDay: Date {
Calendar.current.startOfDay(for: self)
}
}
6 changes: 6 additions & 0 deletions StrokeCog/Home.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct HomeView: View {
case schedule
case contact
case mockUpload
case map
}

static var accountEnabled: Bool {
Expand All @@ -29,6 +30,11 @@ struct HomeView: View {

var body: some View {
TabView(selection: $selectedTab) {
StrokeCogMapView()
.tag(Tabs.map)
.tabItem {
Label("Map", systemImage: "map.circle")
}
ScheduleView(presentingAccount: $presentingAccount)
.tag(Tabs.schedule)
.tabItem {
Expand Down
18 changes: 18 additions & 0 deletions StrokeCog/Location/LocationDataPoint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// LocationDataPoint.swift
// StrokeCog
//
// Created by Vishnu Ravi on 4/4/24.
//

import CoreLocation
import Foundation

struct LocationDataPoint: Codable {
var currentDate: Date
var time: TimeInterval
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
var studyID: String = ""
var updatedBy: String = ""
}
136 changes: 136 additions & 0 deletions StrokeCog/Location/LocationModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//
// LocationService.swift
// LifeSpace
//
// Created by Vishnu Ravi on 4/2/24.

import CoreLocation
import Firebase
import Foundation
import Spezi

public class LocationModule: NSObject, CLLocationManagerDelegate, Module, DefaultInitializable, EnvironmentAccessible {
@Dependency private var standard: StrokeCogStandard?

private(set) var manager = CLLocationManager()
public var allLocations = [CLLocationCoordinate2D]()
public var onLocationsUpdated: (([CLLocationCoordinate2D]) -> Void)?

private var previousLocation: CLLocationCoordinate2D?
private var previousDate: Date?

@Published var authorizationStatus: CLAuthorizationStatus = CLLocationManager().authorizationStatus
@Published var canShowRequestMessage = true

private var lastKnownLocation: CLLocationCoordinate2D? {
didSet {
guard let lastKnownLocation = lastKnownLocation else {
return
}
self.appendNewLocationPoint(point: lastKnownLocation)
}
}

override public required init() {
super.init()
manager.delegate = self

// If user doesn't have a tracking preference, default to true
if UserDefaults.standard.value(forKey: Constants.prefTrackingStatus) == nil {
UserDefaults.standard.set(true, forKey: Constants.prefTrackingStatus)
}

// If tracking status is true, start tracking
if UserDefaults.standard.bool(forKey: Constants.prefTrackingStatus) {
self.startTracking()
}

// Disable Mapbox telemetry
UserDefaults.standard.set(false, forKey: "MGLMapboxMetricsEnabled")
}

public func startTracking() {
if CLLocationManager.locationServicesEnabled() {
self.manager.startUpdatingLocation()
self.manager.startMonitoringSignificantLocationChanges()
self.manager.allowsBackgroundLocationUpdates = true
self.manager.pausesLocationUpdatesAutomatically = false
self.manager.showsBackgroundLocationIndicator = false
print("[LIFESPACE] Starting tracking...")
} else {
print("[LIFESPACE] Cannot start tracking - location services are not enabled.")
}
}

public func stopTracking() {
self.manager.stopUpdatingLocation()
self.manager.stopMonitoringSignificantLocationChanges()
print("[LIFESPACE] Stopping tracking...")
}

public func requestAuthorizationLocation() {
self.manager.requestWhenInUseAuthorization()
self.manager.requestAlwaysAuthorization()
}

/// Get all the points for a particular date from the database
/// - Parameter date: the date for which to fetch all points
func fetchPoints(date: Date = Date()) {
// TODO: Fetch from Firestore
}

/// Adds a new point to the map and saves the location to the database,
/// if it meets the criteria to be added.
/// - Parameter point: the point to add
private func appendNewLocationPoint(point: CLLocationCoordinate2D) {
var add = true

if let previousLocation = previousLocation,
let previousDate = previousDate {
// Check if distance between current point and previous point is greater than the minimum
add = LocationUtils.isAboveMinimumDistance(
previousLocation: previousLocation,
currentLocation: point
)

// Reset all points when day changes
if Date().startOfDay != previousDate.startOfDay {
add = true
fetchPoints()
}
}

if add {
// update local location data for map
allLocations.append(point)
onLocationsUpdated?(allLocations)
previousLocation = point
previousDate = Date()

Task {
do {
try await standard?.add(location: point)
} catch {
print(error.localizedDescription)
}
}
}
}

public func userAuthorizeAlways() -> Bool {
self.manager.authorizationStatus == .authorizedAlways
}

public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// An additional check that we only append points if location tracking is turned on
guard UserDefaults.standard.bool(forKey: Constants.prefTrackingStatus) else {
return
}

lastKnownLocation = locations.first?.coordinate
}

public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
authorizationStatus = manager.authorizationStatus
}
}
33 changes: 33 additions & 0 deletions StrokeCog/Location/LocationUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// LocationUtils.swift
// LifeSpace
//
// Created by Vishnu Ravi on 7/12/22.
// Copyright © 2022 LifeSpace. All rights reserved.
//

import CoreLocation
import Foundation

enum LocationUtils {
/// Checks if the last two points are above the minimum distance apart required to record them for the study.
/// - Parameters:
/// - previousLocation: the last point recorded
/// - currentLocation: the latest point
/// - Returns: Boolean
static func isAboveMinimumDistance(
previousLocation: CLLocationCoordinate2D,
currentLocation: CLLocationCoordinate2D
) -> Bool {
let lastLocation = CLLocation(
latitude: previousLocation.latitude,
longitude: previousLocation.longitude
)
let newLocation = CLLocation(
latitude: currentLocation.latitude,
longitude: currentLocation.longitude
)
let distanceInMeters = newLocation.distance(from: lastLocation)
return distanceInMeters > Constants.minDistanceBetweenPoints
}
}
Loading
Loading