Skip to content
Open
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
75 changes: 49 additions & 26 deletions HomeAssistant.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions Sources/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import FirebaseMessaging
import Intents
import KeychainAccess
import PromiseKit
import RealmSwift
import SafariServices
import Shared
import UIKit
Expand Down Expand Up @@ -335,7 +334,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

private func showNotificationCategoryAlertIfNeeded() {
guard Current.realm().objects(NotificationCategory.self).isEmpty == false else {
guard NotificationCategory.all().isEmpty == false else {
return
}

Expand Down Expand Up @@ -453,8 +452,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

private func setupModels() {
// Force Realm migration to happen now
_ = Realm.live()
// Import any legacy Realm data into GRDB before anything reads it
RealmToGRDBMigration.migrateIfNeeded()
NotificationCategory.setupObserver()
}

Expand Down
56 changes: 33 additions & 23 deletions Sources/App/ClientEvents/LocationHistoryDetailViewController.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import GRDB
import MapKit
import RealmSwift
import Shared
import SwiftUI
import UIKit
Expand Down Expand Up @@ -58,7 +58,7 @@ final class LocationHistoryDetailViewController: UIViewController {
}

private var locationHistoryEntries: [LocationHistoryEntry] = []
private var token: NotificationToken?
private var token: AnyDatabaseCancellable?
private let map = MKMapView()

init(currentEntry: LocationHistoryEntry) {
Expand All @@ -73,7 +73,7 @@ final class LocationHistoryDetailViewController: UIViewController {
}

deinit {
token?.invalidate()
token?.cancel()
}

override func viewWillDisappear(_ animated: Bool) {
Expand All @@ -90,21 +90,31 @@ final class LocationHistoryDetailViewController: UIViewController {
private func setUp() {
setUpObserver()
title = DateFormatter.localizedString(
from: currentEntry.CreatedAt,
from: currentEntry.createdAt,
dateStyle: .short,
timeStyle: .medium
)
navigationItem.title = title
}

private func setUpObserver() {
let results = Current.realm()
.objects(LocationHistoryEntry.self)
.sorted(byKeyPath: "CreatedAt", ascending: false)
guard token == nil else { return }

token = results.observe { [weak self] _ in
self?.locationHistoryEntries = results.map(LocationHistoryEntry.init)
let observation = ValueObservation.tracking { db in
try LocationHistoryEntry
.order(Column(DatabaseTables.LocationHistory.createdAt.rawValue).desc)
.fetchAll(db)
}

token = observation.start(
in: Current.database(),
onError: { error in
Current.Log.error("couldn't observe location history: \(error)")
},
onChange: { [weak self] entries in
self?.locationHistoryEntries = entries
}
)
}

@objc private func center(_ sender: AnyObject?) {
Expand All @@ -123,17 +133,17 @@ final class LocationHistoryDetailViewController: UIViewController {

let accuracyNote: String

if currentEntry.Accuracy == 65 {
if currentEntry.accuracy == 65 {
accuracyNote = " (from Wi-Fi)"
} else if currentEntry.Accuracy == 1414 {
} else if currentEntry.accuracy == 1414 {
accuracyNote = " (from cell tower)"
} else {
accuracyNote = ""
}

let accuracyAuthorization: String

if let authorization = currentEntry.accuracyAuthorization {
if let authorization = currentEntry.clAccuracyAuthorization {
switch authorization {
case .fullAccuracy: accuracyAuthorization = "full"
case .reducedAccuracy: accuracyAuthorization = "reduced"
Expand All @@ -155,20 +165,20 @@ final class LocationHistoryDetailViewController: UIViewController {
"""
## Payload
```json
\(currentEntry.Payload)
\(currentEntry.payload)
```

## Location
- Trigger: \(currentEntry.Trigger ?? "(unknown)")
- Center: (\(latLongString(currentEntry.Latitude)), \(latLongString(currentEntry.Longitude)))
- Accuracy: \(distanceString(currentEntry.Accuracy))\(accuracyNote)
- Trigger: \(currentEntry.trigger ?? "(unknown)")
- Center: (\(latLongString(currentEntry.latitude)), \(latLongString(currentEntry.longitude)))
- Accuracy: \(distanceString(currentEntry.accuracy))\(accuracyNote)
- Accuracy Authorization: \(accuracyAuthorization)

## Regions
""" + "\n"
)

let allRegions = Current.realm().objects(RLMZone.self)
let allRegions = AppZone.all()
.flatMap(\.circularRegionsForMonitoring)
.sorted(by: { a, b in
a.distanceWithAccuracy(from: currentEntry.clLocation) < b
Expand Down Expand Up @@ -228,7 +238,7 @@ final class LocationHistoryDetailViewController: UIViewController {
move(.down)
}

private static func overlays<T: Collection>(for zones: T) -> [MKOverlay] where T.Element: RLMZone {
private static func overlays(for zones: some Collection<AppZone>) -> [MKOverlay] {
zones.flatMap { zone -> [MKOverlay] in
var overlays = [MKOverlay]()

Expand All @@ -238,7 +248,7 @@ final class LocationHistoryDetailViewController: UIViewController {
overlays.append(contentsOf: regions.map { RegionCircle(center: $0.center, radius: $0.radius) })
}

overlays.append(ZoneCircle(center: zone.center, radius: zone.Radius))
overlays.append(ZoneCircle(center: zone.center, radius: zone.radius))
return overlays
}
}
Expand Down Expand Up @@ -328,7 +338,7 @@ final class LocationHistoryDetailViewController: UIViewController {

func updateOverlays() {
map.removeOverlays(map.overlays)
map.addOverlays(Self.overlays(for: Current.realm().objects(RLMZone.self)))
map.addOverlays(Self.overlays(for: AppZone.all()))
map.addOverlays(Self.overlays(for: currentEntry.clLocation))
}

Expand All @@ -344,9 +354,9 @@ private extension LocationHistoryDetailViewController {
) -> Bool {
switch direction {
case .up:
locationHistoryEntries.first?.CreatedAt != currentEntry.CreatedAt
locationHistoryEntries.first?.id != currentEntry.id
case .down:
locationHistoryEntries.last?.CreatedAt != currentEntry.CreatedAt
locationHistoryEntries.last?.id != currentEntry.id
}
}

Expand All @@ -355,7 +365,7 @@ private extension LocationHistoryDetailViewController {
) {
guard
let currentIndex = locationHistoryEntries.firstIndex(where: { entry in
entry.CreatedAt == currentEntry.CreatedAt
entry.id == currentEntry.id
}) else { return }
let newIndex = switch direction {
case .up: currentIndex - 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ struct LocationHistoryEntryListItemView: View {
.edgesIgnoringSafeArea([.top, .bottom])
} label: {
VStack(alignment: .leading) {
Text(dateFormatter.string(from: entry.CreatedAt))
Text(dateFormatter.string(from: entry.createdAt))
.foregroundStyle(.primary)
if let trigger = entry.Trigger {
if let trigger = entry.trigger {
Text(trigger)
.foregroundStyle(.secondary)
}
Expand Down
62 changes: 27 additions & 35 deletions Sources/App/ClientEvents/LocationHistoryListView.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import RealmSwift
import GRDB
import Shared
import SwiftUI

extension LocationHistoryEntry: @retroactive Identifiable {
public var id: String {
ObjectIdentifier(self).hashValue.description
}
}

private class LocationHistoryListViewModel: ObservableObject {
@ObservedResults(LocationHistoryEntry.self) var locationHistoryEntryResults
@Published var locationHistoryEntries: [LocationHistoryEntry] = []

private var token: NotificationToken?
private var token: AnyDatabaseCancellable?

let dateFormatter = with(DateFormatter()) {
$0.dateStyle = .short
Expand All @@ -30,29 +23,32 @@ private class LocationHistoryListViewModel: ObservableObject {
}

deinit {
token?.invalidate()
token?.cancel()
}

func clear() {
let realm = Current.realm()
realm.reentrantWrite {
realm.delete(realm.objects(LocationHistoryEntry.self))
}
LocationHistoryEntry.deleteAll()
}

private func setupObserver() {
let results = Current.realm()
.objects(LocationHistoryEntry.self)
.sorted(byKeyPath: "CreatedAt", ascending: false)

token = results.observe { [weak self] _ in
self?.updateEntries(with: results)
let observation = ValueObservation.tracking { db in
try LocationHistoryEntry
.order(Column(DatabaseTables.LocationHistory.createdAt.rawValue).desc)
.fetchAll(db)
}
updateEntries(with: results)
}

private func updateEntries(with results: Results<LocationHistoryEntry>) {
locationHistoryEntries = results.map(LocationHistoryEntry.init)
// .immediate delivers the initial value synchronously (we are created on
// the main queue), matching the previous Realm behavior of populating
// the list before first render (the snapshot tests rely on this).
token = observation.start(
in: Current.database(),
scheduling: .immediate,
onError: { error in
Current.Log.error("couldn't observe location history: \(error)")
},
onChange: { [weak self] entries in
self?.locationHistoryEntries = entries
}
)
}
}

Expand Down Expand Up @@ -94,28 +90,24 @@ final class LocationHistoryListViewHostingController: UIHostingController<Locati
private struct PreviewLocationHistoryListView: View {
private var locationHistory: [LocationHistoryEntry]

private func writeToRealm(_ locationHistory: [LocationHistoryEntry]) {
let realm = Current.realm()
realm.reentrantWrite {
realm.deleteAll()
for entry in locationHistory {
let newEntry = LocationHistoryEntry(value: entry)
realm.add(newEntry)
}
private func writeToDatabase(_ locationHistory: [LocationHistoryEntry]) {
LocationHistoryEntry.deleteAll()
for entry in locationHistory {
entry.save()
}
}

init(
locationHistory: [LocationHistoryEntry]
) {
self.locationHistory = locationHistory
writeToRealm(locationHistory)
writeToDatabase(locationHistory)
}

var body: some View {
LocationHistoryListView()
.onAppear {
writeToRealm(locationHistory)
writeToDatabase(locationHistory)
}
}
}
Expand Down
16 changes: 0 additions & 16 deletions Sources/App/Scenes/SceneManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,22 +161,6 @@ final class SceneManager {
init() {
(self.webViewControllerPromise, self.webViewControllerSeal) = Guarantee<WebViewController>.pending()
(self.appCoordinatorPromise, self.appCoordinatorSeal) = Guarantee<AppCoordinator>.pending()

// swiftlint:disable prohibit_environment_assignment
Current.realmFatalPresentation = { [weak self] viewController in
guard let self else { return }

let under = UIViewController()
under.view.backgroundColor = .black
under.modalPresentationStyle = .fullScreen

appCoordinator.done { parent in
parent.present(under, animated: false, completion: {
under.present(viewController, animated: true, completion: nil)
})
}
}
// swiftlint:enable prohibit_environment_assignment
}

fileprivate func pendingResolver<T>(from activities: Set<NSUserActivity>) -> (T) -> Void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Foundation
import PromiseKit
import RealmSwift
import Shared
import SwiftUI
import UIKit
Expand Down Expand Up @@ -45,7 +44,7 @@ final class ComplicationEditViewModel: ObservableObject {
self.displayTemplate = config.Template

self.name = config.name ?? ""
self.isPublic = config.IsPublic
self.isPublic = config.isPublic

if let existing = Current.servers.server(forServerIdentifier: config.serverIdentifier) {
self.serverIdentifier = existing.identifier.rawValue
Expand Down Expand Up @@ -155,26 +154,19 @@ final class ComplicationEditViewModel: ObservableObject {

// MARK: - Save / Delete

/// Persists to Realm then asks the API to push the change. Returns once
/// the Realm write is committed; the API update is fired-and-forget like
/// the original Eureka controller.
/// Persists to the database then asks the API to push the change. The API
/// update is fired-and-forget like the original Eureka controller.
func save() {
let realm = Current.realm()
let server = server
let payload = serializedData()
let nameValue = name.isEmpty ? nil : name
let isPublicValue = isPublic
let template = displayTemplate
let serverIdentifierValue = server?.identifier.rawValue

realm.reentrantWrite {
config.name = nameValue
config.IsPublic = isPublicValue
config.serverIdentifier = serverIdentifierValue
config.Template = template
config.Data = payload
realm.add(config, update: .all)
}.then(on: nil) { () -> Promise<Void> in

config.name = name.isEmpty ? nil : name
config.isPublic = isPublic
config.serverIdentifier = server?.identifier.rawValue
config.Template = displayTemplate
config.Data = serializedData()
config.save()

firstly { () -> Promise<Void> in
if let server {
return Current.api(for: server)?
.updateComplications(passively: false) ??
Expand All @@ -186,11 +178,11 @@ final class ComplicationEditViewModel: ObservableObject {
}

func delete() {
let realm = Current.realm()
let server = server
realm.reentrantWrite {
realm.delete(config)
}.then(on: nil) { () -> Promise<Void> in

config.delete()

firstly { () -> Promise<Void> in
if let server {
return Current.api(for: server)?
.updateComplications(passively: false) ??
Expand Down
Loading
Loading