From b1aee0a7d67061ebbe3126c8f1076e93ae2fecd6 Mon Sep 17 00:00:00 2001 From: Austin Payne Date: Thu, 15 Feb 2024 07:59:08 -0700 Subject: [PATCH 1/2] feature: add icon when downloading tiles for offline use It can be difficult to tell when the app is in progress of downloading map tiles for offline use, especially when hitting a slow server like USGS or on a slow network. Adds a download circle icon to indicate download progress to the user. Also helpfully informs when a zoom level is outside the range. --- Meshtastic.xcodeproj/project.pbxproj | 4 ++++ Meshtastic/Helpers/Map/OfflineTileManager.swift | 12 ++++++++++++ .../MapKitMap/Custom/TileDownloadStatus.swift | 14 ++++++++++++++ Meshtastic/Views/Nodes/NodeMap.swift | 4 +++- 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 Meshtastic/Views/MapKitMap/Custom/TileDownloadStatus.swift diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index f1e6046b..224c2ff2 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ B3E905B12B71F7F300654D07 /* TextMessageField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3E905B02B71F7F300654D07 /* TextMessageField.swift */; }; C9697F9D279336B700250207 /* LocalMBTileOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */; }; C9697FA527933B8C00250207 /* SQLite in Frameworks */ = {isa = PBXBuildFile; productRef = C9697FA427933B8C00250207 /* SQLite */; }; + D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */; }; D9C9839D2B79CFD700BDBE6A /* TextMessageSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */; }; D9C983A02B79D0E800BDBE6A /* AlertButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */; }; D9C983A22B79D1A600BDBE6A /* RequestPositionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */; }; @@ -234,6 +235,7 @@ B399E8A32B6F486400E4488E /* RetryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryButton.swift; sourceTree = ""; }; B3E905B02B71F7F300654D07 /* TextMessageField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageField.swift; sourceTree = ""; }; C9697F9C279336B700250207 /* LocalMBTileOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalMBTileOverlay.swift; sourceTree = ""; }; + D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileDownloadStatus.swift; sourceTree = ""; }; D9C9839C2B79CFD700BDBE6A /* TextMessageSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextMessageSize.swift; sourceTree = ""; }; D9C9839F2B79D0E800BDBE6A /* AlertButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertButton.swift; sourceTree = ""; }; D9C983A12B79D1A600BDBE6A /* RequestPositionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPositionButton.swift; sourceTree = ""; }; @@ -494,6 +496,7 @@ DD964FC32974767D007C176F /* MapViewFitExtension.swift */, DD2AD8A7296D2DF9001FF0E7 /* MapViewSwiftUI.swift */, DDDB443529F6287000EE2349 /* MapButtons.swift */, + D9BC22DA2B7DE8E2006A37D5 /* TileDownloadStatus.swift */, ); path = Custom; sourceTree = ""; @@ -1253,6 +1256,7 @@ DDB6ABE028B13AC700384BA1 /* DeviceEnums.swift in Sources */, DD86D40C287F401000BAEB7A /* SaveChannelQRCode.swift in Sources */, DDA1C48E28DB49D3009933EC /* ChannelRoles.swift in Sources */, + D9BC22DB2B7DE8E2006A37D5 /* TileDownloadStatus.swift in Sources */, DD8ED9C8289CE4B900B3B0AB /* RoutingError.swift in Sources */, DD5E5202298EE33B00D21B61 /* admin.pb.swift in Sources */, DDC1B81A2AB5377B00C71E39 /* MessagesTips.swift in Sources */, diff --git a/Meshtastic/Helpers/Map/OfflineTileManager.swift b/Meshtastic/Helpers/Map/OfflineTileManager.swift index ab973c1e..e3250e12 100644 --- a/Meshtastic/Helpers/Map/OfflineTileManager.swift +++ b/Meshtastic/Helpers/Map/OfflineTileManager.swift @@ -11,6 +11,14 @@ import MapKit class OfflineTileManager: ObservableObject { static let shared = OfflineTileManager() + // MARK: - Public properties + + @Published var status: DownloadStatus = .downloaded + + enum DownloadStatus { + case downloaded, downloading + } + init() { print("Documents Directory = \(documentsDirectory)") createDirectoriesIfNecessary() @@ -46,6 +54,10 @@ class OfflineTileManager: ObservableObject { do { return try Data(contentsOf: tilesUrl) } catch let error as NSError where error.code == NSFileReadNoSuchFileError { + DispatchQueue.main.async { self.status = .downloading } + defer { + DispatchQueue.main.async { self.status = .downloaded } + } let data = try Data(contentsOf: overlay.url(forTilePath: path)) try data.write(to: tilesUrl) return data diff --git a/Meshtastic/Views/MapKitMap/Custom/TileDownloadStatus.swift b/Meshtastic/Views/MapKitMap/Custom/TileDownloadStatus.swift new file mode 100644 index 00000000..07cddcb8 --- /dev/null +++ b/Meshtastic/Views/MapKitMap/Custom/TileDownloadStatus.swift @@ -0,0 +1,14 @@ +import SwiftUI + +struct TileDownloadStatus: View { + @ObservedObject var tileManager = OfflineTileManager.shared + + var body: some View { + if tileManager.status == .downloading { + Image(systemName: "arrow.down.circle.fill") + .foregroundColor(.gray) + } else { + EmptyView() + } + } +} diff --git a/Meshtastic/Views/Nodes/NodeMap.swift b/Meshtastic/Views/Nodes/NodeMap.swift index d5ce3676..cd79ef0d 100644 --- a/Meshtastic/Views/Nodes/NodeMap.swift +++ b/Meshtastic/Views/Nodes/NodeMap.swift @@ -13,7 +13,6 @@ import CoreData struct NodeMap: View { @Environment(\.managedObjectContext) var context @EnvironmentObject var bleManager: BLEManager - @ObservedObject var tileManager = OfflineTileManager.shared @StateObject var appState = AppState.shared @State var selectedMapLayer: MapLayer = UserDefaults.mapLayer @State var enableMapRecentering: Bool = UserDefaults.enableMapRecentering @@ -71,6 +70,9 @@ struct NodeMap: View { .padding(.top, 16) } Spacer() + TileDownloadStatus() + .padding(.trailing, 16) + .padding(.bottom, 20) } } .ignoresSafeArea(.all, edges: [.top, .leading, .trailing]) From bdb38b426ed334b39e2239df0baad8e667cf80eb Mon Sep 17 00:00:00 2001 From: Garth Vander Houwen Date: Fri, 16 Feb 2024 16:54:56 -0800 Subject: [PATCH 2/2] Save smart postion user default --- Meshtastic/Views/Settings/AppSettings.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Meshtastic/Views/Settings/AppSettings.swift b/Meshtastic/Views/Settings/AppSettings.swift index 44b1790f..579aeda5 100644 --- a/Meshtastic/Views/Settings/AppSettings.swift +++ b/Meshtastic/Views/Settings/AppSettings.swift @@ -73,6 +73,9 @@ struct AppSettings: View { Label("appsettings.smartposition", systemImage: "brain.fill") } .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + .onChange(of: (enableSmartPosition)) { newEnableSmartPosition in + UserDefaults.enableSmartPosition = newEnableSmartPosition + } VStack { Picker("update.interval", selection: $provideLocationInterval) { ForEach(LocationUpdateInterval.allCases) { lu in