Newer Older
154 lines | 5.666kb
Bogdan Timofte authored 2 weeks ago
1
//
2
//  DataStore.swift
3
//  USB Meter
4
//
5
//  Created by Bogdan Timofte on 03/03/2020.
6
//  Copyright © 2020 Bogdan Timofte. All rights reserved.
7
//
8

            
9
import SwiftUI
10
import Combine
11
import CoreBluetooth
12

            
13
final class AppData : ObservableObject {
Bogdan Timofte authored a week ago
14
    struct KnownMeterSummary: Identifiable {
15
        let macAddress: String
16
        let displayName: String
17
        let modelSummary: String
18
        let advertisedName: String?
19
        let lastSeen: Date?
20
        let lastConnected: Date?
21
        let meter: Meter?
22

            
23
        var id: String {
24
            macAddress
25
        }
26
    }
27

            
Bogdan Timofte authored 2 weeks ago
28
    private var bluetoothManagerNotification: AnyCancellable?
Bogdan Timofte authored a week ago
29
    private var meterStoreObserver: AnyCancellable?
30
    private var meterStoreCloudObserver: AnyCancellable?
31
    private let meterStore = MeterNameStore.shared
Bogdan Timofte authored 2 weeks ago
32

            
Bogdan Timofte authored 2 weeks ago
33
    init() {
Bogdan Timofte authored 2 weeks ago
34
        bluetoothManagerNotification = bluetoothManager.objectWillChange.sink { [weak self] _ in
Bogdan Timofte authored 2 weeks ago
35
            self?.scheduleObjectWillChange()
Bogdan Timofte authored 2 weeks ago
36
        }
Bogdan Timofte authored a week ago
37
        meterStoreObserver = NotificationCenter.default.publisher(for: .meterNameStoreDidChange)
38
            .receive(on: DispatchQueue.main)
39
            .sink { [weak self] _ in
40
                self?.refreshMeterMetadata()
41
            }
42
        meterStoreCloudObserver = NotificationCenter.default.publisher(for: .meterNameStoreCloudStatusDidChange)
43
            .receive(on: DispatchQueue.main)
44
            .sink { [weak self] _ in
45
                self?.scheduleObjectWillChange()
46
            }
Bogdan Timofte authored 2 weeks ago
47
    }
Bogdan Timofte authored a week ago
48

            
Bogdan Timofte authored 2 weeks ago
49
    let bluetoothManager = BluetoothManager()
Bogdan Timofte authored a week ago
50

            
Bogdan Timofte authored 2 weeks ago
51
    @Published var enableRecordFeature: Bool = true
Bogdan Timofte authored a week ago
52

            
Bogdan Timofte authored 2 weeks ago
53
    @Published var meters: [UUID:Meter] = [UUID:Meter]()
Bogdan Timofte authored a week ago
54

            
55
    var cloudAvailability: MeterNameStore.CloudAvailability {
56
        meterStore.currentCloudAvailability
57
    }
58

            
59
    func meterName(for macAddress: String) -> String? {
60
        meterStore.name(for: macAddress)
61
    }
62

            
63
    func setMeterName(_ name: String, for macAddress: String) {
64
        meterStore.upsert(macAddress: macAddress, name: name, temperatureUnitRawValue: nil)
65
    }
66

            
67
    func temperatureUnitPreference(for macAddress: String) -> TemperatureUnitPreference {
68
        let rawValue = meterStore.temperatureUnitRawValue(for: macAddress) ?? TemperatureUnitPreference.celsius.rawValue
69
        return TemperatureUnitPreference(rawValue: rawValue) ?? .celsius
70
    }
71

            
72
    func setTemperatureUnitPreference(_ preference: TemperatureUnitPreference, for macAddress: String) {
73
        meterStore.upsert(macAddress: macAddress, name: nil, temperatureUnitRawValue: preference.rawValue)
Bogdan Timofte authored 2 weeks ago
74
    }
Bogdan Timofte authored 2 weeks ago
75

            
Bogdan Timofte authored a week ago
76
    func registerKnownMeter(macAddress: String, modelName: String?, advertisedName: String?) {
77
        meterStore.registerKnownMeter(macAddress: macAddress, modelName: modelName, advertisedName: advertisedName)
78
    }
79

            
80
    func noteMeterSeen(at date: Date, macAddress: String) {
81
        meterStore.noteLastSeen(date, for: macAddress)
82
    }
83

            
84
    func noteMeterConnected(at date: Date, macAddress: String) {
85
        meterStore.noteLastConnected(date, for: macAddress)
86
    }
87

            
88
    func lastSeen(for macAddress: String) -> Date? {
89
        meterStore.lastSeen(for: macAddress)
90
    }
91

            
92
    func lastConnected(for macAddress: String) -> Date? {
93
        meterStore.lastConnected(for: macAddress)
94
    }
95

            
96
    var knownMeters: [KnownMeterSummary] {
97
        let liveMetersByMAC = Dictionary(uniqueKeysWithValues: meters.values.map { ($0.btSerial.macAddress.description, $0) })
98
        let recordsByMAC = Dictionary(uniqueKeysWithValues: meterStore.allRecords().map { ($0.macAddress, $0) })
99
        let macAddresses = Set(recordsByMAC.keys).union(liveMetersByMAC.keys)
100

            
101
        return macAddresses.map { macAddress in
102
            let liveMeter = liveMetersByMAC[macAddress]
103
            let record = recordsByMAC[macAddress]
104

            
105
            return KnownMeterSummary(
106
                macAddress: macAddress,
107
                displayName: liveMeter?.name ?? record?.customName ?? macAddress,
108
                modelSummary: liveMeter?.deviceModelSummary ?? record?.modelName ?? "Known meter",
109
                advertisedName: liveMeter?.modelString ?? record?.advertisedName,
110
                lastSeen: liveMeter?.lastSeen ?? record?.lastSeen,
111
                lastConnected: liveMeter?.lastConnectedAt ?? record?.lastConnected,
112
                meter: liveMeter
113
            )
114
        }
115
        .sorted { lhs, rhs in
116
            let byName = lhs.displayName.localizedCaseInsensitiveCompare(rhs.displayName)
117
            if byName != .orderedSame {
118
                return byName == .orderedAscending
119
            }
120
            return lhs.macAddress < rhs.macAddress
121
        }
122
    }
123

            
Bogdan Timofte authored 2 weeks ago
124
    private func scheduleObjectWillChange() {
125
        DispatchQueue.main.async { [weak self] in
126
            self?.objectWillChange.send()
127
        }
128
    }
Bogdan Timofte authored a week ago
129

            
130
    private func refreshMeterMetadata() {
131
        DispatchQueue.main.async { [weak self] in
132
            guard let self else { return }
133
            var didUpdateAnyMeter = false
134
            for meter in self.meters.values {
135
                let mac = meter.btSerial.macAddress.description
136
                let displayName = self.meterName(for: mac) ?? mac
137
                if meter.name != displayName {
138
                    meter.updateNameFromStore(displayName)
139
                    didUpdateAnyMeter = true
140
                }
141

            
142
                let previousTemperaturePreference = meter.tc66TemperatureUnitPreference
143
                meter.reloadTemperatureUnitPreference()
144
                if meter.tc66TemperatureUnitPreference != previousTemperaturePreference {
145
                    didUpdateAnyMeter = true
146
                }
147
            }
148

            
149
            if didUpdateAnyMeter {
150
                self.scheduleObjectWillChange()
151
            }
152
        }
153
    }
Bogdan Timofte authored 2 weeks ago
154
}