From a490674f721ccd8463d1e438e6423d986b6b764c Mon Sep 17 00:00:00 2001 From: liamcharger Date: Mon, 22 Apr 2024 00:06:20 -0400 Subject: [PATCH] Improve UX/UI when offline, fix some watch face display issues --- InfiniLink/BLE/BLEUpdateHandler.swift | 7 +- .../Core/Home/DFU/DFUDownloadView.swift | 3 + InfiniLink/Core/Home/DFU/DFUWithBLE.swift | 1 - InfiniLink/Core/Home/DeviceView.swift | 70 ++++++++---- InfiniLink/Core/Home/WatchFace.swift | 12 +- .../Weather/Views/WeatherDetailView.swift | 107 ++++++++++-------- .../Core/Weather/WeatherController.swift | 56 ++++----- InfiniLink/InfiniLinkApp.swift | 5 +- InfiniLink/Utilities/NetworkManager.swift | 24 ++-- InfiniLink/en.lproj/Localizable.strings | 4 + InfiniLink/fr.lproj/Localizable.strings | 4 + InfiniLink/ko.lproj/Localizable.strings | 5 + InfiniLink/ru.lproj/Localizable.strings | 4 + InfiniLink/zh-Hant.lproj/Localizable.strings | 5 + 14 files changed, 187 insertions(+), 120 deletions(-) diff --git a/InfiniLink/BLE/BLEUpdateHandler.swift b/InfiniLink/BLE/BLEUpdateHandler.swift index 7040544..26058d3 100644 --- a/InfiniLink/BLE/BLEUpdateHandler.swift +++ b/InfiniLink/BLE/BLEUpdateHandler.swift @@ -12,7 +12,7 @@ import CoreData import SwiftUI struct BLEUpdatedCharacteristicHandler { - @ObservedObject var healthKitManager = HealthKitManager() + @ObservedObject var healthKitManager = HealthKitManager.shared let bleManager = BLEManager.shared let bleManagerVal = BLEManagerVal.shared @@ -36,8 +36,9 @@ struct BLEUpdatedCharacteristicHandler { } func handleUpdates(characteristic: CBCharacteristic, peripheral: CBPeripheral) { - // TODO: Fix hang - weatherController.updateWeatherData(ignoreTimeLimits: false) + if NetworkManager.shared.getNetworkState() { + weatherController.updateWeatherData(ignoreTimeLimits: false) + } switch characteristic.uuid { case bleManagerVal.cbuuidList.musicControl: diff --git a/InfiniLink/Core/Home/DFU/DFUDownloadView.swift b/InfiniLink/Core/Home/DFU/DFUDownloadView.swift index c27df2a..79a303b 100644 --- a/InfiniLink/Core/Home/DFU/DFUDownloadView.swift +++ b/InfiniLink/Core/Home/DFU/DFUDownloadView.swift @@ -13,6 +13,7 @@ import SwiftUI struct DownloadView: View { @ObservedObject var downloadManager = DownloadManager.shared @ObservedObject var dfuUpdater = DFU_Updater.shared + @ObservedObject var networkManager = NetworkManager.shared @Environment(\.presentationMode) var presentation @Environment(\.colorScheme) var colorScheme @@ -140,6 +141,8 @@ struct DownloadView: View { .foregroundColor(.primary) .clipShape(Capsule()) } + .disabled(!networkManager.getNetworkState()) + .opacity(!networkManager.getNetworkState() ? 0.5 : 1.0) } } } diff --git a/InfiniLink/Core/Home/DFU/DFUWithBLE.swift b/InfiniLink/Core/Home/DFU/DFUWithBLE.swift index f50da54..1667cd9 100644 --- a/InfiniLink/Core/Home/DFU/DFUWithBLE.swift +++ b/InfiniLink/Core/Home/DFU/DFUWithBLE.swift @@ -76,7 +76,6 @@ struct DFUWithBLE: View { .opacity(lockNavigation ? 0.5 : 1.0) } .padding() - .frame(maxWidth: .infinity, alignment: .center) Divider() VStack { if externalResources && !(dfuUpdater.resourceFilename.isEmpty) { diff --git a/InfiniLink/Core/Home/DeviceView.swift b/InfiniLink/Core/Home/DeviceView.swift index 0ff4c6c..49f88f0 100644 --- a/InfiniLink/Core/Home/DeviceView.swift +++ b/InfiniLink/Core/Home/DeviceView.swift @@ -13,6 +13,7 @@ struct DeviceView: View { @ObservedObject var bleManagerVal = BLEManagerVal.shared @ObservedObject var deviceInfo = BLEDeviceInfo.shared @ObservedObject var uptimeManager = UptimeManager.shared + @ObservedObject var networkManager = NetworkManager.shared @AppStorage("watchNotifications") var watchNotifications: Bool = true @AppStorage("batteryNotification") var batteryNotification: Bool = false @@ -137,40 +138,48 @@ struct DeviceView: View { SheetManager.shared.sheetSelection = .weather SheetManager.shared.showSheet = true }) { - VStack { - HStack { - VStack(alignment: .leading) { - Text(NSLocalizedString("weather", comment: "")) - .font(.headline) - if bleManagerVal.loadingWeather { - Text(NSLocalizedString("loading", comment: "Loading...")) + HStack { + VStack(alignment: .leading) { + Text(NSLocalizedString("weather", comment: "")) + .font(.headline) + if bleManagerVal.loadingWeather { + Text(NSLocalizedString("loading", comment: "Loading...")) + } else { + if (UnitTemperature.current == .celsius && deviceData.chosenWeatherMode == "System") || deviceData.chosenWeatherMode == "Metric" { + Text(String(Int(round(bleManagerVal.weatherInformation.temperature))) + "°" + "C") + .font(.title.weight(.semibold)) } else { - if (UnitTemperature.current == .celsius && deviceData.chosenWeatherMode == "System") || deviceData.chosenWeatherMode == "Metric" { - Text(String(Int(round(bleManagerVal.weatherInformation.temperature))) + "°" + "C") - .font(.title.weight(.semibold)) - } else { - Text(String(Int(round(bleManagerVal.weatherInformation.temperature * 1.8 + 32))) + "°" + "F") - .font(.title.weight(.semibold)) - } + Text(String(Int(round(bleManagerVal.weatherInformation.temperature * 1.8 + 32))) + "°" + "F") + .font(.title.weight(.semibold)) } } - .font(.title.weight(.semibold)) - Spacer() - VStack { - if bleManagerVal.loadingWeather { - Image(systemName: "circle.slash") - } else { - Image(systemName: icon) - } + } + .font(.title.weight(.semibold)) + Spacer() + VStack { + if bleManagerVal.loadingWeather { + Image(systemName: "circle.slash") + } else { + Image(systemName: icon) } - .font(.title.weight(.medium)) } + .font(.title.weight(.medium)) } .padding() .background(LinearGradient(colors: [.blue, .yellow], startPoint: .leading, endPoint: .trailing)) .foregroundColor(.white) .cornerRadius(15) } + .disabled(!networkManager.getNetworkState()) + .opacity(!networkManager.getNetworkState() ? 0.4 : 1.0) + .overlay { + if !networkManager.getNetworkState() { + Image(systemName: "wifi.slash") + .foregroundColor(.white) + .font(.system(size: 35).weight(.semibold)) + .frame(maxWidth: .infinity, alignment: .center) + } + } Spacer() .frame(height: 6) } @@ -413,6 +422,20 @@ struct CustomScrollView: View { .padding(22) .frame(width: geometry.size.width / 1.65, height: geometry.size.width / 1.65, alignment: .center) .clipped(antialiased: true) + if !NetworkManager.shared.getNetworkState() { + HStack(spacing: 6) { + Image(systemName: "wifi.slash") + .font(.system(size: 17).weight(.medium)) + Text(NSLocalizedString("youre_offline", comment: "You're offline")) + .font(.system(size: 16)) + } + .padding(13) + .foregroundColor(.gray) + .background(Material.regular) + .clipShape(Capsule()) + .frame(height: geometry.size.width / 1.40, alignment: .bottom) + .padding(.bottom, 12) + } } content } @@ -426,7 +449,6 @@ struct CustomScrollView: View { if bleManager.blefsTransfer != nil { BLEFSHandler.shared.readSettings { settings in self.settings = settings - print(settings) self.stepCountGoal = Int(settings.stepsGoal) self.bleManagerVal.watchFace = Int(settings.watchFace) self.bleManagerVal.pineTimeStyleData = settings.pineTimeStyle diff --git a/InfiniLink/Core/Home/WatchFace.swift b/InfiniLink/Core/Home/WatchFace.swift index 4597128..d5896d2 100644 --- a/InfiniLink/Core/Home/WatchFace.swift +++ b/InfiniLink/Core/Home/WatchFace.swift @@ -258,7 +258,7 @@ struct AnalogWF: View { var body: some View { ZStack { let hour = Calendar.current.component(.hour, from: Date()) - let hour12 = Double(hour >= 12 ? hour - 12 : hour) + let hour12 = Double(hour % 12 == 0 ? 12 : hour % 12) let minute = Double(Calendar.current.component(.minute, from: Date())) Image("AnalogFace") .resizable() @@ -306,13 +306,13 @@ struct DigitalWF: View { if hour24 { hourString = String(format: "%d", currentHour) } else { - let hour12 = currentHour > 12 ? currentHour - 12 : currentHour + let hour12 = currentHour % 12 == 0 ? 12 : currentHour % 12 hourString = "\(hour12)" } let minuteString = String(format: "%02d", Calendar.current.component(.minute, from: Date())) return "\(hourString):\(minuteString)" - }(), font: .custom("JetBrainsMono-ExtraBold", size: geometry.size.width * 0.33), lineSpacing: 0) + }(), font: .custom("7-Segment", size: geometry.size.width * 0.48), lineSpacing: 0) .foregroundColor(.white) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing) .position(x: geometry.size.width / 2.0, y: geometry.size.height / 1.9) @@ -533,7 +533,7 @@ struct InfineatWF: View { } if Calendar.current.component(.hour, from: Date()) >= 12 && !hour24 { let currentHour = Calendar.current.component(.hour, from: Date()) - let hour12 = currentHour > 12 ? currentHour - 12 : (currentHour == 0 ? 12 : currentHour) // Adjust 12 PM to 12 + let hour12 = currentHour % 12 == 0 ? 12 : currentHour % 12 let hourString = String(format: "%02d", hour12) let minuteString = String(format: "%02d", Calendar.current.component(.minute, from: Date())) @@ -671,7 +671,7 @@ struct TerminalWF: View { .position(x: geometry.size.width / 2.0, y: geometry.size.height / 6.5) if !hour24 { Group { - Text("[TIME]").foregroundColor(.white) + Text("\(String(format: "%02d", (currentHour % 12 == 0) ? 12 : currentHour % 12)):\(String(format: "%02d", currentMinute)):\(String(format: "%02d", currentSecond)) \(currentHour >= 12 ? "PM" : "AM")").foregroundColor(.green) + Text("[TIME]").foregroundColor(.white) + Text("\(String(format: "%02d", currentHour % 12 == 0 ? 12 : currentHour % 12)):\(String(format: "%02d", currentMinute)):\(String(format: "%02d", currentSecond)) \(currentHour >= 12 ? "PM" : "AM")").foregroundColor(.green) } .font(.custom("JetBrainsMono-Bold", size: geometry.size.width * 0.085)) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) @@ -799,7 +799,7 @@ struct CasioWF: View { if hour24 { hourString = String(format: "%d", currentHour) } else { - let hour12 = currentHour > 12 ? currentHour - 12 : currentHour + let hour12 = currentHour % 12 == 0 ? 12 : currentHour hourString = "\(hour12)" } let minuteString = String(format: "%02d", Calendar.current.component(.minute, from: Date())) diff --git a/InfiniLink/Core/Weather/Views/WeatherDetailView.swift b/InfiniLink/Core/Weather/Views/WeatherDetailView.swift index bf53fda..22be6ea 100644 --- a/InfiniLink/Core/Weather/Views/WeatherDetailView.swift +++ b/InfiniLink/Core/Weather/Views/WeatherDetailView.swift @@ -9,6 +9,7 @@ import SwiftUI struct WeatherDetailView: View { @ObservedObject var bleManagerVal = BLEManagerVal.shared + @ObservedObject var networkManager = NetworkManager.shared var celsius: Bool { (UnitTemperature.current == .celsius && deviceData.chosenWeatherMode == "System") || deviceData.chosenWeatherMode == "Metric" @@ -63,61 +64,73 @@ struct WeatherDetailView: View { .frame(maxWidth: .infinity, alignment: .trailing) Divider() VStack { - if bleManagerVal.loadingWeather { - Spacer() - ProgressView(NSLocalizedString("loading_weather", comment: "")) - Spacer() - } else { - ScrollView { - VStack(spacing: 8) { - Image(systemName: getIcon(icon: bleManagerVal.weatherInformation.icon)) - .font(.system(size: 45).weight(.medium)) - HStack { - Text(temp(bleManagerVal.weatherInformation.minTemperature)) - .foregroundColor(.gray) - Text(temp(bleManagerVal.weatherInformation.temperature)) - .font(.system(size: 35).weight(.semibold)) - Text(temp(bleManagerVal.weatherInformation.maxTemperature)) + if networkManager.getNetworkState() { + if bleManagerVal.loadingWeather || !networkManager.getNetworkState() { + ProgressView(NSLocalizedString("loading_weather", comment: "")) + .frame(maxHeight: .infinity) + } else { + ScrollView { + VStack(spacing: 8) { + Image(systemName: getIcon(icon: bleManagerVal.weatherInformation.icon)) + .font(.system(size: 45).weight(.medium)) + HStack { + Text(temp(bleManagerVal.weatherInformation.minTemperature)) + .foregroundColor(.gray) + Text(temp(bleManagerVal.weatherInformation.temperature)) + .font(.system(size: 35).weight(.semibold)) + Text(temp(bleManagerVal.weatherInformation.maxTemperature)) + .foregroundColor(.gray) + } + Text(bleManagerVal.weatherInformation.shortDescription) .foregroundColor(.gray) + .font(.body.weight(.semibold)) } - Text(bleManagerVal.weatherInformation.shortDescription) - .foregroundColor(.gray) - .font(.body.weight(.semibold)) - } - .padding() - VStack(spacing: 10) { - ForEach(bleManagerVal.weatherForecastDays, id: \.name) { day in - HStack(spacing: 6) { - Text(day.name) - .font(.body.weight(.medium)) - Image(systemName: getIcon(icon: Int(day.icon))) - .imageScale(.large) - .font(.body.weight(.medium)) - Spacer() + .padding() + VStack(spacing: 10) { + ForEach(bleManagerVal.weatherForecastDays, id: \.name) { day in HStack(spacing: 6) { - Text(temp(day.maxTemperature)) - .foregroundColor(.lightGray) - Rectangle() - .frame(height: 3) - .frame(width: { - let temperatureRange = day.maxTemperature - day.minTemperature - let relativeWidth = CGFloat(temperatureRange) / 40.0 * 60 - return relativeWidth - }()) - .cornerRadius(30) - .background(Material.thin) - Text(temp(day.minTemperature)) - .foregroundColor(.lightGray) + Text(day.name) + .font(.body.weight(.medium)) + Image(systemName: getIcon(icon: Int(day.icon))) + .imageScale(.large) + .font(.body.weight(.medium)) + Spacer() + HStack(spacing: 6) { + Text(temp(day.maxTemperature)) + .foregroundColor(.lightGray) + Rectangle() + .frame(height: 3) + .frame(width: { + let temperatureRange = day.maxTemperature - day.minTemperature + let relativeWidth = CGFloat(temperatureRange) / 40.0 * 60 + return relativeWidth + }()) + .cornerRadius(30) + .background(Material.thin) + Text(temp(day.minTemperature)) + .foregroundColor(.lightGray) + } } + .frame(maxWidth: .infinity) + .padding() + .background(Color.gray.opacity(0.3)) + .cornerRadius(15) } - .frame(maxWidth: .infinity) - .padding() - .background(Color.gray.opacity(0.3)) - .cornerRadius(15) } + .padding() } - .padding() } + } else { + VStack(spacing: 12) { + Image(systemName: "wifi.slash") + .font(.system(size: 35).weight(.semibold)) + Text(NSLocalizedString("offline_desc", comment: "")) + .font(.title2.weight(.semibold)) + .multilineTextAlignment(.center) + } + .padding() + .frame(maxHeight: .infinity) + .foregroundColor(.gray) } } } diff --git a/InfiniLink/Core/Weather/WeatherController.swift b/InfiniLink/Core/Weather/WeatherController.swift index 110ae21..9e2cc9d 100644 --- a/InfiniLink/Core/Weather/WeatherController.swift +++ b/InfiniLink/Core/Weather/WeatherController.swift @@ -106,35 +106,37 @@ class WeatherController: NSObject, ObservableObject, CLLocationManagerDelegate { private func retrieveWeatherDataThrough(API: WeatherAPI_Type) { var currentLocation: CLLocation! - if useCurrentLocation { - startReceivingSignificantLocationChanges() - if locationManager.location != nil { - currentLocation = locationManager.location - bleManagerVal.latitude = currentLocation.coordinate.latitude - bleManagerVal.longitude = currentLocation.coordinate.longitude - - DebugLogManager.shared.debug(error: "Updated Location; latitude: \(round(bleManagerVal.latitude)), longitude: \(round(bleManagerVal.longitude))", log: .app, date: Date()) - } - if !(bleManagerVal.longitude == 0 && bleManagerVal.longitude == 0) { - if API == WeatherAPI_Type.nws { - getForecastURL_NWS() - } else { - getWeatherData_WAPI() + DispatchQueue.main.async { + if self.useCurrentLocation { + self.startReceivingSignificantLocationChanges() + if self.locationManager.location != nil { + currentLocation = self.locationManager.location + self.bleManagerVal.latitude = currentLocation.coordinate.latitude + self.bleManagerVal.longitude = currentLocation.coordinate.longitude + + DebugLogManager.shared.debug(error: "Updated Location; latitude: \(round(self.bleManagerVal.latitude)), longitude: \(round(self.bleManagerVal.longitude))", log: .app, date: Date()) } - } - } else { - getCoordinateFrom(address: setLocation) { [self] coordinate, error in - guard let coordinate = coordinate, error == nil else { - print("There was an error retrieving coordinates from user-set location") - DebugLogManager.shared.debug(error: "There was an error retrieving coordinates from user-set location", log: .app, date: Date()) - return + if !(self.bleManagerVal.longitude == 0 && self.bleManagerVal.longitude == 0) { + if API == WeatherAPI_Type.nws { + self.getForecastURL_NWS() + } else { + self.getWeatherData_WAPI() + } } - bleManagerVal.latitude = coordinate.latitude - bleManagerVal.longitude = coordinate.longitude - if API == WeatherAPI_Type.nws { - getForecastURL_NWS() - } else { - getWeatherData_WAPI() + } else { + self.getCoordinateFrom(address: self.setLocation) { [self] coordinate, error in + guard let coordinate = coordinate, error == nil else { + print("There was an error retrieving coordinates from user-set location") + DebugLogManager.shared.debug(error: "There was an error retrieving coordinates from user-set location", log: .app, date: Date()) + return + } + bleManagerVal.latitude = coordinate.latitude + bleManagerVal.longitude = coordinate.longitude + if API == WeatherAPI_Type.nws { + getForecastURL_NWS() + } else { + getWeatherData_WAPI() + } } } } diff --git a/InfiniLink/InfiniLinkApp.swift b/InfiniLink/InfiniLinkApp.swift index 57da495..a7bdc92 100644 --- a/InfiniLink/InfiniLinkApp.swift +++ b/InfiniLink/InfiniLinkApp.swift @@ -11,11 +11,14 @@ import SwiftUI struct InfiniLink: App { let persistenceController = PersistenceController.shared - @ObservedObject var healthKitManager = HealthKitManager() + @ObservedObject var healthKitManager = HealthKitManager.shared init() { healthKitManager.requestAuthorization() requestNotificationAuthorization() + + // NWPathMonitor always returns true on first fetch, fetch on init to unlock + let _ = NetworkManager.shared.getNetworkState() } func requestNotificationAuthorization() { diff --git a/InfiniLink/Utilities/NetworkManager.swift b/InfiniLink/Utilities/NetworkManager.swift index f662f86..0002f3f 100755 --- a/InfiniLink/Utilities/NetworkManager.swift +++ b/InfiniLink/Utilities/NetworkManager.swift @@ -11,21 +11,23 @@ import Network class NetworkManager: ObservableObject { static let shared = NetworkManager() - let monitor = NWPathMonitor() - let queue = DispatchQueue(label: "NetworkManager") - @Published var isConnected = false - + private let monitor = NWPathMonitor() + @Published var connected = true + init() { - checkStatus() - } - - func checkStatus() { monitor.pathUpdateHandler = { path in - DispatchQueue.main.async { - self.isConnected = path.status == .satisfied + if path.status == .satisfied { + self.connected = true + } else { + self.connected = false } } + + let queue = DispatchQueue(label: "Monitor") monitor.start(queue: queue) } + + func getNetworkState() -> Bool { + return connected + } } - diff --git a/InfiniLink/en.lproj/Localizable.strings b/InfiniLink/en.lproj/Localizable.strings index b51d666..30497b1 100644 --- a/InfiniLink/en.lproj/Localizable.strings +++ b/InfiniLink/en.lproj/Localizable.strings @@ -292,3 +292,7 @@ "set_location" = "Location"; "always_allow_location_services" = "Always Allow Current Location"; "loading_weather" = "Loading weather..."; + +"youre_offline" = "You're offline"; +"offline_desc" = "You're offline. Please connect to the internet to continue."; +"offline" = "Offline..."; diff --git a/InfiniLink/fr.lproj/Localizable.strings b/InfiniLink/fr.lproj/Localizable.strings index 2f867c5..290dc18 100644 --- a/InfiniLink/fr.lproj/Localizable.strings +++ b/InfiniLink/fr.lproj/Localizable.strings @@ -290,3 +290,7 @@ "set_location" = "Location"; "always_allow_location_services" = "Always Allow Current Location"; "loading_weather" = "Loading weather..."; + +"youre_offline" = "You're offline"; +"offline_desc" = "You're offline. Please connect to the internet to continue."; +"offline" = "Offline..."; diff --git a/InfiniLink/ko.lproj/Localizable.strings b/InfiniLink/ko.lproj/Localizable.strings index 790af57..5535be1 100644 --- a/InfiniLink/ko.lproj/Localizable.strings +++ b/InfiniLink/ko.lproj/Localizable.strings @@ -290,3 +290,8 @@ "set_location" = "Location"; "always_allow_location_services" = "Always Allow Current Location"; "loading_weather" = "Loading weather..."; + +"youre_offline" = "You're offline"; +"offline_desc" = "You're offline. Please connect to the internet to continue."; +"offline" = "Offline..."; + diff --git a/InfiniLink/ru.lproj/Localizable.strings b/InfiniLink/ru.lproj/Localizable.strings index e8e0f2f..9b5f64c 100644 --- a/InfiniLink/ru.lproj/Localizable.strings +++ b/InfiniLink/ru.lproj/Localizable.strings @@ -290,3 +290,7 @@ "set_location" = "Location"; "always_allow_location_services" = "Always Allow Current Location"; "loading_weather" = "Loading weather..."; + +"youre_offline" = "You're offline"; +"offline_desc" = "You're offline. Please connect to the internet to continue."; +"offline" = "Offline..."; diff --git a/InfiniLink/zh-Hant.lproj/Localizable.strings b/InfiniLink/zh-Hant.lproj/Localizable.strings index 3174b86..5517154 100644 --- a/InfiniLink/zh-Hant.lproj/Localizable.strings +++ b/InfiniLink/zh-Hant.lproj/Localizable.strings @@ -290,3 +290,8 @@ "set_location" = "Location"; "always_allow_location_services" = "Always Allow Current Location"; "loading_weather" = "Loading weather..."; + +"youre_offline" = "You're offline"; +"offline_desc" = "You're offline. Please connect to the internet to continue."; +"offline" = "Offline..."; +