Skip to content

Commit e44a8fc

Browse files
authored
Merge pull request #4 from CodeByMiniOrg/simplified-validation
Simplified validation
2 parents 33b2054 + 1d652c6 commit e44a8fc

File tree

5 files changed

+22
-161
lines changed

5 files changed

+22
-161
lines changed

LoopFollow/Remote/LoopAPNS/LoopAPNSRemoteView.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import SwiftUI
66

77
struct LoopAPNSRemoteView: View {
88
@Environment(\.presentationMode) var presentationMode
9-
@ObservedObject var loopAPNSSetup = Storage.shared.loopAPNSSetup
109
@StateObject private var viewModel = RemoteSettingsViewModel()
1110

1211
var body: some View {
@@ -18,7 +17,7 @@ struct LoopAPNSRemoteView: View {
1817
]
1918

2019
LazyVGrid(columns: columns, spacing: 16) {
21-
if loopAPNSSetup.value {
20+
if viewModel.loopAPNSSetup {
2221
// Show Loop APNS command buttons if APNS setup configured
2322
CommandButtonView(command: "Meal", iconName: "fork.knife", destination: LoopAPNSCarbsView())
2423
CommandButtonView(command: "Bolus", iconName: "syringe", destination: LoopAPNSBolusView())
@@ -56,10 +55,6 @@ struct LoopAPNSRemoteView: View {
5655
Spacer()
5756
}
5857
.navigationBarTitle("Loop Remote Control", displayMode: .inline)
59-
.onAppear {
60-
// Validate Loop APNS setup when view appears
61-
viewModel.validateFullLoopAPNSSetup()
62-
}
6358
}
6459
}
6560
}

LoopFollow/Remote/LoopAPNS/LoopAPNSSettingsView.swift

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ struct LoopAPNSSettingsView: View {
9292
.buttonStyle(.borderedProminent)
9393
.frame(maxWidth: .infinity)
9494
.padding(.vertical, 10)
95+
9596
VStack(alignment: .leading, spacing: 12) {
9697
Text("Environment")
9798
.font(.headline)
@@ -111,6 +112,7 @@ struct LoopAPNSSettingsView: View {
111112
}
112113
.padding(.top, 4)
113114
}
115+
114116
VStack(alignment: .leading, spacing: 12) {
115117
Text("Device Token")
116118
.font(.headline)
@@ -193,22 +195,11 @@ struct LoopAPNSSettingsView: View {
193195
}
194196
}
195197
.onAppear {
196-
// Validate Loop APNS setup when view appears
197-
viewModel.validateFullLoopAPNSSetup()
198-
199198
// Automatically fetch device token and bundle identifier when entering the setup screen
200199
Task {
201200
await viewModel.refreshDeviceToken()
202201
}
203202
}
204-
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("LoopAPNSSetupChanged"))) { _ in
205-
// Update validation when Loop APNS setup changes
206-
viewModel.validateFullLoopAPNSSetup()
207-
}
208-
.onDisappear {
209-
// Force validation when leaving the settings view
210-
viewModel.validateFullLoopAPNSSetup()
211-
}
212203
}
213204
}
214205
}

LoopFollow/Remote/Settings/RemoteSettingsView.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,18 +161,6 @@ struct RemoteSettingsView: View {
161161

162162
.preferredColorScheme(Storage.shared.forceDarkMode.value ? .dark : nil)
163163
.navigationBarTitle("Remote Settings", displayMode: .inline)
164-
.onAppear {
165-
// Refresh Loop APNS setup validation when returning to this screen
166-
viewModel.validateFullLoopAPNSSetup()
167-
}
168-
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("LoopAPNSSetupChanged"))) { _ in
169-
// Update validation when Loop APNS setup changes
170-
viewModel.validateFullLoopAPNSSetup()
171-
}
172-
.onDisappear {
173-
// Force validation when leaving the settings view
174-
viewModel.validateFullLoopAPNSSetup()
175-
}
176164
}
177165

178166
// MARK: - Custom Row for Remote Type Selection

LoopFollow/Remote/Settings/RemoteSettingsViewModel.swift

Lines changed: 19 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,23 @@ class RemoteSettingsViewModel: ObservableObject {
2828
@Published var loopAPNSQrCodeURL: String
2929
@Published var loopAPNSDeviceToken: String
3030
@Published var loopAPNSBundleIdentifier: String
31-
@Published var loopAPNSSetup: Bool
3231
@Published var productionEnvironment: Bool
3332
@Published var isShowingLoopAPNSScanner: Bool = false
3433
@Published var loopAPNSErrorMessage: String?
3534
@Published var isRefreshingDeviceToken: Bool = false
3635

36+
// MARK: - Computed property for Loop APNS Setup validation
37+
38+
var loopAPNSSetup: Bool {
39+
!keyId.isEmpty &&
40+
!apnsKey.isEmpty &&
41+
!loopAPNSQrCodeURL.isEmpty &&
42+
!loopAPNSDeviceToken.isEmpty &&
43+
!loopAPNSBundleIdentifier.isEmpty
44+
}
45+
3746
private var storage = Storage.shared
3847
private var cancellables = Set<AnyCancellable>()
39-
private var isUpdatingLoopAPNSSetup = false
40-
private var lastValidationTime: Date = .distantPast
4148

4249
init() {
4350
// Initialize published properties from storage
@@ -57,16 +64,13 @@ class RemoteSettingsViewModel: ObservableObject {
5764
loopAPNSQrCodeURL = storage.loopAPNSQrCodeURL.value
5865
loopAPNSDeviceToken = storage.loopAPNSDeviceToken.value
5966
loopAPNSBundleIdentifier = storage.loopAPNSBundleIdentifier.value
60-
loopAPNSSetup = storage.loopAPNSSetup.value
6167
productionEnvironment = storage.productionEnvironment.value
6268

6369
setupBindings()
64-
65-
// Trigger initial validation
66-
validateFullLoopAPNSSetup()
6770
}
6871

6972
private func setupBindings() {
73+
// Basic property bindings
7074
$remoteType
7175
.dropFirst()
7276
.sink { [weak self] in self?.storage.remoteType.value = $0 }
@@ -84,7 +88,12 @@ class RemoteSettingsViewModel: ObservableObject {
8488

8589
$apnsKey
8690
.dropFirst()
87-
.sink { [weak self] in self?.storage.apnsKey.value = $0 }
91+
.sink { [weak self] newValue in
92+
// Validate and fix the APNS key format using the service
93+
let apnsService = LoopAPNSService()
94+
let fixedKey = apnsService.validateAndFixAPNSKey(newValue)
95+
self?.storage.apnsKey.value = fixedKey
96+
}
8897
.store(in: &cancellables)
8998

9099
$keyId
@@ -122,6 +131,7 @@ class RemoteSettingsViewModel: ObservableObject {
122131
.sink { [weak self] in self?.storage.mealWithFatProtein.value = $0 }
123132
.store(in: &cancellables)
124133

134+
// Device type monitoring
125135
Storage.shared.device.$value
126136
.receive(on: DispatchQueue.main)
127137
.sink { [weak self] newValue in
@@ -130,29 +140,7 @@ class RemoteSettingsViewModel: ObservableObject {
130140
}
131141
.store(in: &cancellables)
132142

133-
// Loop APNS setup bindings
134-
$keyId
135-
.dropFirst()
136-
.sink { [weak self] in self?.storage.keyId.value = $0 }
137-
.store(in: &cancellables)
138-
139-
$apnsKey
140-
.dropFirst()
141-
.sink { [weak self] newValue in
142-
// Log APNS key changes for debugging
143-
LogManager.shared.log(category: .apns, message: "APNS Key changed - Length: \(newValue.count)")
144-
LogManager.shared.log(category: .apns, message: "APNS Key contains line breaks: \(newValue.contains("\n"))")
145-
LogManager.shared.log(category: .apns, message: "APNS Key contains BEGIN header: \(newValue.contains("-----BEGIN PRIVATE KEY-----"))")
146-
LogManager.shared.log(category: .apns, message: "APNS Key contains END header: \(newValue.contains("-----END PRIVATE KEY-----"))")
147-
148-
// Validate and fix the APNS key format using the service
149-
let apnsService = LoopAPNSService()
150-
let fixedKey = apnsService.validateAndFixAPNSKey(newValue)
151-
152-
self?.storage.apnsKey.value = fixedKey
153-
}
154-
.store(in: &cancellables)
155-
143+
// Loop APNS bindings
156144
$loopDeveloperTeamId
157145
.dropFirst()
158146
.sink { [weak self] in self?.storage.teamId.value = $0 }
@@ -173,99 +161,14 @@ class RemoteSettingsViewModel: ObservableObject {
173161
.sink { [weak self] in self?.storage.loopAPNSBundleIdentifier.value = $0 }
174162
.store(in: &cancellables)
175163

176-
// Sync loopAPNSSetup with storage
177-
Storage.shared.loopAPNSSetup.$value
178-
.receive(on: DispatchQueue.main)
179-
.sink { [weak self] newValue in
180-
guard let self = self, !self.isUpdatingLoopAPNSSetup else { return }
181-
self.loopAPNSSetup = newValue
182-
}
183-
.store(in: &cancellables)
184-
185164
$productionEnvironment
186165
.dropFirst()
187166
.sink { [weak self] in self?.storage.productionEnvironment.value = $0 }
188167
.store(in: &cancellables)
189-
190-
// Auto-validate Loop APNS setup when key ID, APNS key, or QR code changes
191-
Publishers.CombineLatest3($keyId, $apnsKey, $loopAPNSQrCodeURL)
192-
.dropFirst()
193-
.receive(on: DispatchQueue.main)
194-
.sink { [weak self] _, _, _ in
195-
self?.validateFullLoopAPNSSetup()
196-
}
197-
.store(in: &cancellables)
198-
199-
// Auto-validate when device token or bundle identifier changes
200-
Publishers.CombineLatest($loopAPNSDeviceToken, $loopAPNSBundleIdentifier)
201-
.dropFirst()
202-
.receive(on: DispatchQueue.main)
203-
.sink { [weak self] _, _ in
204-
self?.validateFullLoopAPNSSetup()
205-
}
206-
.store(in: &cancellables)
207-
208-
// Auto-validate when production environment changes
209-
$productionEnvironment
210-
.dropFirst()
211-
.receive(on: DispatchQueue.main)
212-
.sink { [weak self] _ in
213-
self?.validateFullLoopAPNSSetup()
214-
}
215-
.store(in: &cancellables)
216-
217-
$loopDeveloperTeamId
218-
.dropFirst()
219-
.receive(on: DispatchQueue.main)
220-
.sink { [weak self] _ in
221-
self?.validateFullLoopAPNSSetup()
222-
}
223-
.store(in: &cancellables)
224168
}
225169

226170
// MARK: - Loop APNS Setup Methods
227171

228-
/// Validates the full Loop APNS setup including device token and bundle identifier
229-
/// - Returns: True if full setup is valid, false otherwise
230-
func validateFullLoopAPNSSetup() {
231-
// Debounce rapid successive calls (prevent calls within 100ms of each other)
232-
let now = Date()
233-
if now.timeIntervalSince(lastValidationTime) < 0.1 {
234-
LogManager.shared.log(category: .apns, message: "Skipping validation - too soon since last call")
235-
return
236-
}
237-
lastValidationTime = now
238-
239-
// Add call stack debugging
240-
let callStack = Thread.callStackSymbols.prefix(3).map { $0.components(separatedBy: " ").last ?? "unknown" }.joined(separator: " -> ")
241-
LogManager.shared.log(category: .apns, message: "validateFullLoopAPNSSetup called from: \(callStack)")
242-
243-
let hasKeyId = !keyId.isEmpty
244-
let hasAPNSKey = !apnsKey.isEmpty
245-
let hasQrCode = !loopAPNSQrCodeURL.isEmpty
246-
let hasDeviceToken = !loopAPNSDeviceToken.isEmpty
247-
let hasBundleIdentifier = !loopAPNSBundleIdentifier.isEmpty
248-
249-
let hasFullSetup = hasKeyId && hasAPNSKey && hasQrCode && hasDeviceToken && hasBundleIdentifier
250-
251-
let oldSetup = loopAPNSSetup
252-
253-
// Only update storage if the value has actually changed to prevent infinite loops
254-
if storage.loopAPNSSetup.value != hasFullSetup {
255-
isUpdatingLoopAPNSSetup = true
256-
storage.loopAPNSSetup.value = hasFullSetup
257-
isUpdatingLoopAPNSSetup = false
258-
}
259-
260-
// Log validation results for debugging
261-
LogManager.shared.log(category: .apns, message: "Full Loop APNS setup validation - Key ID: \(hasKeyId), APNS Key: \(hasAPNSKey), QR Code: \(hasQrCode), Device Token: \(hasDeviceToken), Bundle ID: \(hasBundleIdentifier), Valid: \(hasFullSetup)")
262-
263-
// Post notification if setup status changed
264-
if oldSetup != hasFullSetup {
265-
NotificationCenter.default.post(name: NSNotification.Name("LoopAPNSSetupChanged"), object: nil)
266-
}
267-
}
268-
269172
func refreshDeviceToken() async {
270173
await MainActor.run {
271174
isRefreshingDeviceToken = true
@@ -280,8 +183,6 @@ class RemoteSettingsViewModel: ObservableObject {
280183
if success {
281184
self.loopAPNSDeviceToken = self.storage.loopAPNSDeviceToken.value
282185
self.loopAPNSBundleIdentifier = self.storage.loopAPNSBundleIdentifier.value
283-
// Trigger validation immediately after updating values
284-
self.validateFullLoopAPNSSetup()
285186
} else {
286187
self.loopAPNSErrorMessage = "Failed to refresh device token. Check your Nightscout URL and token."
287188
}
@@ -308,13 +209,6 @@ class RemoteSettingsViewModel: ObservableObject {
308209
case let .success(profileData):
309210
// Log the profile data for debugging
310211
LogManager.shared.log(category: .apns, message: "Profile fetched successfully for device token")
311-
LogManager.shared.log(category: .apns, message: "Device token from profile: \(profileData.deviceToken ?? "nil")")
312-
LogManager.shared.log(category: .apns, message: "Bundle identifier from profile: \(profileData.bundleIdentifier ?? "nil")")
313-
314-
if let loopSettings = profileData.loopSettings {
315-
LogManager.shared.log(category: .apns, message: "Loop settings device token: \(loopSettings.deviceToken ?? "nil")")
316-
LogManager.shared.log(category: .apns, message: "Loop settings bundle identifier: \(loopSettings.bundleIdentifier ?? "nil")")
317-
}
318212

319213
// Update profile data which includes device token and bundle identifier
320214
ProfileManager.shared.loadProfile(from: profileData)
@@ -332,10 +226,6 @@ class RemoteSettingsViewModel: ObservableObject {
332226
self.storage.loopAPNSBundleIdentifier.value = bundleIdentifier
333227
}
334228

335-
// Log the stored values after processing
336-
LogManager.shared.log(category: .apns, message: "Stored device token: \(self.storage.loopAPNSDeviceToken.value)")
337-
LogManager.shared.log(category: .apns, message: "Stored bundle ID: \(self.storage.loopAPNSBundleIdentifier.value)")
338-
339229
// Log successful configuration
340230
LogManager.shared.log(category: .apns, message: "Successfully configured device tokens from Nightscout profile")
341231

@@ -355,9 +245,7 @@ class RemoteSettingsViewModel: ObservableObject {
355245
switch result {
356246
case let .success(code):
357247
self.loopAPNSQrCodeURL = code
358-
// Trigger validation after QR code is scanned
359248
LogManager.shared.log(category: .apns, message: "Loop APNS QR code scanned: \(code)")
360-
self.validateFullLoopAPNSSetup()
361249
case let .failure(error):
362250
self.loopAPNSErrorMessage = "Scanning failed: \(error.localizedDescription)"
363251
}

LoopFollow/Storage/Storage.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ class Storage {
168168

169169
// MARK: - Loop APNS Setup ---------------------------------------------------
170170

171-
var loopAPNSSetup = StorageValue<Bool>(key: "loopAPNSSetup", defaultValue: false)
172171
var loopAPNSQrCodeURL = StorageValue<String>(key: "loopAPNSQrCodeURL", defaultValue: "")
173172
var loopAPNSDeviceToken = StorageValue<String>(key: "loopAPNSDeviceToken", defaultValue: "")
174173
var loopAPNSBundleIdentifier = StorageValue<String>(key: "loopAPNSBundleIdentifier", defaultValue: "")

0 commit comments

Comments
 (0)