Skip to content
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
27 changes: 11 additions & 16 deletions Data Model/SavedLocation++.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,17 @@
import Foundation

extension SavedLocation {
struct CodableRepresentation: Codable, AnyLocation {
var title: String?
var subtitle: String?
var latitude: Double
var longitude: Double
var timeZoneIdentifier: String?
var uuid: UUID?
}

var codableRepresentation: CodableRepresentation {
CodableRepresentation(title: title,
subtitle: subtitle,
latitude: latitude,
longitude: longitude,
timeZoneIdentifier: timeZoneIdentifier,
uuid: uuid)
typealias CodableRepresentation = LocationData

var codableRepresentation: LocationData {
LocationData(
title: title,
subtitle: subtitle,
latitude: latitude,
longitude: longitude,
timeZoneIdentifier: timeZoneIdentifier,
uuid: uuid
)
}
}

Expand Down
35 changes: 8 additions & 27 deletions Intents/LocationAppEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ struct LocationEntityQuery: EntityQuery, EntityStringQuery {
let encoded = String(id.dropFirst(6))
guard let decoded = encoded.removingPercentEncoding,
let data = decoded.data(using: .utf8),
let locationData = try? JSONDecoder().decode(SavedLocationEntityData.self, from: data) else {
let locationData = try? JSONDecoder().decode(LocationData.self, from: data) else {
return nil
}

return LocationAppEntity(
id: id,
title: locationData.title,
title: locationData.title ?? "Unknown",
subtitle: locationData.subtitle,
latitude: locationData.latitude,
longitude: locationData.longitude,
Expand Down Expand Up @@ -255,7 +255,7 @@ struct LocationEntityQuery: EntityQuery, EntityStringQuery {
longitude: Double,
timeZoneIdentifier: String?
) -> String {
let data = TemporaryLocationData(
let data = LocationData(
title: title,
subtitle: subtitle,
latitude: latitude,
Expand All @@ -278,13 +278,13 @@ struct LocationEntityQuery: EntityQuery, EntityStringQuery {
let encoded = String(id.dropFirst(5))
guard let decoded = encoded.removingPercentEncoding,
let data = decoded.data(using: .utf8),
let locationData = try? JSONDecoder().decode(TemporaryLocationData.self, from: data) else {
let locationData = try? JSONDecoder().decode(LocationData.self, from: data) else {
return nil
}

return LocationAppEntity(
id: id,
title: locationData.title,
title: locationData.title ?? "Unknown",
subtitle: locationData.subtitle,
latitude: locationData.latitude,
longitude: locationData.longitude,
Expand All @@ -294,15 +294,6 @@ struct LocationEntityQuery: EntityQuery, EntityStringQuery {
}
}

/// Helper struct for encoding temporary location data
private struct TemporaryLocationData: Codable {
let title: String
let subtitle: String?
let latitude: Double
let longitude: Double
let timeZoneIdentifier: String?
}

// MARK: - Convenience Initializers

extension LocationAppEntity {
Expand All @@ -313,13 +304,13 @@ extension LocationAppEntity {
let subtitle = savedLocation.subtitle

// Encode full location data in ID so entity can be resolved even if Core Data lookup fails
let data = SavedLocationEntityData(
uuid: savedLocation.uuid,
let data = LocationData(
title: title,
subtitle: subtitle,
latitude: savedLocation.latitude,
longitude: savedLocation.longitude,
timeZoneIdentifier: savedLocation.timeZoneIdentifier
timeZoneIdentifier: savedLocation.timeZoneIdentifier,
uuid: savedLocation.uuid
)

if let encoded = try? JSONEncoder().encode(data),
Expand All @@ -338,13 +329,3 @@ extension LocationAppEntity {
self.savedLocationUUID = savedLocation.uuid
}
}

/// Helper struct for encoding saved location data in entity ID
private struct SavedLocationEntityData: Codable {
let uuid: UUID?
let title: String
let subtitle: String?
let latitude: Double
let longitude: Double
let timeZoneIdentifier: String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,36 @@
endingLineNumber = "58"
landmarkName = "body(content:)"
landmarkType = "7">
<Locations>
<Location
uuid = "D80AA32B-93C1-4B79-8BCD-7882590E25ED - 87f91cbc3384d0c9"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #1 (Foundation.URL) -&gt; () in Solstice.DeepLinkResolver.body(content: SwiftUI._ViewModifier_Content&lt;Solstice.DeepLinkResolver&gt;) -&gt; some"
moduleName = "Solstice.debug.dylib"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/dte/Developer/Solstice/Solstice/Helpers/DeepLinkResolver.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "58"
endingLineNumber = "58">
</Location>
<Location
uuid = "D80AA32B-93C1-4B79-8BCD-7882590E25ED - 24c23c16d8a7bd00"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "closure #1 (Foundation.URL) -&gt; () in Solstice_watchOS_Watch_App.DeepLinkResolver.body(content: SwiftUI._ViewModifier_Content&lt;Solstice_watchOS_Watch_App.DeepLinkResolver&gt;) -&gt; some"
moduleName = "Solstice watchOS Watch App.debug.dylib"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/dte/Developer/Solstice/Solstice/Helpers/DeepLinkResolver.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "58"
endingLineNumber = "58">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
Expand Down
94 changes: 60 additions & 34 deletions Solstice/Charts/CircularSolarChart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import enum Accelerate.vDSP

struct CircularSolarChart<Location: AnyLocation>: View {
@AppStorage(Preferences.detailViewChartAppearance) private var storedAppearance
@Environment(\.labelsVisibility) private var labelsVisibility
@Environment(\.colorScheme) private var colorScheme
@Environment(\.timeMachine) private var timeMachine
#if WIDGET_EXTENSION
@Environment(\.widgetRenderingMode) private var widgetRenderingMode
@Environment(\.widgetFamily) private var widgetFamily
#endif

@State private var size = CGSize.zero
Expand All @@ -36,7 +38,7 @@ struct CircularSolarChart<Location: AnyLocation>: View {
}

var majorSunSize: Double {
max(16, max(size.width, size.height) * 0.075)
max(12, max(size.width, size.height) * 0.075)
}

var minorSunSize: Double {
Expand Down Expand Up @@ -86,6 +88,14 @@ struct CircularSolarChart<Location: AnyLocation>: View {
return calendar
}

private var isSmallWidget: Bool {
#if WIDGET_EXTENSION
return widgetFamily == .systemSmall
#else
return false
#endif
}

func angle(for date: Date) -> Angle {
Helpers.angle(for: date, timeZone: timeZone)
}
Expand Down Expand Up @@ -116,7 +126,6 @@ struct CircularSolarChart<Location: AnyLocation>: View {
.fill(foregroundStyle)
.frame(width: majorSunSize)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.trailing, minorSunSize / 2)
.rotationEffect(angle(for: solar?.date ?? .now))
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
Expand All @@ -128,13 +137,12 @@ struct CircularSolarChart<Location: AnyLocation>: View {
.overlay {
Circle()
.fill(.clear)
.strokeBorder(foregroundStyle, lineWidth: 3)
.strokeBorder(foregroundStyle, lineWidth: 2)
}
.frame(width: majorSunSize)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.trailing, minorSunSize / 2)
.rotationEffect(angle(for: solar?.date ?? .now))
.frame(maxWidth: .infinity, maxHeight: .infinity)
.frame(width: majorSunSize)
.frame(maxWidth: .infinity, alignment: .trailing)
.rotationEffect(angle(for: solar?.date ?? .now))
.frame(maxWidth: .infinity, maxHeight: .infinity)
}

var safeSunriseSunsetShape: some Shape {
Expand Down Expand Up @@ -252,7 +260,7 @@ struct CircularSolarChart<Location: AnyLocation>: View {
Circle()
.fill(.clear)
.stroke(.black, lineWidth: 1)
.padding()
.padding(majorSunSize / 2)

phaseMarkers
}
Expand All @@ -278,46 +286,50 @@ struct CircularSolarChart<Location: AnyLocation>: View {
Circle()
.frame(width: minorSunSize)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.trailing)
.padding(.trailing, majorSunSize / 2)
.offset(x: minorSunSize / 2)
.rotationEffect(angle(for: sunrise))

Circle()
.frame(width: minorSunSize)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.trailing)
.padding(.trailing, majorSunSize / 2)
.offset(x: minorSunSize / 2)
.rotationEffect(angle(for: sunset))
}
}

@ViewBuilder
var labels: some View {
if let solar {
ChartLabel(text: Text(solar.safeSunrise, style: .time),
imageName: "sunrise",
angle: angle(for: solar.safeSunrise))
if labelsVisibility != .hidden {
if let solar {
ChartLabel(text: Text(solar.safeSunrise, style: .time),
imageName: "sunrise",
angle: angle(for: solar.safeSunrise))

ChartLabel(text: Text(solar.safeSunset, style: .time),
imageName: "sunset",
angle: angle(for: solar.safeSunset))
}

ChartLabel(text: Text(solar.safeSunset, style: .time),
imageName: "sunset",
angle: angle(for: solar.safeSunset))
}

if let duration = solar?.daylightDuration,
let diff = solar?.compactDifferenceString {
VStack(spacing: 2) {
HStack(spacing: 2) {
Image(systemName: "hourglass")
Text(Duration.seconds(duration).formatted(.units(width: .narrow)))
if let duration = solar?.daylightDuration,
let diff = solar?.compactDifferenceString {
VStack(spacing: 2) {
HStack(spacing: 2) {
if !isSmallWidget {
Image(systemName: "hourglass")
}
Text(Duration.seconds(duration).formatted(.units(width: .narrow)))
}

Text(diff)
.textScale(.secondary)
.foregroundStyle(.secondary)
}

Text(diff)
.textScale(.secondary)
.foregroundStyle(.secondary)
.padding(4)
.padding(.horizontal, 4)
.backportGlassEffect(in: .rect(cornerRadius: 12, style: .continuous))
}
.padding(4)
.padding(.horizontal, 4)
.backportGlassEffect(in: .rect(cornerRadius: 12, style: .continuous))
}
}

Expand Down Expand Up @@ -361,13 +373,27 @@ struct CircularSolarChart<Location: AnyLocation>: View {
}

fileprivate struct ChartLabel: View {
#if WIDGET_EXTENSION
@Environment(\.widgetFamily) private var widgetFamily
#endif

var text: Text
var imageName: String
var angle: Angle

private var isSmallWidget: Bool {
#if WIDGET_EXTENSION
return widgetFamily == .systemSmall
#else
return false
#endif
}

var body: some View {
HStack(spacing: 2) {
Image(systemName: imageName)
if !isSmallWidget {
Image(systemName: imageName)
}
text
}
.padding(4)
Expand Down
16 changes: 11 additions & 5 deletions Solstice/Helpers/CurrentLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@ class CurrentLocation: NSObject, CLLocationManagerDelegate {
}
}

/// Caches the current location to the App Group for widget access
/// Caches the current location and placemark to the App Group for widget access
private func cacheLocationToAppGroup(_ location: CLLocation?) {
guard let location else { return }
let defaults = UserDefaults(suiteName: Constants.appGroupIdentifier)
defaults?.set(location.coordinate.latitude, forKey: "cachedLatitude")
defaults?.set(location.coordinate.longitude, forKey: "cachedLongitude")
defaults?.set(Date().timeIntervalSince1970, forKey: "cachedLocationTimestamp")

let locationData = LocationData(
title: placemark?.locality,
subtitle: placemark?.country,
latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude,
timeZoneIdentifier: placemark?.timeZone?.identifier
)

LocationAppGroupCache.write(locationData)
}

@ObservationIgnored private let locationManager = CLLocationManager()
Expand Down
Loading