Added mph/kph persistence.
App now remembers your preference for miles per hour or kilometers per hour so you can live your life one quarter mile at a time (or like, 0.402336 kilometers, if that's your jam).
This commit is contained in:
+30
-28
@@ -9,26 +9,28 @@ import SwiftUI
|
|||||||
import CoreLocation
|
import CoreLocation
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@StateObject private var locationManager = LocationManager() // 📍 Manages real-time GPS data
|
@StateObject private var locationManager = LocationManager() // 📍 Handles GPS updates
|
||||||
|
|
||||||
@State private var isMPH = true // 🚗 Speed unit toggle: true = MPH, false = KPH
|
// 🚗 Speed unit toggle (true = MPH, false = KPH), persisted
|
||||||
@State private var previousLocation: CLLocation? // 📌 Track last known location to compute trip distance
|
@State private var isMPH: Bool = UserDefaults.standard.object(forKey: "SpeedUnitIsMPH") as? Bool ?? true
|
||||||
@State private var tripDistanceMeters: Double = 0.0 // 📏 Track trip distance in meters
|
|
||||||
|
|
||||||
// 🌗 Track current theme (loaded from UserDefaults)
|
@State private var previousLocation: CLLocation? // 📌 Last location to calculate distance
|
||||||
|
@State private var tripDistanceMeters: Double = 0.0 // 📏 Distance in meters
|
||||||
|
|
||||||
|
// 🌗 Theme (persisted)
|
||||||
@State private var isDarkMode: Bool = UserDefaults.standard.bool(forKey: "DarkModeEnabled")
|
@State private var isDarkMode: Bool = UserDefaults.standard.bool(forKey: "DarkModeEnabled")
|
||||||
|
|
||||||
// 👁️ Track minimal view preference (loaded from UserDefaults)
|
// 👁️ Minimal view toggle (persisted)
|
||||||
@State private var isMinimalView: Bool = UserDefaults.standard.bool(forKey: "MinimalViewEnabled")
|
@State private var isMinimalView: Bool = UserDefaults.standard.bool(forKey: "MinimalViewEnabled")
|
||||||
|
|
||||||
// 🧠 Load trip distance if saved
|
// 🧠 Load saved trip distance
|
||||||
init() {
|
init() {
|
||||||
if let savedDistance = UserDefaults.standard.object(forKey: "TripDistance") as? Double {
|
if let savedDistance = UserDefaults.standard.object(forKey: "TripDistance") as? Double {
|
||||||
_tripDistanceMeters = State(initialValue: savedDistance)
|
_tripDistanceMeters = State(initialValue: savedDistance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧮 Calculate speed based on unit selection
|
// 🧮 Current speed in selected unit
|
||||||
var currentSpeed: Int {
|
var currentSpeed: Int {
|
||||||
let multiplier = isMPH ? 1.0 : 1.60934
|
let multiplier = isMPH ? 1.0 : 1.60934
|
||||||
return Int(Double(locationManager.speed) * multiplier)
|
return Int(Double(locationManager.speed) * multiplier)
|
||||||
@@ -39,14 +41,14 @@ struct ContentView: View {
|
|||||||
isMPH ? "mph" : "kph"
|
isMPH ? "mph" : "kph"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📐 Distance label (miles or km)
|
// 📐 Trip distance display
|
||||||
var distanceLabel: String {
|
var distanceLabel: String {
|
||||||
let distance = isMPH ? tripDistanceMeters * 0.000621371 : tripDistanceMeters / 1000
|
let distance = isMPH ? tripDistanceMeters * 0.000621371 : tripDistanceMeters / 1000
|
||||||
let roundedDistance = Int(distance.rounded())
|
let roundedDistance = Int(distance.rounded())
|
||||||
return "\(roundedDistance) \(isMPH ? "miles" : "km")"
|
return "\(roundedDistance) \(isMPH ? "miles" : "km")"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🎯 Color indicator for GPS accuracy
|
// 🎯 Color based on GPS accuracy
|
||||||
var accuracyColor: Color {
|
var accuracyColor: Color {
|
||||||
switch locationManager.accuracy {
|
switch locationManager.accuracy {
|
||||||
case ..<10:
|
case ..<10:
|
||||||
@@ -58,7 +60,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🎨 Theme-dependent color helpers
|
// 🎨 Theme-dependent UI colors
|
||||||
var foregroundColor: Color {
|
var foregroundColor: Color {
|
||||||
isDarkMode ? .white : .black
|
isDarkMode ? .white : .black
|
||||||
}
|
}
|
||||||
@@ -78,7 +80,7 @@ struct ContentView: View {
|
|||||||
VStack(spacing: 30) {
|
VStack(spacing: 30) {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// ⏩ Speed display and unit toggle
|
// 🔢 Speed and unit toggle
|
||||||
HStack(alignment: .bottom, spacing: 4) {
|
HStack(alignment: .bottom, spacing: 4) {
|
||||||
Text("\(currentSpeed)")
|
Text("\(currentSpeed)")
|
||||||
.font(.system(size: 140, weight: .bold, design: .rounded))
|
.font(.system(size: 140, weight: .bold, design: .rounded))
|
||||||
@@ -88,13 +90,13 @@ struct ContentView: View {
|
|||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(secondaryTextColor)
|
.foregroundColor(secondaryTextColor)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isMPH.toggle() // 🔁 Tap to switch MPH/KPH
|
isMPH.toggle()
|
||||||
|
UserDefaults.standard.set(isMPH, forKey: "SpeedUnitIsMPH") // 💾 Save unit preference
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 📊 Only show details if not in minimal mode
|
|
||||||
if !isMinimalView {
|
if !isMinimalView {
|
||||||
// Trip meter + reset icon
|
// 📊 Trip distance + reset
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Text("Trip: \(distanceLabel)")
|
Text("Trip: \(distanceLabel)")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
@@ -103,16 +105,16 @@ struct ContentView: View {
|
|||||||
Button(action: {
|
Button(action: {
|
||||||
tripDistanceMeters = 0
|
tripDistanceMeters = 0
|
||||||
previousLocation = nil
|
previousLocation = nil
|
||||||
UserDefaults.standard.set(0.0, forKey: "TripDistance") // 🧹 Clear saved trip
|
UserDefaults.standard.set(0.0, forKey: "TripDistance") // 🧹 Reset saved distance
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: "arrow.counterclockwise.circle") // 🔄 Reset icon
|
Image(systemName: "arrow.counterclockwise.circle")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.foregroundColor(secondaryTextColor)
|
.foregroundColor(secondaryTextColor)
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle()) // Remove button glow
|
.buttonStyle(PlainButtonStyle())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GPS accuracy indicator
|
// 🎯 GPS Accuracy indicator
|
||||||
Text(String(format: "GPS Accuracy: ±%.0f meters", locationManager.accuracy))
|
Text(String(format: "GPS Accuracy: ±%.0f meters", locationManager.accuracy))
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(accuracyColor)
|
.foregroundColor(accuracyColor)
|
||||||
@@ -122,14 +124,14 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
// ⚙️ Bottom control bar (eye + gear)
|
// ⚙️ Bottom toolbar (minimal mode + settings)
|
||||||
VStack {
|
VStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack {
|
HStack {
|
||||||
// 👁️ Minimal view toggle
|
// 👁️ Toggle minimal view + persist
|
||||||
Button(action: {
|
Button(action: {
|
||||||
isMinimalView.toggle()
|
isMinimalView.toggle()
|
||||||
UserDefaults.standard.set(isMinimalView, forKey: "MinimalViewEnabled") // 💾 Save preference
|
UserDefaults.standard.set(isMinimalView, forKey: "MinimalViewEnabled")
|
||||||
}) {
|
}) {
|
||||||
Image(systemName: isMinimalView ? "eye.slash.fill" : "eye.fill")
|
Image(systemName: isMinimalView ? "eye.slash.fill" : "eye.fill")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
@@ -139,7 +141,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// ⚙️ Open system settings
|
// ⚙️ Open system Settings
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||||
UIApplication.shared.open(url)
|
UIApplication.shared.open(url)
|
||||||
@@ -155,13 +157,13 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🌗 Tap anywhere to toggle dark/light mode and save it
|
// 🌗 Tap background to toggle dark/light mode and save it
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isDarkMode.toggle()
|
isDarkMode.toggle()
|
||||||
UserDefaults.standard.set(isDarkMode, forKey: "DarkModeEnabled") // 💾 Save theme mode
|
UserDefaults.standard.set(isDarkMode, forKey: "DarkModeEnabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🚨 Show alert for location errors or denied permissions
|
// 🚨 Handle location errors with alert
|
||||||
.alert(isPresented: $locationManager.showAlert) {
|
.alert(isPresented: $locationManager.showAlert) {
|
||||||
Alert(
|
Alert(
|
||||||
title: Text("Location Services"),
|
title: Text("Location Services"),
|
||||||
@@ -174,13 +176,13 @@ struct ContentView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧮 Update trip distance as new GPS data comes in
|
// 🧮 Track trip distance as location updates
|
||||||
.onReceive(locationManager.$location) { location in
|
.onReceive(locationManager.$location) { location in
|
||||||
if let newLocation = location {
|
if let newLocation = location {
|
||||||
if let oldLocation = previousLocation {
|
if let oldLocation = previousLocation {
|
||||||
let distance = newLocation.distance(from: oldLocation)
|
let distance = newLocation.distance(from: oldLocation)
|
||||||
tripDistanceMeters += distance
|
tripDistanceMeters += distance
|
||||||
UserDefaults.standard.set(tripDistanceMeters, forKey: "TripDistance") // 💾 Save new trip value
|
UserDefaults.standard.set(tripDistanceMeters, forKey: "TripDistance") // 💾 Save updated distance
|
||||||
}
|
}
|
||||||
previousLocation = newLocation
|
previousLocation = newLocation
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user